From d5c4464fe1856c46e3b534dab1570173cb411687 Mon Sep 17 00:00:00 2001 From: JianMiau Date: Mon, 10 Aug 2026 12:05:30 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BC=E4=BD=88=200.1.7=EF=BC=9A=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=9C=83=E8=A9=B1=E6=A8=A1=E5=BC=8F=E9=96=8B=E9=97=9C?= =?UTF-8?q?=EF=BC=88=E7=8D=A8=E7=AB=8B=EF=BC=8F=E5=85=B1=E7=94=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 摘要: bot 設定新增「會話模式」:預設各聊天室獨立會話;可切為所有聊天室 共用一條記憶(單一專案助理情境)。共用模式下 /new 清除全部記憶, /status 會顯示目前模式,bot 列表小字標示「共用會話」。 Co-Authored-By: Claude Fable 5 --- package.json | 2 +- src/bot.js | 26 +++++++++++++++++++------- src/config.js | 2 ++ src/registry.js | 5 ++++- src/web/public/index.html | 15 +++++++++++++-- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index b6d602e..3c13b5e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "telegram-codex-bot", - "version": "0.1.6", + "version": "0.1.7", "description": "Telegram bot powered by the local Codex CLI — zero-dependency, pm2-managed, with a built-in PM2 web viewer", "license": "MIT", "type": "commonjs", diff --git a/src/bot.js b/src/bot.js index 67bd5e6..44b99ea 100644 --- a/src/bot.js +++ b/src/bot.js @@ -136,14 +136,14 @@ function startBot(config) { } function statusText(chatId) { - const s = sessions.get(chatId); + const s = sessions.get(sessionKeyOf(chatId)); return [ '📋 目前狀態', `工作目錄:${config.workDir}`, `沙盒模式:${config.sandbox}`, `模型:${config.model || '(codex 預設)'}`, `推理強度:${config.reasoningEffort || '(預設)'}`, - `速度:${config.serviceTier || '(預設)'}`, + `會話模式:${config.sharedSession ? '所有聊天室共用' : '各聊天室獨立'}`, s ? `會話:${s.threadId}\n最後使用:${s.updatedAt}` : '會話:尚未建立(下一則訊息會開新會話)', ].join('\n'); } @@ -159,9 +159,15 @@ function startBot(config) { '群組中要 @我 或回覆我的訊息才會觸發。可以直接傳圖片(附文字說明)。', ].join('\n'); + // 會話 key:獨立模式用 chat id,共用模式所有聊天室用同一把 + function sessionKeyOf(chatId) { + return config.sharedSession ? 'shared' : chatId; + } + async function handleMessage(message) { if (!message || message.from?.id === botId) return; const chatId = message.chat.id; + const sessionKey = sessionKeyOf(chatId); const text = message.text || message.caption || ''; const photo = pickPhoto(message); if (!text && !photo) return; @@ -174,8 +180,14 @@ function startBot(config) { return; } if (matchCommand(content, NEW_COMMANDS)) { - sessions.clear(chatId); - await t.sendMessage(chatId, '🆕 已開新會話,之前的對話記憶已清除。', { replyTo: message.message_id }); + sessions.clear(sessionKey); + await t.sendMessage( + chatId, + config.sharedSession + ? '🆕 已開新會話(此 bot 為共用會話模式,所有聊天室的記憶一併清除)。' + : '🆕 已開新會話,之前的對話記憶已清除。', + { replyTo: message.message_id } + ); return; } if (matchCommand(content, STATUS_COMMANDS)) { @@ -216,7 +228,7 @@ function startBot(config) { } const prompt = buildPrompt(message, content || '(使用者只傳了圖片,請描述並依上下文處理)'); - const existing = sessions.get(chatId); + const existing = sessions.get(sessionKey); let result; let sessionResetNote = ''; @@ -229,7 +241,7 @@ function startBot(config) { const sessionGone = /session|thread|conversation/i.test(err.message) && /not.*found|找不到|no .*(session|thread)/i.test(err.message); const modelMismatch = /recorded with model/i.test(err.message); if (existing && (sessionGone || modelMismatch)) { - sessions.clear(chatId); + sessions.clear(sessionKey); result = await runCodex({ config, prompt, threadId: null, images, onProgress }); sessionResetNote = modelMismatch ? '🆕 模型設定已變更,舊會話無法沿用,已自動開新會話(先前的對話記憶未帶入)。\n\n' @@ -239,7 +251,7 @@ function startBot(config) { } } - if (result.threadId) sessions.set(chatId, result.threadId); + if (result.threadId) sessions.set(sessionKey, result.threadId); const footer = buildFooter(config, result.usage, modelMeta, codexDefaults); const html = t.mdToTgHtml(sessionResetNote + (result.text || '(Codex 沒有回覆文字)') + footer); diff --git a/src/config.js b/src/config.js index 0035876..26b39bb 100644 --- a/src/config.js +++ b/src/config.js @@ -20,6 +20,8 @@ const DEFAULTS = { reasoningEffort: '', // 速度固定 Fast(priority = 1.5x);要改只能手動編輯設定檔 serviceTier: 'priority', + // 會話模式:false = 每個聊天室獨立會話(預設);true = 所有聊天室共用一個會話 + sharedSession: false, // 單次回應逾時(分鐘) timeoutMinutes: 30, // pm2 程序名稱(web 檢視工具會叫 -web) diff --git a/src/registry.js b/src/registry.js index 31c829f..ab7daaa 100644 --- a/src/registry.js +++ b/src/registry.js @@ -78,6 +78,7 @@ async function listBots() { sandbox: cfg?.sandbox || null, model: cfg?.model || '', reasoningEffort: cfg?.reasoningEffort || '', + sharedSession: !!cfg?.sharedSession, serviceTier: cfg?.serviceTier || '', timeoutMinutes: cfg?.timeoutMinutes || null, tokenMasked: maskToken(cfg?.telegramToken), @@ -112,7 +113,7 @@ function validateBotInput({ name, token, workDir, sandbox }, { isCreate }) { } } -async function createBot({ name, token, workDir, sandbox, model, reasoningEffort, timeoutMinutes }) { +async function createBot({ name, token, workDir, sandbox, model, reasoningEffort, sharedSession, timeoutMinutes }) { validateBotInput({ name, token, workDir, sandbox }, { isCreate: true }); const reg = loadRegistry(); if (reg.bots[name]) throw new Error(`已有同名 bot:${name}`); @@ -128,6 +129,7 @@ async function createBot({ name, token, workDir, sandbox, model, reasoningEffort sandbox: sandbox || 'workspace-write', model: model || '', reasoningEffort: reasoningEffort || '', + sharedSession: !!sharedSession, serviceTier: 'priority', // 速度固定 Fast 1.5x timeoutMinutes: Number(timeoutMinutes) || 30, pm2Name: name, @@ -158,6 +160,7 @@ async function updateBot(name, patch) { } if (patch.model !== undefined) raw.model = patch.model; if (patch.reasoningEffort !== undefined) raw.reasoningEffort = patch.reasoningEffort; + if (patch.sharedSession !== undefined) raw.sharedSession = !!patch.sharedSession; if (patch.timeoutMinutes !== undefined && Number(patch.timeoutMinutes) > 0) { raw.timeoutMinutes = Number(patch.timeoutMinutes); } diff --git a/src/web/public/index.html b/src/web/public/index.html index 0c98de2..33a2747 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -219,6 +219,14 @@
速度固定為 Fast(1.5x)
+
+ + +
共用模式下 /new 會清掉所有聊天室的記憶
+
@@ -374,8 +382,8 @@ function renderBots(bots) { '' + statusBadge(b.status) + '' + '' + esc(shortPath(b.workDir)) + '' + '' + esc(b.sandbox || '-') + - ((b.model || b.reasoningEffort) - ? '
' + esc([b.model, b.reasoningEffort].filter(Boolean).join(' / ')) + '' + ((b.model || b.reasoningEffort || b.sharedSession) + ? '
' + esc([b.model, b.reasoningEffort, b.sharedSession ? '共用會話' : ''].filter(Boolean).join(' / ')) + '' : '') + '' + '' + esc(b.tokenMasked || '-') + '' + '' + fmtBytes(b.memory) + '' + @@ -444,6 +452,7 @@ function openCreate() { document.getElementById('f-workdir').value = ''; document.getElementById('f-sandbox').value = 'workspace-write'; populateModelSelects('', ''); + document.getElementById('f-session').value = 'per-chat'; document.getElementById('f-timeout').value = '30'; document.getElementById('f-autostart-wrap').style.display = ''; document.getElementById('f-autostart').checked = true; @@ -465,6 +474,7 @@ function openEdit(name) { document.getElementById('f-workdir').value = b.workDir || ''; document.getElementById('f-sandbox').value = b.sandbox || 'workspace-write'; populateModelSelects(b.model || '', b.reasoningEffort || ''); + document.getElementById('f-session').value = b.sharedSession ? 'shared' : 'per-chat'; document.getElementById('f-timeout').value = b.timeoutMinutes || 30; document.getElementById('f-autostart-wrap').style.display = 'none'; document.getElementById('bot-modal').classList.add('show'); @@ -485,6 +495,7 @@ async function saveBot() { sandbox: document.getElementById('f-sandbox').value, model: document.getElementById('f-model').value, reasoningEffort: document.getElementById('f-effort').value, + sharedSession: document.getElementById('f-session').value === 'shared', timeoutMinutes: document.getElementById('f-timeout').value, }; try {