@foxden-app/foxclaw 0.11.1 → 0.13.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.
Files changed (138) hide show
  1. package/.env.example +18 -1
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +4 -3
  4. package/README_EN.md +4 -3
  5. package/dist/antigravity/account_ui.d.ts +34 -0
  6. package/dist/antigravity/account_ui.js +354 -0
  7. package/dist/antigravity/adapter.js +1 -1
  8. package/dist/antigravity/auth.d.ts +4 -1
  9. package/dist/antigravity/auth.js +17 -5
  10. package/dist/antigravity/controller.d.ts +3 -106
  11. package/dist/antigravity/controller.js +2 -2083
  12. package/dist/antigravity/conversation_ui.d.ts +56 -0
  13. package/dist/antigravity/conversation_ui.js +509 -0
  14. package/dist/antigravity/ui_callbacks.d.ts +6 -0
  15. package/dist/antigravity/ui_callbacks.js +6 -0
  16. package/dist/bridge/unified_bridge.d.ts +114 -0
  17. package/dist/bridge/unified_bridge.js +1343 -0
  18. package/dist/channels/bridge_messaging_router.d.ts +8 -4
  19. package/dist/channels/bridge_messaging_router.js +15 -4
  20. package/dist/channels/telegram/telegram_messaging_port.d.ts +16 -0
  21. package/dist/channels/telegram/telegram_messaging_port.js +22 -2
  22. package/dist/channels/text_actions.d.ts +14 -0
  23. package/dist/channels/text_actions.js +48 -0
  24. package/dist/channels/weixin/weixin_channel_adapter.d.ts +3 -4
  25. package/dist/channels/weixin/weixin_channel_adapter.js +2 -2
  26. package/dist/channels/weixin/weixin_messaging_port.d.ts +16 -1
  27. package/dist/channels/weixin/weixin_messaging_port.js +34 -7
  28. package/dist/codex_app/activity.d.ts +62 -0
  29. package/dist/codex_app/activity.js +418 -0
  30. package/dist/codex_app/adapter.d.ts +2 -2
  31. package/dist/codex_app/adapter.js +55 -25
  32. package/dist/codex_app/backend_ui.d.ts +4 -0
  33. package/dist/codex_app/backend_ui.js +48 -0
  34. package/dist/codex_app/client.js +0 -12
  35. package/dist/config.d.ts +4 -0
  36. package/dist/config.js +41 -5
  37. package/dist/controller/access.d.ts +1 -11
  38. package/dist/controller/access.js +1 -33
  39. package/dist/controller/activity.d.ts +1 -62
  40. package/dist/controller/activity.js +2 -418
  41. package/dist/controller/auth_errors.d.ts +11 -0
  42. package/dist/controller/auth_errors.js +124 -0
  43. package/dist/controller/auth_files.d.ts +13 -0
  44. package/dist/controller/auth_files.js +156 -0
  45. package/dist/controller/auth_presentation.d.ts +77 -0
  46. package/dist/controller/auth_presentation.js +697 -0
  47. package/dist/controller/auth_quota_service.d.ts +27 -0
  48. package/dist/controller/auth_quota_service.js +151 -0
  49. package/dist/controller/controller.d.ts +30 -56
  50. package/dist/controller/controller.js +181 -3497
  51. package/dist/controller/interactions.d.ts +74 -0
  52. package/dist/controller/interactions.js +560 -0
  53. package/dist/controller/local_usage_service.d.ts +19 -0
  54. package/dist/controller/local_usage_service.js +62 -0
  55. package/dist/controller/native_panel_service.d.ts +55 -0
  56. package/dist/controller/native_panel_service.js +214 -0
  57. package/dist/controller/native_panels.d.ts +61 -0
  58. package/dist/controller/native_panels.js +784 -0
  59. package/dist/controller/shared_helpers.d.ts +53 -0
  60. package/dist/controller/shared_helpers.js +399 -0
  61. package/dist/controller/state_types.d.ts +390 -0
  62. package/dist/controller/state_types.js +82 -0
  63. package/dist/controller/turn_rendering.d.ts +22 -0
  64. package/dist/controller/turn_rendering.js +288 -0
  65. package/dist/core/access.d.ts +11 -0
  66. package/dist/core/access.js +33 -0
  67. package/dist/core/attachment_files.d.ts +13 -0
  68. package/dist/core/attachment_files.js +180 -0
  69. package/dist/core/attachment_types.d.ts +22 -0
  70. package/dist/core/attachment_types.js +1 -0
  71. package/dist/core/attachments.d.ts +5 -3
  72. package/dist/core/attachments.js +2 -2
  73. package/dist/core/backend_registry.d.ts +22 -0
  74. package/dist/core/backend_registry.js +50 -0
  75. package/dist/core/backend_ui.d.ts +28 -0
  76. package/dist/core/backend_ui.js +1 -0
  77. package/dist/core/channel_events.d.ts +48 -0
  78. package/dist/core/channel_events.js +1 -0
  79. package/dist/core/channel_port.d.ts +52 -8
  80. package/dist/core/engine_spi.d.ts +20 -5
  81. package/dist/core/listener_scope.d.ts +11 -0
  82. package/dist/core/listener_scope.js +10 -0
  83. package/dist/core/message_text.d.ts +2 -0
  84. package/dist/core/message_text.js +28 -0
  85. package/dist/core/network_errors.d.ts +1 -0
  86. package/dist/core/network_errors.js +23 -0
  87. package/dist/core/orchestrator.d.ts +57 -22
  88. package/dist/core/orchestrator.js +877 -477
  89. package/dist/core/queued_engine_input.d.ts +12 -0
  90. package/dist/core/queued_engine_input.js +16 -0
  91. package/dist/core/runtime_supervisor.d.ts +19 -0
  92. package/dist/core/runtime_supervisor.js +45 -0
  93. package/dist/core/scope_operations.d.ts +7 -0
  94. package/dist/core/scope_operations.js +19 -0
  95. package/dist/core/stream_preview.js +21 -9
  96. package/dist/dsh/adapter.d.ts +48 -0
  97. package/dist/dsh/adapter.js +205 -0
  98. package/dist/dsh/backend.d.ts +7 -0
  99. package/dist/dsh/backend.js +21 -0
  100. package/dist/dsh/client.d.ts +46 -0
  101. package/dist/dsh/client.js +184 -0
  102. package/dist/dsh/permissions_plugin.d.ts +30 -0
  103. package/dist/dsh/permissions_plugin.js +21 -0
  104. package/dist/dsh/runtime.d.ts +20 -0
  105. package/dist/dsh/runtime.js +36 -0
  106. package/dist/dsh/ui.d.ts +38 -0
  107. package/dist/dsh/ui.js +342 -0
  108. package/dist/i18n.d.ts +4 -0
  109. package/dist/i18n.js +12 -0
  110. package/dist/main.js +144 -72
  111. package/dist/opencode/adapter.js +28 -9
  112. package/dist/opencode/controller.js +1 -1
  113. package/dist/store/channel_inbox.d.ts +14 -0
  114. package/dist/store/channel_inbox.js +21 -0
  115. package/dist/store/database.d.ts +10 -4
  116. package/dist/store/database.js +13 -3
  117. package/dist/store/task_journal.d.ts +32 -0
  118. package/dist/store/task_journal.js +70 -0
  119. package/dist/telegram/api.d.ts +2 -2
  120. package/dist/telegram/api.js +10 -28
  121. package/dist/telegram/gateway.d.ts +13 -27
  122. package/dist/telegram/gateway.js +86 -43
  123. package/dist/telegram/media.d.ts +2 -34
  124. package/dist/telegram/media.js +1 -180
  125. package/dist/telegram/task_preview.d.ts +13 -0
  126. package/dist/telegram/task_preview.js +92 -0
  127. package/dist/telegram/task_result.d.ts +2 -0
  128. package/dist/telegram/task_result.js +12 -0
  129. package/dist/telegram/text.d.ts +1 -1
  130. package/dist/telegram/text.js +2 -27
  131. package/dist/types.d.ts +2 -2
  132. package/docs/dsh.md +83 -0
  133. package/docs/release.md +13 -11
  134. package/docs/zh/architecture-review.md +106 -0
  135. package/docs/zh/dsh.md +83 -0
  136. package/docs/zh/release.md +13 -11
  137. package/docs/zh/telegram-experience.md +40 -0
  138. package/package.json +7 -5
package/dist/dsh/ui.js ADDED
@@ -0,0 +1,342 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { randomBytes } from 'node:crypto';
5
+ import { DshClient } from './client.js';
6
+ import { DshEngineAdapter } from './adapter.js';
7
+ /** DSH controls shared by the unified bot and the optional dedicated bot. */
8
+ export class DshUi {
9
+ config;
10
+ store;
11
+ logger;
12
+ messaging;
13
+ actions = new Map();
14
+ approvals = new Map();
15
+ adapter;
16
+ get pendingApprovals() { return this.approvals.size; }
17
+ constructor(config, store, logger, messaging) {
18
+ this.config = config;
19
+ this.store = store;
20
+ this.logger = logger;
21
+ this.messaging = messaging;
22
+ if (!config.dsh)
23
+ throw new Error('DSH backend is not configured');
24
+ const dsh = config.dsh;
25
+ this.adapter = new DshEngineAdapter({
26
+ createClient: scopeId => {
27
+ const client = new DshClient(dsh, scopeId, logger);
28
+ client.permissionHandler = (request, signal) => this.requestPermission(scopeId, request, signal);
29
+ client.cancelPermissions = () => {
30
+ for (const pending of this.approvals.values())
31
+ if (pending.scopeId === scopeId)
32
+ pending.resolve({ outcome: { outcome: 'cancelled' } });
33
+ };
34
+ return client;
35
+ },
36
+ preferences: scopeId => {
37
+ const binding = store.getBinding(scopeId);
38
+ const settings = store.getChatSettings(scopeId);
39
+ return { cwd: binding?.cwd || config.defaultCwd, threadId: binding?.threadId || null, model: settings?.model ?? null, effort: settings?.reasoningEffort ?? null, access: settings?.accessPreset ?? this.defaultAccess() };
40
+ },
41
+ bind: (scopeId, id, cwd) => {
42
+ store.setBinding(scopeId, id, cwd);
43
+ const settings = store.getChatSettings(scopeId);
44
+ store.setScopeBackendBinding(scopeId, 'dsh', id, cwd, { model: settings?.model ?? null, reasoningEffort: settings?.reasoningEffort ?? null, accessPreset: settings?.accessPreset ?? this.defaultAccess() });
45
+ },
46
+ });
47
+ }
48
+ defaultAccess() { return this.config.defaultSandboxMode === 'danger-full-access' ? 'full-access' : this.config.defaultSandboxMode === 'read-only' ? 'read-only' : 'default'; }
49
+ copy(locale, zh, en) { return locale === 'zh' ? zh : en; }
50
+ status(scopeId, locale) {
51
+ const settings = this.store.getChatSettings(scopeId);
52
+ return `• **${this.copy(locale, '推理档位', 'Reasoning')}**: \`${settings?.reasoningEffort ?? 'default'}\`\n• **${this.copy(locale, 'DSH 权限', 'DSH access')}**: \`${settings?.accessPreset ?? this.defaultAccess()}\`\n• **${this.copy(locale, '配置', 'Settings')}**: \`/setup\` · \`/plugins\``;
53
+ }
54
+ button(scopeId, kind, value, text, cwd) {
55
+ for (const [key, action] of this.actions)
56
+ if (action.expires < Date.now())
57
+ this.actions.delete(key);
58
+ const key = randomBytes(6).toString('hex');
59
+ this.actions.set(key, { scopeId, kind, value, ...(cwd ? { cwd } : {}), expires: Date.now() + Math.max(this.config.telegramPanelTtlMs, 300000) });
60
+ return { text, callback_data: `dsh:a:${key}` };
61
+ }
62
+ async panel(scopeId, text, keyboard, orchestrator, messageId) {
63
+ if (messageId)
64
+ await orchestrator.editMessage(scopeId, messageId, text, keyboard);
65
+ else
66
+ messageId = await orchestrator.sendMessage(scopeId, text, keyboard);
67
+ orchestrator.scheduleStalePanelDeletion(scopeId, messageId);
68
+ }
69
+ async setup(scopeId, locale, orchestrator, messageId) {
70
+ const settings = this.store.getChatSettings(scopeId);
71
+ const binding = this.store.getBinding(scopeId);
72
+ const access = settings?.accessPreset ?? this.defaultAccess();
73
+ const mode = settings?.activeTurnMessageMode ?? 'queue';
74
+ const rows = [
75
+ [{ text: this.copy(locale, '🎯 模型', '🎯 Model'), callback_data: 'dsh:models' }, { text: this.copy(locale, '🧠 推理档位', '🧠 Reasoning'), callback_data: 'dsh:efforts' }],
76
+ ['read-only', 'default', 'full-access'].map(value => this.button(scopeId, 'access', value, `${access === value ? '✓ ' : ''}${this.copy(locale, value === 'read-only' ? '只读' : value === 'default' ? '工作区写入' : '完全访问', value)}`)),
77
+ [{ text: this.copy(locale, '📁 会话', '📁 Sessions'), callback_data: 'dsh:threads' }, { text: this.copy(locale, '🧩 插件配置', '🧩 Plugins'), callback_data: 'dsh:plugins' }],
78
+ [{ text: mode === 'queue' ? this.copy(locale, '⏳ 排队', '⏳ Queue') : this.copy(locale, '⚡ 中断接管', '⚡ Interrupt & steer'), callback_data: 'engine:setup:active_mode' }],
79
+ [{ text: this.copy(locale, '✨ 新建会话', '✨ New session'), callback_data: 'engine:setup:new' }, { text: this.copy(locale, '🔌 切换后端', '🔌 Backend'), callback_data: 'engine:setup:backend' }],
80
+ [{ text: this.copy(locale, '🔄 刷新', '🔄 Refresh'), callback_data: 'engine:setup:main' }],
81
+ ];
82
+ await this.panel(scopeId, `⚙️ **DeepSeek Harness**\n\n${this.copy(locale, '模型', 'Model')}: \`${settings?.model ?? 'default'}\`\n${this.copy(locale, '推理档位', 'Reasoning')}: \`${settings?.reasoningEffort ?? 'default'}\`\n${this.copy(locale, '权限', 'Access')}: \`${access}\`\n${this.copy(locale, '会话', 'Session')}: \`${binding?.threadId || 'new'}\`\n${this.copy(locale, '工作目录', 'Directory')}: \`${binding?.cwd || this.config.defaultCwd}\``, rows, orchestrator, messageId);
83
+ }
84
+ async models(scopeId, locale, orchestrator, messageId) {
85
+ const models = await this.adapter.listModels(scopeId);
86
+ const current = this.store.getChatSettings(scopeId)?.model;
87
+ const buttons = [this.button(scopeId, 'model', '', this.copy(locale, '默认模型', 'Default model')), ...models.map(model => this.button(scopeId, 'model', model.id, `${model.id === current ? '✓ ' : ''}${model.name}`))];
88
+ const rows = [];
89
+ for (let index = 0; index < buttons.length; index += 2)
90
+ rows.push(buttons.slice(index, index + 2));
91
+ rows.push([{ text: this.copy(locale, '返回设置', 'Back'), callback_data: 'engine:setup:main' }]);
92
+ await this.panel(scopeId, this.copy(locale, '🎯 **DSH 模型**', '🎯 **DSH models**'), rows, orchestrator, messageId);
93
+ }
94
+ async efforts(scopeId, locale, orchestrator, messageId) {
95
+ const choices = await this.adapter.listEfforts(scopeId);
96
+ const rows = [[this.button(scopeId, 'effort', '', this.copy(locale, '默认档位', 'Default effort'))]];
97
+ for (const choice of choices)
98
+ rows.push([this.button(scopeId, 'effort', choice.value, choice.name)]);
99
+ rows.push([{ text: this.copy(locale, '返回设置', 'Back'), callback_data: 'engine:setup:main' }]);
100
+ await this.panel(scopeId, choices.length ? this.copy(locale, '🧠 **当前模型的推理档位**', '🧠 **Reasoning for this model**') : this.copy(locale, '当前模型未提供可切换的推理档位。', 'This model exposes no reasoning selector.'), rows, orchestrator, messageId);
101
+ }
102
+ async selectModel(scopeId, value, orchestrator) {
103
+ await this.adapter.selectModel(scopeId, value || null);
104
+ this.store.setChatModel(scopeId, value || null);
105
+ this.store.setChatEffort(scopeId, null);
106
+ this.store.setChatServiceTier(scopeId, null);
107
+ orchestrator.syncCurrentBackendSettings(scopeId);
108
+ }
109
+ async selectEffort(scopeId, value, orchestrator) {
110
+ await this.adapter.selectEffort(scopeId, value || null);
111
+ this.store.setChatEngineEffort(scopeId, value || null);
112
+ orchestrator.syncCurrentBackendSettings(scopeId);
113
+ }
114
+ async selectAccess(scopeId, value, orchestrator) {
115
+ if (value !== 'read-only' && value !== 'default' && value !== 'full-access')
116
+ throw new Error('Use read-only, default, or full-access');
117
+ if (orchestrator.hasActiveTurn(scopeId))
118
+ throw new Error('Interrupt the active turn before changing DSH permissions');
119
+ await this.adapter.setAccess(scopeId, value);
120
+ this.store.setChatAccessPreset(scopeId, value);
121
+ orchestrator.syncCurrentBackendSettings(scopeId);
122
+ }
123
+ async threads(scopeId, locale, orchestrator, messageId, cursor) {
124
+ const page = await this.adapter.listSessions(scopeId, cursor);
125
+ const rows = page.sessions.map(session => [this.button(scopeId, 'open', session.sessionId, (session.title || session.sessionId).slice(0, 60), session.cwd)]);
126
+ if (page.nextCursor)
127
+ rows.push([this.button(scopeId, 'page', page.nextCursor, this.copy(locale, '下一页', 'Next page'))]);
128
+ rows.push([{ text: this.copy(locale, '返回设置', 'Back'), callback_data: 'engine:setup:main' }]);
129
+ await this.panel(scopeId, this.copy(locale, '📁 **DSH 持久会话**', '📁 **Persistent DSH sessions**'), rows, orchestrator, messageId);
130
+ }
131
+ async open(scopeId, id, cwd, orchestrator) {
132
+ if (orchestrator.hasActiveTurn(scopeId))
133
+ throw new Error('Interrupt the active turn before opening another DSH session');
134
+ if (!cwd) {
135
+ let cursor;
136
+ do {
137
+ const page = await this.adapter.listSessions(scopeId, cursor);
138
+ cwd = page.sessions.find(session => session.sessionId === id)?.cwd;
139
+ cursor = page.nextCursor ?? undefined;
140
+ } while (!cwd && cursor);
141
+ }
142
+ if (!cwd)
143
+ throw new Error('DSH session not found');
144
+ const previous = this.store.getBinding(scopeId);
145
+ this.store.setBinding(scopeId, id, cwd);
146
+ try {
147
+ await this.adapter.sessionForScope(scopeId);
148
+ }
149
+ catch (error) {
150
+ if (previous)
151
+ this.store.setBinding(scopeId, previous.threadId, previous.cwd);
152
+ else
153
+ this.store.clearBinding(scopeId);
154
+ throw error;
155
+ }
156
+ orchestrator.syncCurrentBackendSettings(scopeId);
157
+ }
158
+ async plugins(scopeId, locale, orchestrator) {
159
+ const dsh = this.config.dsh;
160
+ const home = dsh.home || process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
161
+ let dependencies = {};
162
+ try {
163
+ const manifest = JSON.parse(await fs.readFile(path.join(home, 'profiles', dsh.profile, 'package.json'), 'utf8'));
164
+ if (typeof manifest === 'object' && manifest !== null && 'dependencies' in manifest && typeof manifest.dependencies === 'object' && manifest.dependencies !== null)
165
+ dependencies = manifest.dependencies;
166
+ }
167
+ catch (error) {
168
+ this.logger.debug('dsh.plugin_manifest_unavailable', { error: String(error) });
169
+ }
170
+ await orchestrator.sendMessage(scopeId, `🧩 **DSH ${this.copy(locale, '插件配置', 'plugin configuration')}**\n\nProfile: \`${dsh.profile}\`\n${Object.entries(dependencies).map(([name, version]) => `• ${name}: ${String(version)}`).join('\n') || this.copy(locale, 'Profile 尚未初始化或没有显式依赖。', 'Profile is not initialized or has no explicit dependencies.')}\n• foxclaw-permissions\n\n${this.copy(locale, '已配置补丁', 'Configured patches')}:\n${dsh.patches.map(patch => `• ${patch}`).join('\n') || '—'}\n\n${this.copy(locale, '安装/移除插件请在终端执行,随后重启 foxclaw', 'Install/remove plugins in a terminal, then restart foxclaw')}:\n\`dsh plugin --profile ${dsh.profile} add <package>\`\n\`dsh plugin --profile ${dsh.profile} remove <package>\``);
171
+ }
172
+ async command(scopeId, command, args, locale, orchestrator) {
173
+ try {
174
+ switch (command) {
175
+ case 'setup':
176
+ await this.setup(scopeId, locale, orchestrator);
177
+ return true;
178
+ case 'models':
179
+ await this.models(scopeId, locale, orchestrator);
180
+ return true;
181
+ case 'model':
182
+ if (args) {
183
+ await this.selectModel(scopeId, args === 'default' ? '' : args, orchestrator);
184
+ await this.setup(scopeId, locale, orchestrator);
185
+ }
186
+ else
187
+ await this.models(scopeId, locale, orchestrator);
188
+ return true;
189
+ case 'effort':
190
+ if (args) {
191
+ await this.selectEffort(scopeId, args === 'default' ? '' : args, orchestrator);
192
+ await this.setup(scopeId, locale, orchestrator);
193
+ }
194
+ else
195
+ await this.efforts(scopeId, locale, orchestrator);
196
+ return true;
197
+ case 'access':
198
+ case 'permissions':
199
+ if (args)
200
+ await this.selectAccess(scopeId, args, orchestrator);
201
+ await this.setup(scopeId, locale, orchestrator);
202
+ return true;
203
+ case 'threads':
204
+ await this.threads(scopeId, locale, orchestrator);
205
+ return true;
206
+ case 'open':
207
+ if (args) {
208
+ await this.open(scopeId, args, undefined, orchestrator);
209
+ await this.setup(scopeId, locale, orchestrator);
210
+ }
211
+ else
212
+ await this.threads(scopeId, locale, orchestrator);
213
+ return true;
214
+ case 'plugins':
215
+ await this.plugins(scopeId, locale, orchestrator);
216
+ return true;
217
+ case 'where':
218
+ case 'config':
219
+ await this.setup(scopeId, locale, orchestrator);
220
+ return true;
221
+ case 'update':
222
+ await orchestrator.sendMessage(scopeId, this.copy(locale, '请在终端执行 `foxclaw update` 升级此独立 DSH Bot。', 'Run `foxclaw update` in a terminal to upgrade this dedicated DSH bot.'));
223
+ return true;
224
+ case 'help':
225
+ case 'start':
226
+ await orchestrator.sendMessage(scopeId, this.copy(locale, '**DSH 命令**\n/setup · 设置\n/models · 模型\n/model <provider/model|default>\n/effort [档位|default]\n/permissions [read-only|default|full-access]\n/threads · 持久会话\n/open <会话 ID>\n/new [目录] · 新建\n/interrupt · 中断\n/active <queue|steer>\n/plugins · 插件配置\n/backend · 切换后端\n/status · 状态', '**DSH commands**\n/setup · Settings\n/models · Models\n/model <provider/model|default>\n/effort [level|default]\n/permissions [read-only|default|full-access]\n/threads · Persistent sessions\n/open <session ID>\n/new [directory]\n/interrupt\n/active <queue|steer>\n/plugins · Plugin configuration\n/backend\n/status'));
227
+ return true;
228
+ case 'backend':
229
+ case 'backends':
230
+ case 'engine':
231
+ case 'engines':
232
+ case 'status':
233
+ case 'active':
234
+ case 'queue':
235
+ case 'steer':
236
+ case 'interrupt':
237
+ case 'stop':
238
+ case 'abort':
239
+ case 'new':
240
+ case 'clear': return false;
241
+ default:
242
+ await orchestrator.sendMessage(scopeId, this.copy(locale, `DSH 的 ACP 接口未提供 /${command}。发送 /help 查看可用操作。`, `DSH ACP does not expose /${command}. Send /help for available commands.`));
243
+ return true;
244
+ }
245
+ }
246
+ catch (error) {
247
+ await orchestrator.sendMessage(scopeId, `⚠️ ${error instanceof Error ? error.message : String(error)}`);
248
+ return true;
249
+ }
250
+ }
251
+ async callback(scopeId, data, locale, orchestrator, event) {
252
+ const answer = (text = '') => this.messaging.answerCallback(event?.callbackQueryId ?? '', text);
253
+ if (!data.startsWith('dsh:')) {
254
+ if (orchestrator.getBackendDescriptorForScope(scopeId).engineType === 'dsh' && data.startsWith('engine:') && !['engine:setup:main', 'engine:setup:models', 'engine:setup:active_mode', 'engine:setup:new', 'engine:setup:backend'].includes(data) && !data.startsWith('engine:backend:')) {
255
+ await answer(this.copy(locale, '请重新打开 DSH 设置', 'Open DSH settings again'));
256
+ return true;
257
+ }
258
+ return false;
259
+ }
260
+ if (data.startsWith('dsh:p:')) {
261
+ const [, , key, index] = data.split(':');
262
+ const pending = this.approvals.get(key);
263
+ const option = pending?.options[Number(index)];
264
+ if (!pending || pending.scopeId !== scopeId || !option) {
265
+ await answer(this.copy(locale, '审批已失效', 'Approval expired'));
266
+ return true;
267
+ }
268
+ pending.resolve({ outcome: { outcome: 'selected', optionId: option.optionId } });
269
+ await answer(this.copy(locale, '已提交', 'Submitted'));
270
+ return true;
271
+ }
272
+ if (orchestrator.getBackendDescriptorForScope(scopeId).engineType !== 'dsh') {
273
+ await answer(this.copy(locale, '请先切换到 DSH', 'Switch to DSH first'));
274
+ return true;
275
+ }
276
+ await answer();
277
+ try {
278
+ if (data === 'dsh:models')
279
+ await this.models(scopeId, locale, orchestrator, event?.messageId);
280
+ else if (data === 'dsh:efforts')
281
+ await this.efforts(scopeId, locale, orchestrator, event?.messageId);
282
+ else if (data === 'dsh:threads')
283
+ await this.threads(scopeId, locale, orchestrator, event?.messageId);
284
+ else if (data === 'dsh:plugins')
285
+ await this.plugins(scopeId, locale, orchestrator);
286
+ else if (data.startsWith('dsh:a:')) {
287
+ const action = this.actions.get(data.slice(6));
288
+ if (!action || action.scopeId !== scopeId || action.expires < Date.now())
289
+ throw new Error(this.copy(locale, '面板已失效,请重新打开 /setup', 'Panel expired; open /setup again'));
290
+ if (action.kind === 'model')
291
+ await this.selectModel(scopeId, action.value, orchestrator);
292
+ else if (action.kind === 'effort')
293
+ await this.selectEffort(scopeId, action.value, orchestrator);
294
+ else if (action.kind === 'access')
295
+ await this.selectAccess(scopeId, action.value, orchestrator);
296
+ else if (action.kind === 'open')
297
+ await this.open(scopeId, action.value, action.cwd, orchestrator);
298
+ else if (action.kind === 'page') {
299
+ await this.threads(scopeId, locale, orchestrator, event?.messageId, action.value);
300
+ return true;
301
+ }
302
+ await this.setup(scopeId, locale, orchestrator, event?.messageId);
303
+ }
304
+ }
305
+ catch (error) {
306
+ await orchestrator.sendMessage(scopeId, `⚠️ ${error instanceof Error ? error.message : String(error)}`);
307
+ }
308
+ return true;
309
+ }
310
+ async requestPermission(scopeId, request, signal) {
311
+ if (signal.aborted)
312
+ return { outcome: { outcome: 'cancelled' } };
313
+ const key = randomBytes(6).toString('hex');
314
+ const locale = this.store.getChatSettings(scopeId)?.locale ?? 'zh';
315
+ let settle;
316
+ const result = new Promise(resolve => { settle = resolve; });
317
+ const pending = { scopeId, resolve: settle, options: request.options, messageId: null };
318
+ this.approvals.set(key, pending);
319
+ const abort = () => settle({ outcome: { outcome: 'cancelled' } });
320
+ signal.addEventListener('abort', abort, { once: true });
321
+ try {
322
+ pending.messageId = await this.messaging.sendRichMarkdown(scopeId, `${this.copy(locale, '🔐 **DSH 请求审批**', '🔐 **DSH permission request**')}\n\`${request.toolCall.title || request.toolCall.toolCallId}\``, [request.options.map((option, index) => ({ text: option.kind === 'allow_once' ? this.copy(locale, '允许本次', 'Allow once') : option.kind === 'reject_once' ? this.copy(locale, '拒绝', 'Reject') : option.name, callback_data: `dsh:p:${key}:${index}` }))]);
323
+ return await result;
324
+ }
325
+ catch (error) {
326
+ this.logger.warn('dsh.approval_delivery_failed', { error: String(error) });
327
+ return { outcome: { outcome: 'cancelled' } };
328
+ }
329
+ finally {
330
+ this.approvals.delete(key);
331
+ signal.removeEventListener('abort', abort);
332
+ if (pending.messageId)
333
+ await this.messaging.editRichMarkdown(scopeId, pending.messageId, this.copy(locale, '🔐 DSH 审批已结束。', '🔐 DSH permission request resolved.'), []).catch(() => { });
334
+ }
335
+ }
336
+ async stop() {
337
+ for (const pending of this.approvals.values())
338
+ pending.resolve({ outcome: { outcome: 'cancelled' } });
339
+ this.actions.clear();
340
+ await this.adapter.stop();
341
+ }
342
+ }
package/dist/i18n.d.ts CHANGED
@@ -1499,4 +1499,8 @@ export declare function getAntigravityTelegramCommands(locale: AppLocale): Array
1499
1499
  command: string;
1500
1500
  description: string;
1501
1501
  }>;
1502
+ export declare function getDshTelegramCommands(locale: AppLocale): Array<{
1503
+ command: string;
1504
+ description: string;
1505
+ }>;
1502
1506
  export {};
package/dist/i18n.js CHANGED
@@ -1627,3 +1627,15 @@ export function getAntigravityTelegramCommands(locale) {
1627
1627
  { command: 'interrupt', description: 'Stop active turn' },
1628
1628
  ];
1629
1629
  }
1630
+ export function getDshTelegramCommands(locale) {
1631
+ const descriptions = locale === 'zh' ? {
1632
+ help: 'DSH 命令', setup: 'DSH 设置', backend: '切换后端', new: '新建会话', threads: '持久会话列表', open: '打开会话',
1633
+ models: '模型列表', model: '选择模型', effort: '推理档位', permissions: '权限设置', plugins: '插件配置',
1634
+ active: '插话或排队', queue: '排队下一轮', steer: '中断并接管', status: '运行状态', interrupt: '中断任务',
1635
+ } : {
1636
+ help: 'DSH commands', setup: 'DSH settings', backend: 'Switch backend', new: 'New session', threads: 'Persistent sessions', open: 'Open session',
1637
+ models: 'Models', model: 'Select model', effort: 'Reasoning effort', permissions: 'Permissions', plugins: 'Plugin configuration',
1638
+ active: 'Steer or queue', queue: 'Queue next turn', steer: 'Interrupt and steer', status: 'Runtime status', interrupt: 'Stop task',
1639
+ };
1640
+ return Object.entries(descriptions).map(([command, description]) => ({ command, description }));
1641
+ }