Files
telegram-codex-bot/src/config.js
T
JianMiauandClaude Fable 5 b80d6f0a1b 發佈 0.1.8:支援傳圖/傳檔給使用者,新增連網開關
摘要:
1. 交件匣機制:每輪 prompt 附系統規則,Codex 把要給使用者的檔案放進
   工作目錄的 .tgcodex-outbox/,回覆後 bot 自動傳到 Telegram(圖片走
   sendPhoto、其餘或超過 10MB 走 sendDocument)並清空,一輪上限 10 個檔案。
2. bot 設定新增「允許 Codex 連網」開關(-c sandbox_workspace_write.network_access),
   開啟後可用 codex 的 imagegen 技能生圖,已實測產出 PNG 進交件匣。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 17:13:12 +08:00

117 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
// 設定載入與驗證。優先序:env 變數 > 設定檔 > 預設值。
const fs = require('fs');
const path = require('path');
const CONFIG_BASENAME = 'tgcodex.config.json';
const SANDBOX_MODES = ['read-only', 'workspace-write', 'danger-full-access'];
const DEFAULTS = {
// Telegram Bot tokenBotFather 取得)
telegramToken: '',
// Codex 可讀寫的專案目錄(相對路徑以設定檔所在目錄為基準)
workDir: '.',
// Codex 沙盒模式,預設可讀寫專案目錄
sandbox: 'workspace-write',
// 指定模型;空字串 = 使用 codex 預設
model: '',
// 推理強度(low/medium/high/xhigh/max/ultra…依模型而定);空字串 = codex 預設
reasoningEffort: '',
// 速度固定 Fastpriority = 1.5x);要改只能手動編輯設定檔
serviceTier: 'priority',
// 會話模式:false = 每個聊天室獨立會話(預設);true = 所有聊天室共用一個會話
sharedSession: false,
// 允許 Codex 在沙盒內連網(workspace-write 預設禁網;要呼叫外部 API 如生圖服務才開)
networkAccess: false,
// 單次回應逾時(分鐘)
timeoutMinutes: 30,
// pm2 程序名稱(web 檢視工具會叫 <pm2Name>-web
pm2Name: 'tgcodex-bot',
// 內建 PM2 web 檢視工具
web: { enabled: true, port: 3799, host: '127.0.0.1' },
// 手動指定 codex 入口(codex.js 或執行檔路徑);空字串 = 自動偵測
codexPath: '',
};
function findConfigPath(explicit) {
if (explicit) return path.resolve(explicit);
if (process.env.TGCODEX_CONFIG) return path.resolve(process.env.TGCODEX_CONFIG);
return path.resolve(process.cwd(), CONFIG_BASENAME);
}
function loadConfig(explicitPath) {
const file = findConfigPath(explicitPath);
let raw = {};
if (fs.existsSync(file)) {
try {
raw = JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch (err) {
throw new Error(`設定檔 ${file} 不是合法 JSON${err.message}`);
}
}
const cfg = {
...DEFAULTS,
...raw,
web: { ...DEFAULTS.web, ...(raw.web || {}) },
};
if (process.env.TGCODEX_TOKEN) cfg.telegramToken = process.env.TGCODEX_TOKEN;
if (process.env.TGCODEX_WORKDIR) cfg.workDir = process.env.TGCODEX_WORKDIR;
if (process.env.TGCODEX_SANDBOX) cfg.sandbox = process.env.TGCODEX_SANDBOX;
if (process.env.TGCODEX_MODEL) cfg.model = process.env.TGCODEX_MODEL;
if (process.env.TGCODEX_WEB_PORT) cfg.web.port = Number(process.env.TGCODEX_WEB_PORT);
if (process.env.TGCODEX_WEB_HOST) cfg.web.host = process.env.TGCODEX_WEB_HOST;
cfg.configPath = file;
cfg.configDir = path.dirname(file);
cfg.workDir = path.resolve(cfg.configDir, cfg.workDir);
cfg.stateDir = path.join(cfg.configDir, '.tgcodex');
cfg.logsDir = path.join(cfg.stateDir, 'logs');
cfg.tmpDir = path.join(cfg.stateDir, 'tmp');
if (!SANDBOX_MODES.includes(cfg.sandbox)) {
throw new Error(`sandbox 必須是 ${SANDBOX_MODES.join(' / ')},收到:${cfg.sandbox}`);
}
if (!/^[A-Za-z0-9._-]+$/.test(cfg.pm2Name)) {
throw new Error(`pm2Name 只能包含英數字、點、底線、連字號:${cfg.pm2Name}`);
}
if (cfg.web.enabled) {
const port = Number(cfg.web.port);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error(`web.port 必須是 1-65535 的整數:${cfg.web.port}`);
}
cfg.web.port = port;
}
// 速度政策固定 Fast:舊設定檔存了空值也矯正回 priority
if (!cfg.serviceTier) cfg.serviceTier = 'priority';
const minutes = Number(cfg.timeoutMinutes);
if (!(minutes > 0)) throw new Error(`timeoutMinutes 必須是正數:${cfg.timeoutMinutes}`);
cfg.timeoutMinutes = minutes;
return cfg;
}
// bot 執行前的額外檢查(web 檢視工具不需要 token,所以拆開)
function assertBotConfig(cfg) {
if (!cfg.telegramToken) {
throw new Error(
`缺少 Telegram token。請在 ${cfg.configPath} 設定 telegramToken` +
'或設環境變數 TGCODEX_TOKEN。(先跑 `telegram-codex-bot init` 產生設定檔)'
);
}
if (!fs.existsSync(cfg.workDir)) {
throw new Error(`workDir 不存在:${cfg.workDir}`);
}
}
function ensureStateDirs(cfg) {
for (const dir of [cfg.stateDir, cfg.logsDir, cfg.tmpDir]) {
fs.mkdirSync(dir, { recursive: true });
}
}
module.exports = { CONFIG_BASENAME, DEFAULTS, SANDBOX_MODES, loadConfig, assertBotConfig, ensureStateDirs, findConfigPath };