@foxden-app/foxclaw 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +18 -1
- package/CHANGELOG.md +16 -0
- package/README.md +4 -3
- package/README_EN.md +4 -3
- package/dist/antigravity/controller.d.ts +2 -0
- package/dist/antigravity/controller.js +47 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +41 -5
- package/dist/core/engine_spi.d.ts +1 -1
- package/dist/core/orchestrator.d.ts +6 -0
- package/dist/core/orchestrator.js +78 -26
- package/dist/dsh/adapter.d.ts +48 -0
- package/dist/dsh/adapter.js +192 -0
- package/dist/dsh/client.d.ts +46 -0
- package/dist/dsh/client.js +184 -0
- package/dist/dsh/permissions_plugin.d.ts +30 -0
- package/dist/dsh/permissions_plugin.js +21 -0
- package/dist/dsh/runtime.d.ts +21 -0
- package/dist/dsh/runtime.js +45 -0
- package/dist/dsh/ui.d.ts +38 -0
- package/dist/dsh/ui.js +342 -0
- package/dist/i18n.d.ts +4 -0
- package/dist/i18n.js +12 -0
- package/dist/main.js +99 -39
- package/dist/store/database.d.ts +3 -1
- package/dist/store/database.js +8 -3
- package/dist/types.d.ts +1 -1
- package/docs/dsh.md +83 -0
- package/docs/zh/dsh.md +83 -0
- package/package.json +3 -2
package/.env.example
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Configure at least one Codex, Antigravity, or DSH Telegram bot below.
|
|
2
|
+
# For DSH-only execution, leave CODEX_BOT_TOKENS empty.
|
|
2
3
|
# Comma-separated bot tokens. The 1st bot shares the default terminal CODEX_HOME (~/.codex).
|
|
3
4
|
# Additional bots receive independent isolated homes (~/.foxclaw/codex/telegram/@TelegramUsername/home).
|
|
4
5
|
CODEX_BOT_TOKENS=<codex_telegram_bot_token>
|
|
@@ -7,6 +8,22 @@ CODEX_BOT_TOKENS=<codex_telegram_bot_token>
|
|
|
7
8
|
# Comma-separated bot tokens. The 1st bot shares the default Antigravity home (~/.gemini/antigravity-cli).
|
|
8
9
|
# Additional bots receive independent isolated homes (~/.foxclaw/antigravity/telegram/@TelegramUsername/home).
|
|
9
10
|
# ANTIGRAVITY_BOT_TOKENS=<antigravity_telegram_bot_token>
|
|
11
|
+
|
|
12
|
+
# Optional DeepSeek Harness backend for existing Codex/Antigravity bots.
|
|
13
|
+
# After enabling it, use /backend dsh in Telegram.
|
|
14
|
+
# DSH_ENABLED=true
|
|
15
|
+
# DSH_SOURCE_DIR=/absolute/path/to/deepseek-harness
|
|
16
|
+
# Alternatively, use an installed CLI or its .js entry point:
|
|
17
|
+
# DSH_CLI_BIN=/absolute/path/to/dsh
|
|
18
|
+
# Optional independent DSH bot; also enables the backend automatically.
|
|
19
|
+
# DSH_BOT_TOKEN=<different_telegram_bot_token>
|
|
20
|
+
# DSH_HOME=/absolute/path/to/.dsh
|
|
21
|
+
# DSH_PROFILE=acp
|
|
22
|
+
# DSH_PATCHES=["/absolute/path/to/patch.yml"]
|
|
23
|
+
# DSH_STARTUP_TIMEOUT_MS=60000
|
|
24
|
+
# Provider credentials are managed by DSH. Environment-based keys can be set here.
|
|
25
|
+
# DEEPSEEK_API_KEY=
|
|
26
|
+
# See docs/dsh.md for setup, permissions, and plugin management.
|
|
10
27
|
|
|
11
28
|
# Required: only this Telegram user can control the bridge
|
|
12
29
|
TG_ALLOWED_USER_ID=<telegram_user_id>
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable FoxClaw changes are listed here. Each release note is bilingual so GitHub Releases and the npm package are useful to both Chinese and English readers.
|
|
4
4
|
|
|
5
|
+
## 0.12.0 - 2026-10-05
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
|
|
9
|
+
- 新增 **DeepSeek Harness (DSH) 后端**:通过 DSH 原生 ACP profile 接入,现有 Codex/Antigravity Bot 可使用 `/backend dsh` 切换,也支持 `DSH_BOT_TOKEN` 独立运行。
|
|
10
|
+
- 新增 DSH 专用 Telegram 设置面板:原生模型与推理档位、只读/工作区写入/完全访问、工具审批、持久会话列表与恢复;隐藏 Codex 多账号、认证同步和 Fast tier 控件。
|
|
11
|
+
- 支持 DSH 工具进度、最终回答、图片与 JSON 文件附件、排队、中断接管和新建会话。修复中断后的队列抢跑,以及服务重启时跨 Bot 恢复会话和队列的问题。
|
|
12
|
+
- 新增 `/plugins` 配置查看入口及中英文接入文档;插件安装/移除沿用 DSH 终端命令。状态页明确标注 ACP 尚未提供的逐轮 token 统计,现有统一 Bot 的 `/update` 在 DSH 模式下继续可用。
|
|
13
|
+
|
|
14
|
+
### English
|
|
15
|
+
|
|
16
|
+
- Added a **DeepSeek Harness (DSH) backend** using the native ACP profile. Existing Codex/Antigravity bots can switch with `/backend dsh`; a dedicated bot can run with `DSH_BOT_TOKEN`.
|
|
17
|
+
- Added DSH-specific Telegram settings for native models and reasoning levels, read-only/workspace-write/full-access presets, tool approvals, and persistent session listing/resume. Codex account, auth-sync, and Fast tier controls are omitted.
|
|
18
|
+
- Added tool progress, final answers, image/JSON attachments, queueing, interruption, takeover, and new sessions. Fixed queue races after interruption and cross-bot session/queue recovery on service restart.
|
|
19
|
+
- Added `/plugins` configuration inspection and bilingual setup documentation; package installation/removal stays in the DSH CLI. Status explicitly marks per-turn token counts as unavailable, and unified bots retain `/update` while using DSH.
|
|
20
|
+
|
|
5
21
|
## 0.11.1 - 2026-09-30
|
|
6
22
|
|
|
7
23
|
### 中文
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**狸爪:一个更适配编程需求的移动端 Codex 控制器。**
|
|
6
6
|
|
|
7
|
-
FoxClaw(狸爪)的目标很直接:让你用手机控制本机的 Codex、OpenCode 或
|
|
7
|
+
FoxClaw(狸爪)的目标很直接:让你用手机控制本机的 Codex、OpenCode、Antigravity (AGY) 或 DeepSeek Harness (DSH),把移动端变成一个真正能用的 web coding 入口。Telegram 或微信负责交互,各后端负责本地执行;你发任务、看进度、批审批、切线程,Agent 在电脑上继续写代码。DSH 通过 Telegram 接入,支持独立 Bot 或在现有 Bot 中切换。
|
|
8
8
|
|
|
9
9
|
这适合你离开办公桌去吃饭、在旅途中、在跑步机上,或者陪小孩逛公园的时候继续工作。人可以离开电脑,Codex 不必停;它会把关键进展、错误、审批请求和最终结果同步回手机。
|
|
10
10
|
|
|
@@ -38,12 +38,13 @@ FoxClaw(狸爪)的目标很直接:让你用手机控制本机的 Codex、O
|
|
|
38
38
|
- 对 Node、Telegram 机器人、Codex CLI 不太熟?看 [新手安装指南](./docs/zh/install-for-beginners.md)。
|
|
39
39
|
- 已经装好,想系统了解 `/help`、`/setup`、`/threads`、`/watch`、`/auth` 和账号轮转?看 [用户手册](./docs/zh/user-manual.md)。
|
|
40
40
|
- 想把同一组合法 ChatGPT auth 候选同步到多台机器?看 [跨节点 auth 同步配置指南](./docs/zh/cross-node-auth-sync.md)。
|
|
41
|
+
- 想接入 DeepSeek Harness,切换模型、推理档位、权限或查看插件配置?看 [DSH 后端配置](./docs/zh/dsh.md)。
|
|
41
42
|
- 想了解每个版本改了什么?看 [更新日志](./CHANGELOG.md)。
|
|
42
43
|
- 维护者准备发版?看 [发布 runbook](./docs/zh/release.md)。
|
|
43
44
|
- Git、Node、`.env` 都玩得转?直接往下看快速设置。
|
|
44
45
|
- 卡住了?看 [故障排查](./docs/zh/troubleshooting.md)。
|
|
45
46
|
|
|
46
|
-
最低要求:一个或多个 Telegram bot token、你的 Telegram 数字用户 ID、Node.js 24
|
|
47
|
+
最低要求:一个或多个 Telegram bot token、你的 Telegram 数字用户 ID、Node.js 24+,以及已配置的执行后端。Codex 使用 `CODEX_BOT_TOKENS`;DSH 独立运行使用 `DSH_BOT_TOKEN`,无需安装或登录 Codex。首次安装大约 10–20 分钟。
|
|
47
48
|
|
|
48
49
|
**30 秒体验**:启动 FoxClaw 后,给你的 Telegram 机器人发一句 `List files in DEFAULT_CWD`。Codex 会在本地检查那个目录,然后把结果发回 Telegram。
|
|
49
50
|
|
|
@@ -79,7 +80,7 @@ foxclaw start
|
|
|
79
80
|
跑 `doctor` 或 `start` 之前先把 `.env` 填好。私聊模式最小配置:
|
|
80
81
|
|
|
81
82
|
```dotenv
|
|
82
|
-
|
|
83
|
+
CODEX_BOT_TOKENS=123456:telegram-token
|
|
83
84
|
TG_ALLOWED_USER_ID=123456789
|
|
84
85
|
DEFAULT_CWD=/absolute/path/to/workspace
|
|
85
86
|
DEFAULT_APPROVAL_POLICY=on-request
|
package/README_EN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**A mobile Codex controller built for real programming workflows.**
|
|
6
6
|
|
|
7
|
-
FoxClaw turns your phone into a practical web coding cockpit for local Codex, OpenCode,
|
|
7
|
+
FoxClaw turns your phone into a practical web coding cockpit for local Codex, OpenCode, Antigravity (AGY), or DeepSeek Harness (DSH). Telegram or Weixin handles the chat interface, each backend handles local execution, and you can send tasks, inspect progress, approve actions, switch sessions, and keep working without opening a laptop. DSH connects through Telegram, using a dedicated bot or backend switching in an existing bot.
|
|
8
8
|
|
|
9
9
|
It is built for the moments when you leave your desk for lunch, commute, travel, use a treadmill, or take the kids to the park. You can step away from the keyboard while Codex keeps coding and sends progress, errors, approval requests, and final results back to your phone.
|
|
10
10
|
|
|
@@ -38,12 +38,13 @@ FoxClaw is more than message forwarding. It provides Telegram panels for Codex w
|
|
|
38
38
|
- New to Node, Telegram bots, or Codex CLI? Use the [Beginner Install Guide](./docs/install-for-beginners.md).
|
|
39
39
|
- Already installed and want the full command guide for `/help`, `/setup`, `/threads`, `/watch`, `/auth`, and auth rotation? Read the [User Manual](./docs/user-manual.md).
|
|
40
40
|
- Want to sync the same legally owned ChatGPT auth candidate pool across multiple machines? Read the [Cross-Node Auth Sync Setup Guide](./docs/cross-node-auth-sync.md).
|
|
41
|
+
- Want DeepSeek Harness with native models, reasoning, permissions, and plugin configuration? Read [DSH Backend Setup](./docs/dsh.md).
|
|
41
42
|
- Want to see what changed in each release? Read the [Changelog](./CHANGELOG.md).
|
|
42
43
|
- Maintaining a release? Use the [Release Runbook](./docs/release.md).
|
|
43
44
|
- Already comfortable with Git, Node, and `.env` files? Use the quick setup below.
|
|
44
45
|
- Something failed? Check [Troubleshooting](./docs/troubleshooting.md).
|
|
45
46
|
|
|
46
|
-
The minimum install needs one or more Telegram bot tokens, your numeric Telegram user id, Node.js 24+, and a
|
|
47
|
+
The minimum install needs one or more Telegram bot tokens, your numeric Telegram user id, Node.js 24+, and a configured execution backend. Codex uses `CODEX_BOT_TOKENS`; standalone DSH uses `DSH_BOT_TOKEN` and does not require Codex installation or login. A first install usually takes 10–20 minutes.
|
|
47
48
|
|
|
48
49
|
**30-second demo**: after FoxClaw is running, send `List files in DEFAULT_CWD` to your Telegram bot. FoxClaw asks local Codex to inspect that folder on your computer and sends the answer back to Telegram.
|
|
49
50
|
|
|
@@ -79,7 +80,7 @@ foxclaw start
|
|
|
79
80
|
Fill `.env` before running `doctor` or `start`. Minimum private-chat config:
|
|
80
81
|
|
|
81
82
|
```dotenv
|
|
82
|
-
|
|
83
|
+
CODEX_BOT_TOKENS=123456:telegram-token
|
|
83
84
|
TG_ALLOWED_USER_ID=123456789
|
|
84
85
|
DEFAULT_CWD=/absolute/path/to/workspace
|
|
85
86
|
DEFAULT_APPROVAL_POLICY=on-request
|
|
@@ -17,6 +17,7 @@ export interface UnifiedBridgeRuntimeStatus {
|
|
|
17
17
|
running: boolean;
|
|
18
18
|
connected: boolean;
|
|
19
19
|
activeTurns: number;
|
|
20
|
+
dshPendingApprovals: number;
|
|
20
21
|
botUsername: string | null;
|
|
21
22
|
codexHome: string;
|
|
22
23
|
codexAppServer?: CodexAppServerRuntimeStatus | undefined;
|
|
@@ -53,6 +54,7 @@ export declare class UnifiedBridgeCore {
|
|
|
53
54
|
private readonly defaultBackendId;
|
|
54
55
|
private readonly selfUpdater?;
|
|
55
56
|
private readonly orchestrator;
|
|
57
|
+
private readonly dshUi?;
|
|
56
58
|
private readonly watchers;
|
|
57
59
|
private readonly pendingAgyRenames;
|
|
58
60
|
private readonly stalePanelDeleteTimers;
|
|
@@ -15,8 +15,9 @@ import { CodexEngineAdapter } from '../codex_app/adapter.js';
|
|
|
15
15
|
import { OpencodeEngineAdapter } from '../opencode/adapter.js';
|
|
16
16
|
import { BridgeSessionCore } from '../controller/controller.js';
|
|
17
17
|
import { BridgeMessagingRouter } from '../channels/bridge_messaging_router.js';
|
|
18
|
-
import { BRIDGE_SCOPE_WEIXIN_PREFIX } from '../core/bridge_scope.js';
|
|
18
|
+
import { BRIDGE_SCOPE_WEIXIN_PREFIX, BRIDGE_SCOPE_TELEGRAM_PREFIX, parseTelegramTargetFromBridgeScope } from '../core/bridge_scope.js';
|
|
19
19
|
import { syncCodexLocalUsageToStore } from '../store/token_usage.js';
|
|
20
|
+
import { DshUi } from '../dsh/ui.js';
|
|
20
21
|
function formatCandidateButtonPrefix(c) {
|
|
21
22
|
const p5h = typeof c.quota?.fiveHourPercent === 'number' ? `${c.quota.fiveHourPercent}%` : '—';
|
|
22
23
|
const pw = typeof c.quota?.weeklyPercent === 'number' ? `${c.quota.weeklyPercent}%` : '—';
|
|
@@ -67,6 +68,7 @@ export class UnifiedBridgeCore {
|
|
|
67
68
|
defaultBackendId;
|
|
68
69
|
selfUpdater;
|
|
69
70
|
orchestrator;
|
|
71
|
+
dshUi;
|
|
70
72
|
watchers = new Map();
|
|
71
73
|
pendingAgyRenames = new Map();
|
|
72
74
|
stalePanelDeleteTimers = new Map();
|
|
@@ -158,7 +160,13 @@ export class UnifiedBridgeCore {
|
|
|
158
160
|
isDefault: isOpencodeDefault,
|
|
159
161
|
});
|
|
160
162
|
}
|
|
163
|
+
if (config.dsh) {
|
|
164
|
+
this.dshUi = new DshUi(config, store, logger, this.messaging);
|
|
165
|
+
initialBackends.push({ id: 'dsh', name: 'DeepSeek Harness (DSH)', engineType: 'dsh', adapter: this.dshUi.adapter });
|
|
166
|
+
}
|
|
161
167
|
const allAdapters = [this.adapter];
|
|
168
|
+
if (this.dshUi)
|
|
169
|
+
allAdapters.push(this.dshUi.adapter);
|
|
162
170
|
if (this.codexAdapter)
|
|
163
171
|
allAdapters.push(this.codexAdapter);
|
|
164
172
|
if (this.opencodeAdapter)
|
|
@@ -172,9 +180,27 @@ export class UnifiedBridgeCore {
|
|
|
172
180
|
adapters: allAdapters,
|
|
173
181
|
backends: initialBackends,
|
|
174
182
|
defaultBackendId: this.defaultBackendId,
|
|
183
|
+
ownsScope: scopeId => {
|
|
184
|
+
if (!scopeId.startsWith(BRIDGE_SCOPE_TELEGRAM_PREFIX))
|
|
185
|
+
return !config.tgMultiBotMode && !scopeId.startsWith(BRIDGE_SCOPE_WEIXIN_PREFIX);
|
|
186
|
+
const target = parseTelegramTargetFromBridgeScope(scopeId);
|
|
187
|
+
return target.botId ? target.botId === this.bot.identity : !config.tgMultiBotMode;
|
|
188
|
+
},
|
|
175
189
|
backendProvider: () => this.getBackendDescriptors(),
|
|
176
190
|
messaging: this.messaging,
|
|
177
191
|
customUi: {
|
|
192
|
+
renderSetupMenu: async (scopeId, locale, messageId) => {
|
|
193
|
+
if (this.orchestrator.getBackendDescriptorForScope(scopeId).engineType !== 'dsh' || !this.dshUi)
|
|
194
|
+
return false;
|
|
195
|
+
await this.dshUi.setup(scopeId, locale, this.orchestrator, messageId);
|
|
196
|
+
return true;
|
|
197
|
+
},
|
|
198
|
+
renderModelsMenu: async (scopeId, locale, messageId) => {
|
|
199
|
+
if (this.orchestrator.getBackendDescriptorForScope(scopeId).engineType !== 'dsh' || !this.dshUi)
|
|
200
|
+
return false;
|
|
201
|
+
await this.dshUi.models(scopeId, locale, this.orchestrator, messageId);
|
|
202
|
+
return true;
|
|
203
|
+
},
|
|
178
204
|
renderCustomStatus: (scopeId, locale) => this.renderCustomStatus(scopeId, locale),
|
|
179
205
|
renderCustomSetupRows: (scopeId, locale) => this.renderCustomSetupRows(scopeId, locale),
|
|
180
206
|
handleCustomCallback: (scopeId, data, locale, messageId, event) => this.handleCustomCallback(scopeId, data, locale, messageId, event),
|
|
@@ -185,6 +211,8 @@ export class UnifiedBridgeCore {
|
|
|
185
211
|
}
|
|
186
212
|
async getBackendDescriptors() {
|
|
187
213
|
const list = [];
|
|
214
|
+
if (this.dshUi)
|
|
215
|
+
list.push({ id: 'dsh', name: 'DeepSeek Harness (DSH)', engineType: 'dsh', adapter: this.dshUi.adapter });
|
|
188
216
|
const isCodexDefault = this.defaultBackendId === 'codex';
|
|
189
217
|
const active = await this.auth.getActiveAccount();
|
|
190
218
|
const activeQuota = active?.quota
|
|
@@ -298,6 +326,7 @@ export class UnifiedBridgeCore {
|
|
|
298
326
|
async stop() {
|
|
299
327
|
this.clearSelfUpdateStatusPoll();
|
|
300
328
|
await this.orchestrator.stop();
|
|
329
|
+
await this.dshUi?.stop();
|
|
301
330
|
this.auth.stopKeepAlive();
|
|
302
331
|
for (const [scopeId, watcher] of this.watchers.entries()) {
|
|
303
332
|
watcher.stopped = true;
|
|
@@ -381,6 +410,7 @@ export class UnifiedBridgeCore {
|
|
|
381
410
|
running: true,
|
|
382
411
|
connected: isConnected,
|
|
383
412
|
activeTurns: this.orchestrator.getActiveTurnsCount(),
|
|
413
|
+
dshPendingApprovals: this.dshUi?.pendingApprovals ?? 0,
|
|
384
414
|
botUsername: this.bot.username,
|
|
385
415
|
codexHome: this.config.codexHome ?? this.config.codexAuthDir ?? path.join(os.homedir(), '.codex'),
|
|
386
416
|
...(this.codexApp && typeof this.codexApp.getServerStatus === 'function' && this.codexApp.getServerStatus()
|
|
@@ -460,6 +490,8 @@ export class UnifiedBridgeCore {
|
|
|
460
490
|
}
|
|
461
491
|
async renderCustomStatus(scopeId, locale) {
|
|
462
492
|
const activeBackend = this.orchestrator.getBackendDescriptorForScope(scopeId);
|
|
493
|
+
if (activeBackend.engineType === 'dsh' && this.dshUi)
|
|
494
|
+
return this.dshUi.status(scopeId, locale);
|
|
463
495
|
if (activeBackend.engineType === 'codex') {
|
|
464
496
|
const codexHome = this.config.codexAuthDir ?? this.config.codexHome ?? path.join(os.homedir(), '.codex');
|
|
465
497
|
let currentCodexAccount = 'default';
|
|
@@ -567,6 +599,8 @@ export class UnifiedBridgeCore {
|
|
|
567
599
|
}
|
|
568
600
|
async handleCustomInbound(event, locale) {
|
|
569
601
|
const scopeId = event.scopeId;
|
|
602
|
+
if (this.orchestrator.getBackendDescriptorForScope(scopeId).engineType === 'dsh')
|
|
603
|
+
return false;
|
|
570
604
|
// Check for JSON token upload / document attachment
|
|
571
605
|
const doc = event.attachments?.find((a) => a.kind === 'document');
|
|
572
606
|
if (doc && (doc.fileName?.endsWith('.json') || doc.mimeType?.includes('json'))) {
|
|
@@ -725,6 +759,8 @@ export class UnifiedBridgeCore {
|
|
|
725
759
|
: '⚠️ Self-updater is not available in current runtime. Run `foxclaw update` in terminal.');
|
|
726
760
|
return true;
|
|
727
761
|
}
|
|
762
|
+
if (activeBackend.engineType === 'dsh' && this.dshUi)
|
|
763
|
+
return this.dshUi.command(scopeId, cmd, args, locale, this.orchestrator);
|
|
728
764
|
if (isCodex) {
|
|
729
765
|
if (this.codexCore) {
|
|
730
766
|
switch (cmd.toLowerCase()) {
|
|
@@ -836,6 +872,16 @@ export class UnifiedBridgeCore {
|
|
|
836
872
|
}
|
|
837
873
|
}
|
|
838
874
|
async handleCustomCallback(scopeId, data, locale, messageId, event) {
|
|
875
|
+
if (this.dshUi && await this.dshUi.callback(scopeId, data, locale, this.orchestrator, event))
|
|
876
|
+
return true;
|
|
877
|
+
if (this.orchestrator.getBackendDescriptorForScope(scopeId).engineType === 'dsh') {
|
|
878
|
+
if (!['engine:setup:main', 'engine:setup:active_mode', 'engine:setup:new', 'engine:setup:backend'].includes(data) && !data.startsWith('engine:backend:')) {
|
|
879
|
+
if (event)
|
|
880
|
+
await this.messaging.answerCallback(event.callbackQueryId, locale === 'zh' ? '请重新打开 DSH 设置面板' : 'Open the DSH setup panel');
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
839
885
|
if (this.codexCore && (data.startsWith('thread:') ||
|
|
840
886
|
data.startsWith('auth:') ||
|
|
841
887
|
data.startsWith('settings:access:') ||
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LogLevel } from './logger.js';
|
|
2
2
|
import type { ApprovalPolicyValue, SandboxModeValue } from './types.js';
|
|
3
|
+
import type { DshClientOptions } from './dsh/client.js';
|
|
3
4
|
export declare const APP_HOME: string;
|
|
4
5
|
export declare const DEFAULT_STORE_PATH: string;
|
|
5
6
|
export declare const DEFAULT_STATUS_PATH: string;
|
|
@@ -19,6 +20,8 @@ export declare function resolveEnvPath(): string;
|
|
|
19
20
|
export declare function getLoadedEnvPath(): string | null;
|
|
20
21
|
export declare function loadEnv(): void;
|
|
21
22
|
export interface AppConfig {
|
|
23
|
+
dsh?: DshClientOptions;
|
|
24
|
+
dshBotToken?: string | null;
|
|
22
25
|
codexBotTokens: string[];
|
|
23
26
|
antigravityBotTokens: string[];
|
|
24
27
|
tgBotToken: string;
|
|
@@ -112,6 +115,7 @@ export interface CodexApiProviderConfig {
|
|
|
112
115
|
export declare function loadConfig(): AppConfig;
|
|
113
116
|
export declare function buildCodexApiProviderOverrides(providers: readonly CodexApiProviderConfig[], defaultProviderId?: string | null): string[];
|
|
114
117
|
export declare function validateCodexBotTokens(tokens: readonly string[]): void;
|
|
118
|
+
export declare function parseDshPatches(value?: string): string[];
|
|
115
119
|
export declare function validateOpencodeBotToken(token: string | null, codexTokens: readonly string[]): void;
|
|
116
120
|
export declare function validateAntigravityBotTokens(tokens: readonly string[], codexTokens: readonly string[], opencodeToken: string | null): void;
|
|
117
121
|
export declare function validateAntigravityBotToken(token: string | null, codexTokens: readonly string[], opencodeToken: string | null): void;
|
package/dist/config.js
CHANGED
|
@@ -50,8 +50,12 @@ export function loadConfig() {
|
|
|
50
50
|
const codexBotTokens = parseCommaSeparatedIds(process.env.CODEX_BOT_TOKENS || process.env.CODEX_BOT_TOKEN);
|
|
51
51
|
const antigravityBotTokens = parseCommaSeparatedIds(process.env.ANTIGRAVITY_BOT_TOKENS || process.env.ANTIGRAVITY_BOT_TOKEN);
|
|
52
52
|
const opencodeBotToken = optional('OPENCODE_BOT_TOKEN');
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const dshBotToken = optional('DSH_BOT_TOKEN');
|
|
54
|
+
if (dshBotToken && [...codexBotTokens, ...antigravityBotTokens, opencodeBotToken].includes(dshBotToken)) {
|
|
55
|
+
throw new Error('DSH_BOT_TOKEN must use a different bot from Codex, Antigravity, and OpenCode');
|
|
56
|
+
}
|
|
57
|
+
if (codexBotTokens.length === 0 && antigravityBotTokens.length === 0 && !dshBotToken) {
|
|
58
|
+
throw new Error('At least one Telegram bot token must be configured via CODEX_BOT_TOKENS, ANTIGRAVITY_BOT_TOKENS, or DSH_BOT_TOKEN');
|
|
55
59
|
}
|
|
56
60
|
validateCodexBotTokens(codexBotTokens);
|
|
57
61
|
validateAntigravityBotTokens(antigravityBotTokens, codexBotTokens, opencodeBotToken);
|
|
@@ -60,11 +64,23 @@ export function loadConfig() {
|
|
|
60
64
|
const antigravityDefaultRuntimeBotToken = selectDefaultRuntimeBotToken(antigravityBotTokens, optional('ANTIGRAVITY_DEFAULT_RUNTIME_BOT_TOKEN')) ||
|
|
61
65
|
antigravityBotTokens[0] ||
|
|
62
66
|
null;
|
|
63
|
-
const totalBots = codexBotTokens.length + antigravityBotTokens.length;
|
|
64
|
-
const tgBotTokens = codexBotTokens.length > 0 ? codexBotTokens : antigravityBotTokens;
|
|
65
|
-
const tgBotToken = (codexBotTokens[0] || antigravityBotTokens[0]);
|
|
67
|
+
const totalBots = codexBotTokens.length + antigravityBotTokens.length + (dshBotToken ? 1 : 0);
|
|
68
|
+
const tgBotTokens = codexBotTokens.length > 0 ? codexBotTokens : antigravityBotTokens.length > 0 ? antigravityBotTokens : dshBotToken ? [dshBotToken] : [];
|
|
69
|
+
const tgBotToken = (codexBotTokens[0] || antigravityBotTokens[0] || dshBotToken);
|
|
66
70
|
const antigravityBotToken = antigravityBotTokens[0] || null;
|
|
67
71
|
const config = {
|
|
72
|
+
dshBotToken,
|
|
73
|
+
...(boolEnv('DSH_ENABLED', Boolean(dshBotToken || optional('DSH_SOURCE_DIR') || optional('DSH_CLI_BIN'))) ? {
|
|
74
|
+
dsh: {
|
|
75
|
+
cliBin: optional('DSH_CLI_BIN') || resolveCommand('dsh') || 'dsh',
|
|
76
|
+
sourceDir: optional('DSH_SOURCE_DIR'),
|
|
77
|
+
home: optional('DSH_HOME'),
|
|
78
|
+
profile: optional('DSH_PROFILE') || 'acp',
|
|
79
|
+
patches: parseDshPatches(process.env.DSH_PATCHES),
|
|
80
|
+
runtimeDir: path.join(APP_HOME, 'runtime', 'dsh'),
|
|
81
|
+
startupTimeoutMs: Number(optional('DSH_STARTUP_TIMEOUT_MS') || 60000),
|
|
82
|
+
},
|
|
83
|
+
} : {}),
|
|
68
84
|
codexBotTokens,
|
|
69
85
|
antigravityBotTokens,
|
|
70
86
|
tgBotToken,
|
|
@@ -142,6 +158,12 @@ export function loadConfig() {
|
|
|
142
158
|
voiceTextLimit: intEnv('VOICE_TEXT_LIMIT', 2800),
|
|
143
159
|
voiceTtsTimeoutMs: intEnv('VOICE_TTS_TIMEOUT_MS', 300_000),
|
|
144
160
|
};
|
|
161
|
+
if (dshBotToken && !config.dsh)
|
|
162
|
+
throw new Error('DSH_BOT_TOKEN requires DSH_ENABLED=true');
|
|
163
|
+
if (config.dsh && (!Number.isSafeInteger(config.dsh.startupTimeoutMs) || config.dsh.startupTimeoutMs <= 0))
|
|
164
|
+
throw new Error('DSH_STARTUP_TIMEOUT_MS must be a positive integer');
|
|
165
|
+
if (config.wxEnabled && codexBotTokens.length === 0 && antigravityBotTokens.length === 0)
|
|
166
|
+
throw new Error('WX_ENABLED currently requires a Codex or Antigravity runtime alongside DSH');
|
|
145
167
|
ensureAppDirs(config);
|
|
146
168
|
return config;
|
|
147
169
|
}
|
|
@@ -176,6 +198,20 @@ export function validateCodexBotTokens(tokens) {
|
|
|
176
198
|
seen.add(token);
|
|
177
199
|
}
|
|
178
200
|
}
|
|
201
|
+
export function parseDshPatches(value) {
|
|
202
|
+
if (!value?.trim())
|
|
203
|
+
return [];
|
|
204
|
+
let parsed;
|
|
205
|
+
try {
|
|
206
|
+
parsed = JSON.parse(value);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
throw new Error('DSH_PATCHES must be a JSON array of patch paths');
|
|
210
|
+
}
|
|
211
|
+
if (!Array.isArray(parsed) || !parsed.every(item => typeof item === 'string' && item.trim().length > 0))
|
|
212
|
+
throw new Error('DSH_PATCHES must be a JSON array of patch paths');
|
|
213
|
+
return parsed.map(item => path.resolve(item));
|
|
214
|
+
}
|
|
179
215
|
export function validateOpencodeBotToken(token, codexTokens) {
|
|
180
216
|
if (!token)
|
|
181
217
|
return;
|
|
@@ -60,7 +60,7 @@ export interface EngineTurnErrorContext {
|
|
|
60
60
|
export interface IEngineAdapter {
|
|
61
61
|
readonly id: string;
|
|
62
62
|
readonly name: string;
|
|
63
|
-
listModels(): Promise<EngineModel[]>;
|
|
63
|
+
listModels(scopeId?: string): Promise<EngineModel[]>;
|
|
64
64
|
executeTurn(request: EngineTurnRequest): EngineTurnExecution;
|
|
65
65
|
preflightTurn?(request: EngineTurnRequest): Promise<void>;
|
|
66
66
|
handleTurnError?(context: EngineTurnErrorContext): Promise<boolean>;
|
|
@@ -21,9 +21,12 @@ export interface UnifiedActiveTurn {
|
|
|
21
21
|
stepIndex?: number;
|
|
22
22
|
toolCount: number;
|
|
23
23
|
currentTool?: string | null;
|
|
24
|
+
suppressQueueDrain?: boolean;
|
|
24
25
|
startTime: number;
|
|
25
26
|
}
|
|
26
27
|
export interface EngineCustomUiHook {
|
|
28
|
+
renderSetupMenu?(scopeId: string, locale: AppLocale, messageId?: number): Promise<boolean>;
|
|
29
|
+
renderModelsMenu?(scopeId: string, locale: AppLocale, messageId?: number): Promise<boolean>;
|
|
27
30
|
renderCustomStatus?(scopeId: string, locale: AppLocale): Promise<string | null>;
|
|
28
31
|
renderCustomSetupRows?(scopeId: string, locale: AppLocale): Promise<InlineKeyboard>;
|
|
29
32
|
handleCustomCallback?(scopeId: string, data: string, locale: AppLocale, messageId?: number, event?: TelegramCallbackEvent): Promise<boolean>;
|
|
@@ -42,6 +45,8 @@ export declare class UnifiedChannelOrchestrator {
|
|
|
42
45
|
private readonly defaultBackendId;
|
|
43
46
|
private readonly activeTurns;
|
|
44
47
|
private readonly stalePanelDeleteTimers;
|
|
48
|
+
private readonly recoveryTimers;
|
|
49
|
+
private readonly ownsScope;
|
|
45
50
|
private readonly backendProvider?;
|
|
46
51
|
get adapter(): IEngineAdapter;
|
|
47
52
|
constructor(options: {
|
|
@@ -54,6 +59,7 @@ export declare class UnifiedChannelOrchestrator {
|
|
|
54
59
|
backends?: BackendDescriptor[];
|
|
55
60
|
backendProvider?: (() => Promise<BackendDescriptor[]> | BackendDescriptor[]) | undefined;
|
|
56
61
|
defaultBackendId?: string;
|
|
62
|
+
ownsScope?: (scopeId: string) => boolean;
|
|
57
63
|
messaging: TelegramMessagingPort;
|
|
58
64
|
customUi?: EngineCustomUiHook | undefined;
|
|
59
65
|
});
|