@foxden-app/foxclaw 0.11.0 → 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 +34 -0
- package/README.md +4 -3
- package/README_EN.md +4 -3
- package/dist/antigravity/controller.d.ts +7 -0
- package/dist/antigravity/controller.js +123 -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/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: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
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -349,7 +349,7 @@ async function runSendVoiceCli() {
|
|
|
349
349
|
throw new Error('Telegram voice files must be 50MB or smaller.');
|
|
350
350
|
}
|
|
351
351
|
const inferredBotId = parsed.botId ?? inferTelegramBotId(process.env.CODEX_HOME) ?? inferTelegramBotId(config.codexHome);
|
|
352
|
-
const allTokens = [...config.codexBotTokens, ...config.antigravityBotTokens];
|
|
352
|
+
const allTokens = [...config.codexBotTokens, ...config.antigravityBotTokens, ...(config.dshBotToken ? [config.dshBotToken] : [])];
|
|
353
353
|
const { botId, botToken } = resolveTelegramVoiceTarget(allTokens, inferredBotId);
|
|
354
354
|
const { BridgeStore } = await import('./store/database.js');
|
|
355
355
|
const store = new BridgeStore(config.storePath);
|
|
@@ -395,7 +395,7 @@ async function runSendMediaCli() {
|
|
|
395
395
|
}
|
|
396
396
|
const plan = planTelegramOutboundMedia(filePath);
|
|
397
397
|
const inferredBotId = parsed.botId ?? inferTelegramBotId(process.env.CODEX_HOME) ?? inferTelegramBotId(config.codexHome);
|
|
398
|
-
const allTokens = [...config.codexBotTokens, ...config.antigravityBotTokens];
|
|
398
|
+
const allTokens = [...config.codexBotTokens, ...config.antigravityBotTokens, ...(config.dshBotToken ? [config.dshBotToken] : [])];
|
|
399
399
|
const { botId, botToken } = resolveTelegramVoiceTarget(allTokens, inferredBotId);
|
|
400
400
|
const { BridgeStore } = await import('./store/database.js');
|
|
401
401
|
const store = new BridgeStore(config.storePath);
|
|
@@ -605,7 +605,7 @@ function truncateStatusLine(value, maxLength) {
|
|
|
605
605
|
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
606
606
|
}
|
|
607
607
|
async function runServeCli() {
|
|
608
|
-
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter }, { AuthCandidateMirror }, { CrossNodeAuthSync }, { OpencodeTelegramRuntime }, { UnifiedBridgeCore }, { AntigravityAppClient }, { AntigravityAuthManager, linkAntigravityAuthTokens },] = await Promise.all([
|
|
608
|
+
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter }, { AuthCandidateMirror }, { CrossNodeAuthSync }, { OpencodeTelegramRuntime }, { DshTelegramRuntime }, { UnifiedBridgeCore }, { AntigravityAppClient }, { AntigravityAuthManager, linkAntigravityAuthTokens },] = await Promise.all([
|
|
609
609
|
import('./channels/bridge_messaging_router.js'),
|
|
610
610
|
import('./channels/telegram/telegram_messaging_port.js'),
|
|
611
611
|
import('./channels/weixin/weixin_channel_adapter.js'),
|
|
@@ -621,6 +621,7 @@ async function runServeCli() {
|
|
|
621
621
|
import('./auth/mirror.js'),
|
|
622
622
|
import('./auth/cross_node_sync.js'),
|
|
623
623
|
import('./opencode/runtime.js'),
|
|
624
|
+
import('./dsh/runtime.js'),
|
|
624
625
|
import('./antigravity/controller.js'),
|
|
625
626
|
import('./antigravity/client.js'),
|
|
626
627
|
import('./antigravity/auth.js'),
|
|
@@ -639,11 +640,47 @@ async function runServeCli() {
|
|
|
639
640
|
let activeAuthMirror = null;
|
|
640
641
|
let activeAuthSync = null;
|
|
641
642
|
let activeOpencodeRuntime = null;
|
|
643
|
+
let activeDshRuntime = null;
|
|
642
644
|
let sharedCodexApp = null;
|
|
643
645
|
let sharedAntigravityApp = null;
|
|
644
646
|
let sharedAntigravityAuth = null;
|
|
645
647
|
try {
|
|
646
648
|
store = new BridgeStore(config.storePath);
|
|
649
|
+
if (config.dshBotToken) {
|
|
650
|
+
activeDshRuntime = new DshTelegramRuntime(config, store, logger);
|
|
651
|
+
await activeDshRuntime.start();
|
|
652
|
+
logger.info('dsh.bridge.started', activeDshRuntime.getRuntimeStatus());
|
|
653
|
+
}
|
|
654
|
+
if (config.opencodeBotToken) {
|
|
655
|
+
activeOpencodeRuntime = new OpencodeTelegramRuntime(config, store, logger);
|
|
656
|
+
await activeOpencodeRuntime.start();
|
|
657
|
+
logger.info('opencode.bridge.started', activeOpencodeRuntime.getRuntimeStatus());
|
|
658
|
+
}
|
|
659
|
+
if (config.dshBotToken && config.codexBotTokens.length === 0 && config.antigravityBotTokens.length === 0) {
|
|
660
|
+
const writeDshStatus = (running = true) => {
|
|
661
|
+
const status = activeDshRuntime.getRuntimeStatus();
|
|
662
|
+
writeRuntimeStatus(config.statusPath, {
|
|
663
|
+
running, connected: running && status.connected, userAgent: 'DSH ACP', botUsername: status.username,
|
|
664
|
+
currentBindings: store.countBindings(), pendingApprovals: status.pendingApprovals, pendingUserInputs: 0, queuedTurns: store.countQueuedTurnInputs(),
|
|
665
|
+
activeTurns: running ? status.activeTurns + (activeOpencodeRuntime?.getRuntimeStatus().activeTurns ?? 0) : 0, lastError: null, updatedAt: new Date().toISOString(),
|
|
666
|
+
channels: { telegram: running, weixin: false }, bots: [{ ...status, connected: running && status.connected }, ...(activeOpencodeRuntime ? [{ id: 'opencode', username: activeOpencodeRuntime.getRuntimeStatus().botUsername, connected: running && activeOpencodeRuntime.getRuntimeStatus().connected, activeTurns: activeOpencodeRuntime.getRuntimeStatus().activeTurns }] : [])],
|
|
667
|
+
});
|
|
668
|
+
};
|
|
669
|
+
writeDshStatus();
|
|
670
|
+
const timer = setInterval(writeDshStatus, 5000);
|
|
671
|
+
const shutdown = async () => {
|
|
672
|
+
clearInterval(timer);
|
|
673
|
+
await activeDshRuntime.stop();
|
|
674
|
+
await activeOpencodeRuntime?.stop();
|
|
675
|
+
writeDshStatus(false);
|
|
676
|
+
store.close();
|
|
677
|
+
processLock.release();
|
|
678
|
+
process.exit(0);
|
|
679
|
+
};
|
|
680
|
+
process.on('SIGINT', () => void shutdown());
|
|
681
|
+
process.on('SIGTERM', () => void shutdown());
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
647
684
|
const selfUpdater = createSelfUpdateRuntime({
|
|
648
685
|
entryPoint,
|
|
649
686
|
nodePath: process.execPath,
|
|
@@ -658,12 +695,7 @@ async function runServeCli() {
|
|
|
658
695
|
if (config.codexCliBin) {
|
|
659
696
|
sharedCodexApp = new CodexAppClient(config.codexCliBin, config.codexAppLaunchCmd, config.codexAppAutolaunch, config.codexAppServerStatePath, config.codexAppServerLogPath, logger, config.codexHome ? { CODEX_HOME: config.codexHome } : null, codexApiProviderOverrides);
|
|
660
697
|
}
|
|
661
|
-
|
|
662
|
-
activeOpencodeRuntime = new OpencodeTelegramRuntime(config, store, logger);
|
|
663
|
-
await activeOpencodeRuntime.start();
|
|
664
|
-
logger.info('opencode.bridge.started', activeOpencodeRuntime.getRuntimeStatus());
|
|
665
|
-
}
|
|
666
|
-
const totalBots = config.codexBotTokens.length + config.antigravityBotTokens.length;
|
|
698
|
+
const totalBots = config.codexBotTokens.length + config.antigravityBotTokens.length + (config.dshBotToken ? 1 : 0);
|
|
667
699
|
const namespacedScopes = totalBots > 1;
|
|
668
700
|
const seeds = [];
|
|
669
701
|
const canonicalAuthDir = config.codexAuthDir ?? config.codexHome ?? path.join(os.homedir(), '.codex');
|
|
@@ -809,27 +841,27 @@ async function runServeCli() {
|
|
|
809
841
|
...(first?.codexAppServer ? { codexAppServer: first.codexAppServer } : {}),
|
|
810
842
|
botUsername: first?.botUsername ?? null,
|
|
811
843
|
currentBindings: store.countBindings(),
|
|
812
|
-
pendingApprovals: store.countPendingApprovals(),
|
|
844
|
+
pendingApprovals: store.countPendingApprovals() + statuses.reduce((sum, status) => sum + status.dshPendingApprovals, 0) + (activeDshRuntime?.getRuntimeStatus().pendingApprovals ?? 0),
|
|
813
845
|
pendingUserInputs: store.countPendingUserInputs(),
|
|
814
846
|
queuedTurns: store.countQueuedTurnInputs(),
|
|
815
847
|
activeTurns: statuses.reduce((sum, status) => sum + status.activeTurns, 0)
|
|
816
|
-
+ (weixinStatus?.activeTurns ?? 0),
|
|
848
|
+
+ (weixinStatus?.activeTurns ?? 0) + (activeDshRuntime?.getRuntimeStatus().activeTurns ?? 0),
|
|
817
849
|
lastError: statuses.find((status) => status.lastError)?.lastError
|
|
818
850
|
?? weixinStatus?.lastError
|
|
819
851
|
?? null,
|
|
820
852
|
updatedAt: new Date().toISOString(),
|
|
821
853
|
channels: { telegram: running, weixin: running && config.wxEnabled },
|
|
822
|
-
bots: runtimes.map((runtime, index) => ({
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
854
|
+
bots: [...runtimes.map((runtime, index) => ({
|
|
855
|
+
id: runtime.id,
|
|
856
|
+
username: statuses[index]?.botUsername ?? runtime.bot.username,
|
|
857
|
+
connected: running && Boolean(statuses[index]?.connected),
|
|
858
|
+
activeTurns: running ? (statuses[index]?.activeTurns ?? 0) : 0,
|
|
859
|
+
runtimeKind: runtime.sharedDefaultRuntime ? 'default' : 'isolated',
|
|
860
|
+
defaultBackend: runtime.defaultBackend,
|
|
861
|
+
codexHome: statuses[index]?.codexHome ?? runtime.home,
|
|
862
|
+
antigravityHome: runtime.antigravityHome,
|
|
863
|
+
...(statuses[index]?.codexAppServer ? { codexAppServer: statuses[index].codexAppServer } : {}),
|
|
864
|
+
})), ...(activeDshRuntime ? [activeDshRuntime.getRuntimeStatus()] : [])],
|
|
833
865
|
...(weixinStatus ? {
|
|
834
866
|
weixinRuntime: {
|
|
835
867
|
connected: running && weixinStatus.connected,
|
|
@@ -844,6 +876,7 @@ async function runServeCli() {
|
|
|
844
876
|
});
|
|
845
877
|
};
|
|
846
878
|
const authSyncLocalIdle = () => runtimes.every((runtime) => runtime.core.isIdleForServiceUpdate())
|
|
879
|
+
&& (activeDshRuntime?.getRuntimeStatus().activeTurns ?? 0) === 0
|
|
847
880
|
&& (!activeWeixinCore || activeWeixinCore.isIdleForServiceUpdate())
|
|
848
881
|
&& mirror.isIdle();
|
|
849
882
|
const clusterUpdateScheduler = createClusterUpdateScheduler({
|
|
@@ -896,19 +929,19 @@ async function runServeCli() {
|
|
|
896
929
|
statusUpdated: () => writeAggregateStatus(),
|
|
897
930
|
getServiceStatus: async () => ({
|
|
898
931
|
currentVersion: readPackageVersion(),
|
|
899
|
-
bots: await Promise.all(runtimes.map(async (runtime) => {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
932
|
+
bots: [...await Promise.all(runtimes.map(async (runtime) => {
|
|
933
|
+
const status = runtime.core.getRuntimeStatus();
|
|
934
|
+
return {
|
|
935
|
+
id: runtime.id,
|
|
936
|
+
username: status.botUsername ?? runtime.bot.username,
|
|
937
|
+
connected: status.connected,
|
|
938
|
+
activeTurns: status.activeTurns,
|
|
939
|
+
runtimeKind: runtime.sharedDefaultRuntime ? 'default' : 'isolated',
|
|
940
|
+
currentAuth: await runtime.core.getCurrentAuthLabel().catch(() => null),
|
|
941
|
+
codexHome: status.codexHome ?? runtime.home,
|
|
942
|
+
...(status.codexAppServer ? { codexAppServer: status.codexAppServer } : {}),
|
|
943
|
+
};
|
|
944
|
+
})), ...(activeDshRuntime ? [activeDshRuntime.getRuntimeStatus()] : [])],
|
|
912
945
|
...(activeWeixinCore ? {
|
|
913
946
|
weixinRuntime: {
|
|
914
947
|
connected: activeWeixinCore.getRuntimeStatus().connected,
|
|
@@ -1027,6 +1060,7 @@ async function runServeCli() {
|
|
|
1027
1060
|
await activeWeixinCore?.stop();
|
|
1028
1061
|
await Promise.all(runtimes.map((runtime) => runtime.telegram.stop()));
|
|
1029
1062
|
await activeOpencodeRuntime?.stop();
|
|
1063
|
+
await activeDshRuntime?.stop();
|
|
1030
1064
|
writeAggregateStatus(false);
|
|
1031
1065
|
const allManaged = [...managedApps, ...(sharedCodexApp ? [sharedCodexApp] : [])];
|
|
1032
1066
|
await Promise.all(allManaged.map((app) => app.stop({ terminateServer: true }).catch((error) => {
|
|
@@ -1047,6 +1081,7 @@ async function runServeCli() {
|
|
|
1047
1081
|
await activeWeixinCore?.stop().catch(() => { });
|
|
1048
1082
|
await Promise.allSettled(activeTelegramAdapters.map((adapter) => adapter.stop()));
|
|
1049
1083
|
await activeOpencodeRuntime?.stop().catch(() => { });
|
|
1084
|
+
await activeDshRuntime?.stop().catch(() => { });
|
|
1050
1085
|
await Promise.allSettled(managedApps.map((app) => app.stop({ terminateServer: true })));
|
|
1051
1086
|
store?.close();
|
|
1052
1087
|
processLock.release();
|
|
@@ -1537,10 +1572,23 @@ async function configureEnvInteractively(envPath, existed) {
|
|
|
1537
1572
|
else {
|
|
1538
1573
|
skipped.push('TG_ALLOWED_USER_ID');
|
|
1539
1574
|
}
|
|
1575
|
+
const dshToken = sanitizeEnvInput(await rl.question('DeepSeek Harness Telegram bot token (DSH_BOT_TOKEN) [optional]: '));
|
|
1576
|
+
if (dshToken) {
|
|
1577
|
+
updates.DSH_BOT_TOKEN = dshToken;
|
|
1578
|
+
if (!codexTokens && !existed) {
|
|
1579
|
+
updates.CODEX_BOT_TOKENS = '';
|
|
1580
|
+
skipped.splice(skipped.indexOf('CODEX_BOT_TOKENS'), 1);
|
|
1581
|
+
}
|
|
1582
|
+
const dshSource = sanitizeEnvInput(await rl.question('DeepSeek Harness source directory (DSH_SOURCE_DIR) [Enter to use installed dsh]: '));
|
|
1583
|
+
if (dshSource)
|
|
1584
|
+
updates.DSH_SOURCE_DIR = normalizeUserPath(dshSource);
|
|
1585
|
+
if (!/^\d+:[A-Za-z0-9_-]+$/.test(dshToken))
|
|
1586
|
+
warnings.push('DSH_BOT_TOKEN does not look like a standard Telegram bot token.');
|
|
1587
|
+
}
|
|
1540
1588
|
const cwdDefault = defaultInitCwd();
|
|
1541
1589
|
const cwdPrompt = cwdDefault
|
|
1542
|
-
? `Default
|
|
1543
|
-
: 'Default
|
|
1590
|
+
? `Default workspace (DEFAULT_CWD) [${cwdDefault}]: `
|
|
1591
|
+
: 'Default workspace (DEFAULT_CWD): ';
|
|
1544
1592
|
const cwdAnswer = sanitizeEnvInput(await rl.question(cwdPrompt));
|
|
1545
1593
|
const cwd = cwdAnswer ? normalizeUserPath(cwdAnswer) : cwdDefault;
|
|
1546
1594
|
if (cwd) {
|
|
@@ -1741,12 +1789,24 @@ function runDoctorChecks() {
|
|
|
1741
1789
|
...(process.env.ANTIGRAVITY_BOT_TOKENS ?? '').split(','),
|
|
1742
1790
|
process.env.ANTIGRAVITY_BOT_TOKEN ?? '',
|
|
1743
1791
|
].map((value) => value.trim()).filter(Boolean);
|
|
1792
|
+
const dshToken = process.env.DSH_BOT_TOKEN?.trim();
|
|
1793
|
+
const dshFlag = process.env.DSH_ENABLED;
|
|
1794
|
+
const dshEnabled = dshFlag ? dshFlag !== 'false' && dshFlag !== '0' : Boolean(dshToken || process.env.DSH_SOURCE_DIR?.trim() || process.env.DSH_CLI_BIN?.trim());
|
|
1744
1795
|
const checks = [
|
|
1745
1796
|
['node >= 24', Number(process.versions.node.split('.')[0]) >= 24],
|
|
1746
|
-
['
|
|
1747
|
-
['telegram bot token(s) configured', codexTokens.length > 0 || antigravityTokens.length > 0],
|
|
1797
|
+
['telegram bot token(s) configured', codexTokens.length > 0 || antigravityTokens.length > 0 || Boolean(dshToken)],
|
|
1748
1798
|
['telegram allowed user configured', Boolean(process.env.TG_ALLOWED_USER_ID)],
|
|
1749
1799
|
];
|
|
1800
|
+
if (codexTokens.length > 0 || process.env.WX_ENABLED === 'true' || process.env.WX_ENABLED === '1')
|
|
1801
|
+
checks.push(['codex cli available', hasConfiguredCommand(configuredCodexBin, 'codex')]);
|
|
1802
|
+
if (dshEnabled) {
|
|
1803
|
+
const source = process.env.DSH_SOURCE_DIR?.trim();
|
|
1804
|
+
checks.push(['DSH CLI available', source ? fs.existsSync(path.join(path.resolve(source), 'apps/cli/src/bin.ts')) : hasConfiguredCommand(process.env.DSH_CLI_BIN, 'dsh')]);
|
|
1805
|
+
if (dshToken)
|
|
1806
|
+
checks.push(['DSH uses an independent Telegram bot', ![...codexTokens, ...antigravityTokens, process.env.OPENCODE_BOT_TOKEN?.trim()].includes(dshToken)]);
|
|
1807
|
+
}
|
|
1808
|
+
if (dshToken)
|
|
1809
|
+
checks.push(['DSH backend enabled for its Telegram bot', dshEnabled]);
|
|
1750
1810
|
if (process.env.OPENCODE_BOT_TOKEN?.trim()) {
|
|
1751
1811
|
const configuredOpencodeBin = process.env.OPENCODE_CLI_BIN;
|
|
1752
1812
|
checks.push(['opencode cli available', hasConfiguredCommand(configuredOpencodeBin, 'opencode')]);
|
package/dist/store/database.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ export declare class BridgeStore {
|
|
|
74
74
|
setChatSettings(chatId: string, model: string | null, reasoningEffort: ReasoningEffortValue | null, locale?: AppLocale | null): void;
|
|
75
75
|
setChatModel(chatId: string, model: string | null): void;
|
|
76
76
|
setChatEffort(chatId: string, effort: ReasoningEffortValue | null): void;
|
|
77
|
+
/** Persist an adapter-advertised reasoning id without imposing Codex's vocabulary. */
|
|
78
|
+
setChatEngineEffort(chatId: string, effort: string | null): void;
|
|
77
79
|
setChatLocale(chatId: string, locale: AppLocale): void;
|
|
78
80
|
setChatAccessPreset(chatId: string, accessPreset: AccessPresetValue | null): void;
|
|
79
81
|
setChatCollaborationMode(chatId: string, collaborationMode: CollaborationModeValue | null): void;
|
|
@@ -140,7 +142,7 @@ export declare class BridgeStore {
|
|
|
140
142
|
countQueuedTurnInputs(scopeId?: string): number;
|
|
141
143
|
updateQueuedTurnInputStatus(queueId: string, status: QueuedTurnInputStatus, error?: string | null): void;
|
|
142
144
|
cancelQueuedTurnInputs(scopeId: string): number;
|
|
143
|
-
requeueInterruptedQueuedTurnInputs(): number;
|
|
145
|
+
requeueInterruptedQueuedTurnInputs(scopeId?: string): number;
|
|
144
146
|
savePendingAttachmentBatch(record: PendingAttachmentBatchRecord): void;
|
|
145
147
|
getPendingAttachmentBatch(batchId: string): PendingAttachmentBatchRecord | null;
|
|
146
148
|
findPendingAttachmentBatchByMediaGroup(scopeId: string, mediaGroupId: string): PendingAttachmentBatchRecord | null;
|