@foxden-app/foxclaw 0.12.0 → 0.13.1
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/CHANGELOG.md +34 -0
- package/dist/antigravity/account_ui.d.ts +34 -0
- package/dist/antigravity/account_ui.js +354 -0
- package/dist/antigravity/adapter.js +1 -1
- package/dist/antigravity/auth.d.ts +4 -1
- package/dist/antigravity/auth.js +17 -5
- package/dist/antigravity/controller.d.ts +3 -108
- package/dist/antigravity/controller.js +2 -2129
- package/dist/antigravity/conversation_ui.d.ts +56 -0
- package/dist/antigravity/conversation_ui.js +509 -0
- package/dist/antigravity/ui_callbacks.d.ts +6 -0
- package/dist/antigravity/ui_callbacks.js +6 -0
- package/dist/bridge/unified_bridge.d.ts +114 -0
- package/dist/bridge/unified_bridge.js +1343 -0
- package/dist/channels/bridge_messaging_router.d.ts +8 -4
- package/dist/channels/bridge_messaging_router.js +15 -4
- package/dist/channels/telegram/telegram_messaging_port.d.ts +16 -0
- package/dist/channels/telegram/telegram_messaging_port.js +22 -2
- package/dist/channels/text_actions.d.ts +14 -0
- package/dist/channels/text_actions.js +48 -0
- package/dist/channels/weixin/weixin_channel_adapter.d.ts +3 -4
- package/dist/channels/weixin/weixin_channel_adapter.js +2 -2
- package/dist/channels/weixin/weixin_messaging_port.d.ts +16 -1
- package/dist/channels/weixin/weixin_messaging_port.js +34 -7
- package/dist/codex_app/activity.d.ts +62 -0
- package/dist/codex_app/activity.js +418 -0
- package/dist/codex_app/adapter.d.ts +2 -2
- package/dist/codex_app/adapter.js +55 -25
- package/dist/codex_app/backend_ui.d.ts +4 -0
- package/dist/codex_app/backend_ui.js +48 -0
- package/dist/codex_app/client.js +0 -12
- package/dist/controller/access.d.ts +1 -11
- package/dist/controller/access.js +1 -33
- package/dist/controller/activity.d.ts +1 -62
- package/dist/controller/activity.js +2 -418
- package/dist/controller/auth_errors.d.ts +11 -0
- package/dist/controller/auth_errors.js +124 -0
- package/dist/controller/auth_files.d.ts +13 -0
- package/dist/controller/auth_files.js +156 -0
- package/dist/controller/auth_presentation.d.ts +77 -0
- package/dist/controller/auth_presentation.js +697 -0
- package/dist/controller/auth_quota_service.d.ts +27 -0
- package/dist/controller/auth_quota_service.js +151 -0
- package/dist/controller/controller.d.ts +30 -56
- package/dist/controller/controller.js +181 -3497
- package/dist/controller/interactions.d.ts +74 -0
- package/dist/controller/interactions.js +560 -0
- package/dist/controller/local_usage_service.d.ts +19 -0
- package/dist/controller/local_usage_service.js +62 -0
- package/dist/controller/native_panel_service.d.ts +55 -0
- package/dist/controller/native_panel_service.js +214 -0
- package/dist/controller/native_panels.d.ts +61 -0
- package/dist/controller/native_panels.js +784 -0
- package/dist/controller/shared_helpers.d.ts +53 -0
- package/dist/controller/shared_helpers.js +399 -0
- package/dist/controller/state_types.d.ts +390 -0
- package/dist/controller/state_types.js +82 -0
- package/dist/controller/turn_rendering.d.ts +22 -0
- package/dist/controller/turn_rendering.js +288 -0
- package/dist/core/access.d.ts +11 -0
- package/dist/core/access.js +33 -0
- package/dist/core/attachment_files.d.ts +13 -0
- package/dist/core/attachment_files.js +180 -0
- package/dist/core/attachment_types.d.ts +22 -0
- package/dist/core/attachment_types.js +1 -0
- package/dist/core/attachments.d.ts +5 -3
- package/dist/core/attachments.js +2 -2
- package/dist/core/backend_registry.d.ts +22 -0
- package/dist/core/backend_registry.js +50 -0
- package/dist/core/backend_ui.d.ts +28 -0
- package/dist/core/backend_ui.js +1 -0
- package/dist/core/channel_events.d.ts +48 -0
- package/dist/core/channel_events.js +1 -0
- package/dist/core/channel_port.d.ts +52 -8
- package/dist/core/engine_spi.d.ts +19 -4
- package/dist/core/listener_scope.d.ts +11 -0
- package/dist/core/listener_scope.js +10 -0
- package/dist/core/message_text.d.ts +2 -0
- package/dist/core/message_text.js +28 -0
- package/dist/core/network_errors.d.ts +1 -0
- package/dist/core/network_errors.js +23 -0
- package/dist/core/orchestrator.d.ts +53 -24
- package/dist/core/orchestrator.js +855 -507
- package/dist/core/queued_engine_input.d.ts +12 -0
- package/dist/core/queued_engine_input.js +16 -0
- package/dist/core/runtime_supervisor.d.ts +19 -0
- package/dist/core/runtime_supervisor.js +45 -0
- package/dist/core/scope_operations.d.ts +7 -0
- package/dist/core/scope_operations.js +19 -0
- package/dist/core/stream_preview.js +21 -9
- package/dist/dsh/adapter.js +18 -5
- package/dist/dsh/backend.d.ts +7 -0
- package/dist/dsh/backend.js +21 -0
- package/dist/dsh/runtime.d.ts +0 -1
- package/dist/dsh/runtime.js +4 -13
- package/dist/dsh/ui.d.ts +8 -8
- package/dist/dsh/ui.js +1 -1
- package/dist/main.js +55 -43
- package/dist/opencode/adapter.js +28 -9
- package/dist/opencode/controller.js +1 -1
- package/dist/store/channel_inbox.d.ts +14 -0
- package/dist/store/channel_inbox.js +21 -0
- package/dist/store/database.d.ts +7 -3
- package/dist/store/database.js +7 -2
- package/dist/store/task_journal.d.ts +32 -0
- package/dist/store/task_journal.js +70 -0
- package/dist/telegram/api.d.ts +2 -2
- package/dist/telegram/api.js +10 -28
- package/dist/telegram/gateway.d.ts +13 -27
- package/dist/telegram/gateway.js +86 -43
- package/dist/telegram/media.d.ts +2 -34
- package/dist/telegram/media.js +1 -180
- package/dist/telegram/task_preview.d.ts +13 -0
- package/dist/telegram/task_preview.js +92 -0
- package/dist/telegram/task_result.d.ts +2 -0
- package/dist/telegram/task_result.js +12 -0
- package/dist/telegram/text.d.ts +1 -1
- package/dist/telegram/text.js +2 -27
- package/dist/types.d.ts +1 -1
- package/dist/update.d.ts +27 -2
- package/dist/update.js +81 -28
- package/docs/release.md +13 -11
- package/docs/zh/architecture-review.md +106 -0
- package/docs/zh/release.md +13 -11
- package/docs/zh/telegram-experience.md +40 -0
- package/package.json +8 -5
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StagedAttachment } from './attachment_types.js';
|
|
2
|
+
export interface QueuedEngineInput {
|
|
3
|
+
version: 1;
|
|
4
|
+
prompt: string;
|
|
5
|
+
backendId: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
stagedAttachments: StagedAttachment[];
|
|
8
|
+
}
|
|
9
|
+
export declare function decodeQueuedEngineInput(inputJson: string, fallbackPrompt: string): QueuedEngineInput | {
|
|
10
|
+
prompt: string;
|
|
11
|
+
stagedAttachments: StagedAttachment[];
|
|
12
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function decodeQueuedEngineInput(inputJson, fallbackPrompt) {
|
|
2
|
+
const input = JSON.parse(inputJson);
|
|
3
|
+
// Legacy queues contained only a Codex-shaped text input array.
|
|
4
|
+
if (Array.isArray(input)) {
|
|
5
|
+
const text = input[0]?.text;
|
|
6
|
+
return { prompt: typeof text === 'string' ? text : fallbackPrompt, stagedAttachments: [] };
|
|
7
|
+
}
|
|
8
|
+
if (typeof input !== 'object' || input === null || !('version' in input) || input.version !== 1 ||
|
|
9
|
+
!('prompt' in input) || typeof input.prompt !== 'string' ||
|
|
10
|
+
!('backendId' in input) || typeof input.backendId !== 'string' ||
|
|
11
|
+
!('cwd' in input) || typeof input.cwd !== 'string' ||
|
|
12
|
+
!('stagedAttachments' in input) || !Array.isArray(input.stagedAttachments)) {
|
|
13
|
+
throw new Error('Invalid queued engine input');
|
|
14
|
+
}
|
|
15
|
+
return input;
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ManagedRuntime {
|
|
2
|
+
start?(): void | Promise<void>;
|
|
3
|
+
stop(): void | Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export interface ShutdownFailure {
|
|
6
|
+
name: string;
|
|
7
|
+
error: unknown;
|
|
8
|
+
}
|
|
9
|
+
/** Owns resources from allocation onward, including partially started runtimes. */
|
|
10
|
+
export declare class RuntimeSupervisor {
|
|
11
|
+
private readonly entries;
|
|
12
|
+
private readonly identities;
|
|
13
|
+
private closing;
|
|
14
|
+
private shutdown;
|
|
15
|
+
register(name: string, resource: ManagedRuntime): void;
|
|
16
|
+
start(name: string): Promise<void>;
|
|
17
|
+
stop(): Promise<ShutdownFailure[]>;
|
|
18
|
+
private stopAll;
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Owns resources from allocation onward, including partially started runtimes. */
|
|
2
|
+
export class RuntimeSupervisor {
|
|
3
|
+
entries = [];
|
|
4
|
+
identities = new Set();
|
|
5
|
+
closing = false;
|
|
6
|
+
shutdown = null;
|
|
7
|
+
register(name, resource) {
|
|
8
|
+
if (this.closing)
|
|
9
|
+
throw new Error('Runtime supervisor is stopping');
|
|
10
|
+
if (this.identities.has(resource))
|
|
11
|
+
return;
|
|
12
|
+
if (this.entries.some(entry => entry.name === name))
|
|
13
|
+
throw new Error(`Runtime already registered: ${name}`);
|
|
14
|
+
this.identities.add(resource);
|
|
15
|
+
this.entries.push({ name, resource, start: null });
|
|
16
|
+
}
|
|
17
|
+
start(name) {
|
|
18
|
+
if (this.closing)
|
|
19
|
+
return Promise.reject(new Error('Runtime supervisor is stopping'));
|
|
20
|
+
const entry = this.entries.find(entry => entry.name === name);
|
|
21
|
+
if (!entry)
|
|
22
|
+
return Promise.reject(new Error(`Unknown runtime: ${name}`));
|
|
23
|
+
entry.start ??= Promise.resolve().then(() => entry.resource.start?.());
|
|
24
|
+
return entry.start;
|
|
25
|
+
}
|
|
26
|
+
stop() {
|
|
27
|
+
this.closing = true;
|
|
28
|
+
this.shutdown ??= this.stopAll();
|
|
29
|
+
return this.shutdown;
|
|
30
|
+
}
|
|
31
|
+
async stopAll() {
|
|
32
|
+
const failures = [];
|
|
33
|
+
// Consumers are registered after their dependencies and stopped before them.
|
|
34
|
+
for (const entry of [...this.entries].reverse()) {
|
|
35
|
+
await entry.start?.catch(() => { });
|
|
36
|
+
try {
|
|
37
|
+
await entry.resource.stop();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
failures.push({ name: entry.name, error });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return failures;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Serialize state changes for one conversation; unrelated scopes proceed independently. */
|
|
2
|
+
export declare class ScopeOperations {
|
|
3
|
+
private readonly tails;
|
|
4
|
+
run<T>(scopeId: string, operation: () => Promise<T>): Promise<T>;
|
|
5
|
+
isIdle(): boolean;
|
|
6
|
+
idle(): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Serialize state changes for one conversation; unrelated scopes proceed independently. */
|
|
2
|
+
export class ScopeOperations {
|
|
3
|
+
tails = new Map();
|
|
4
|
+
run(scopeId, operation) {
|
|
5
|
+
const previous = this.tails.get(scopeId) ?? Promise.resolve();
|
|
6
|
+
const next = previous.catch(() => { }).then(operation);
|
|
7
|
+
this.tails.set(scopeId, next);
|
|
8
|
+
void next.finally(() => {
|
|
9
|
+
if (this.tails.get(scopeId) === next)
|
|
10
|
+
this.tails.delete(scopeId);
|
|
11
|
+
}).catch(() => { });
|
|
12
|
+
return next;
|
|
13
|
+
}
|
|
14
|
+
isIdle() { return this.tails.size === 0; }
|
|
15
|
+
async idle() {
|
|
16
|
+
while (this.tails.size)
|
|
17
|
+
await Promise.allSettled([...this.tails.values()]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatMetricTokenCount } from '../store/token_usage.js';
|
|
2
|
-
import {
|
|
2
|
+
import { chunkMessage } from './message_text.js';
|
|
3
3
|
export function formatElapsedDuration(seconds, locale = 'zh') {
|
|
4
4
|
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
5
5
|
return '0s';
|
|
@@ -75,14 +75,26 @@ export function buildFoldedToolsSummary(options) {
|
|
|
75
75
|
: '';
|
|
76
76
|
previewLines = [...head, omittedNote, ...tail].filter(Boolean);
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
// Keep whole formatted lines within the summary budget; never slice through tags/entities.
|
|
79
|
+
const bounded = [];
|
|
80
|
+
let budget = 1500;
|
|
81
|
+
for (const line of [...previewLines].reverse()) {
|
|
82
|
+
if (line.length + 1 > budget)
|
|
83
|
+
continue;
|
|
84
|
+
bounded.unshift(line);
|
|
85
|
+
budget -= line.length + 1;
|
|
86
|
+
}
|
|
87
|
+
const omittedBySize = previewLines.length - bounded.length;
|
|
88
|
+
if (omittedBySize)
|
|
89
|
+
bounded.unshift(`<i>${locale === 'zh' ? `另有 ${omittedBySize} 项记录已收起` : `${omittedBySize} additional entries omitted`}</i>`);
|
|
90
|
+
toolDetailSection = `\n\n<i>${locale === 'zh' ? '工具调用记录:' : 'Tool calls:'}</i>\n${bounded.join('\n')}`;
|
|
79
91
|
}
|
|
80
92
|
return `<blockquote expandable>${header}${usageLine}${toolDetailSection}</blockquote>\n\n`;
|
|
81
93
|
}
|
|
82
94
|
export function combineSummaryAndResponse(foldedSummary, responseText, limit = 4000) {
|
|
83
95
|
const content = (responseText || '(无输出 / No output)').trim();
|
|
84
96
|
if (!foldedSummary) {
|
|
85
|
-
return
|
|
97
|
+
return chunkMessage(content, limit);
|
|
86
98
|
}
|
|
87
99
|
const combined = `${foldedSummary}${content}`;
|
|
88
100
|
if (combined.length <= limit) {
|
|
@@ -96,18 +108,18 @@ export function combineSummaryAndResponse(foldedSummary, responseText, limit = 4
|
|
|
96
108
|
if (splitAt >= Math.floor(remainingInFirstChunk / 3)) {
|
|
97
109
|
const firstChunkText = content.slice(0, splitAt + 1).trim();
|
|
98
110
|
const rest = content.slice(splitAt + 1).trim();
|
|
99
|
-
const restChunks =
|
|
111
|
+
const restChunks = chunkMessage(rest, limit);
|
|
100
112
|
return [`${foldedSummary}${firstChunkText}`, ...restChunks];
|
|
101
113
|
}
|
|
102
114
|
}
|
|
103
115
|
// If foldedSummary takes up most of the message or no clean paragraph break was found,
|
|
104
116
|
// foldedSummary gets its own clean message, and responseText is chunked independently.
|
|
105
|
-
const responseChunks =
|
|
117
|
+
const responseChunks = chunkMessage(content, limit);
|
|
106
118
|
return [foldedSummary.trim(), ...responseChunks];
|
|
107
119
|
}
|
|
108
120
|
export function renderStreamPreviewContent(state) {
|
|
109
121
|
const parts = [];
|
|
110
|
-
const name = state.engineName || 'AI';
|
|
122
|
+
const name = escapeTelegramHtml(state.engineName || 'AI');
|
|
111
123
|
const roundText = state.stepIndex && state.stepIndex > 0 ? `第 ${state.stepIndex} 轮` : '';
|
|
112
124
|
const toolText = typeof state.toolCount === 'number' && state.toolCount > 0 ? `累计执行 ${state.toolCount} 次工具` : '';
|
|
113
125
|
const timeText = typeof state.elapsedSeconds === 'number' && state.elapsedSeconds > 0 ? `已耗时 ${state.elapsedSeconds}s` : '';
|
|
@@ -125,13 +137,13 @@ export function renderStreamPreviewContent(state) {
|
|
|
125
137
|
parts.push('');
|
|
126
138
|
}
|
|
127
139
|
if (state.toolLines.length > 0) {
|
|
128
|
-
const recent = state.toolLines.slice(-
|
|
140
|
+
const recent = state.toolLines.slice(-3);
|
|
129
141
|
parts.push(`<blockquote expandable>🛠️ <b>正在调用工具 (${state.toolLines.length} 项)</b>\n${recent.join('\n')}</blockquote>`);
|
|
130
142
|
parts.push('');
|
|
131
143
|
}
|
|
132
144
|
if (state.accumulatedText) {
|
|
133
|
-
const preview = state.accumulatedText.slice(-
|
|
134
|
-
parts.push(preview);
|
|
145
|
+
const preview = state.accumulatedText.slice(-1000);
|
|
146
|
+
parts.push(escapeTelegramHtml(preview));
|
|
135
147
|
}
|
|
136
148
|
else {
|
|
137
149
|
parts.push(state.isBoost ? `🚀 ${name} (Boost 模式) 正在深度思考中…` : `⏳ ${name} 正在思考中…`);
|
package/dist/dsh/adapter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
-
import { buildAttachmentPrompt, isNativeImageAttachment } from '../
|
|
3
|
+
import { buildAttachmentPrompt, isNativeImageAttachment } from '../core/attachment_files.js';
|
|
4
4
|
export function optionChoices(option) {
|
|
5
5
|
if (option?.type !== 'select')
|
|
6
6
|
return [];
|
|
@@ -107,7 +107,7 @@ export class DshEngineAdapter {
|
|
|
107
107
|
let finalMessageId;
|
|
108
108
|
const tools = new Map();
|
|
109
109
|
const onUpdate = (notification) => {
|
|
110
|
-
if (notification.sessionId !== state?.sessionId)
|
|
110
|
+
if (cancelled || notification.sessionId !== state?.sessionId)
|
|
111
111
|
return;
|
|
112
112
|
const update = notification.update;
|
|
113
113
|
if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {
|
|
@@ -170,13 +170,26 @@ export class DshEngineAdapter {
|
|
|
170
170
|
};
|
|
171
171
|
queueMicrotask(() => { void run(); });
|
|
172
172
|
return {
|
|
173
|
-
cancel: () => {
|
|
174
|
-
if (finished || cancelled)
|
|
173
|
+
cancel: async () => {
|
|
174
|
+
if (finished || cancelled) {
|
|
175
|
+
await promise;
|
|
175
176
|
return;
|
|
177
|
+
}
|
|
176
178
|
cancelled = true;
|
|
177
179
|
state?.client.cancelPermissions?.();
|
|
178
180
|
if (state?.client.connected)
|
|
179
|
-
|
|
181
|
+
await state.client.agent.notify('session/cancel', { sessionId: state.sessionId }).catch(() => { });
|
|
182
|
+
let timer;
|
|
183
|
+
try {
|
|
184
|
+
await Promise.race([promise, new Promise(resolve => { timer = setTimeout(resolve, 5000); })]);
|
|
185
|
+
if (!finished)
|
|
186
|
+
await this.clients.get(request.scopeId)?.stop();
|
|
187
|
+
await promise;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
if (timer)
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
}
|
|
180
193
|
},
|
|
181
194
|
waitForResult: () => promise,
|
|
182
195
|
on: (event, listener) => { emitter.on(event, listener); },
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AppConfig } from '../config.js';
|
|
2
|
+
import type { BridgeStore } from '../store/database.js';
|
|
3
|
+
import type { Logger } from '../logger.js';
|
|
4
|
+
import type { ChannelPort } from '../core/channel_port.js';
|
|
5
|
+
import type { BackendDescriptor } from '../core/engine_spi.js';
|
|
6
|
+
/** Same definition is mounted on a shared bot or on a dedicated DSH bot. */
|
|
7
|
+
export declare function createDshBackend(config: AppConfig, store: BridgeStore, logger: Logger, messaging: ChannelPort): BackendDescriptor;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getDshTelegramCommands } from '../i18n.js';
|
|
2
|
+
import { DshUi } from './ui.js';
|
|
3
|
+
/** Same definition is mounted on a shared bot or on a dedicated DSH bot. */
|
|
4
|
+
export function createDshBackend(config, store, logger, messaging) {
|
|
5
|
+
const ui = new DshUi(config, store, logger, messaging);
|
|
6
|
+
return {
|
|
7
|
+
id: ui.adapter.id, name: ui.adapter.name, engineType: ui.adapter.id, adapter: ui.adapter,
|
|
8
|
+
defaults: { reasoningEffort: null, supportedReasoningEfforts: [], boost: false, tokenUsage: false },
|
|
9
|
+
commands: getDshTelegramCommands,
|
|
10
|
+
createUi: host => ({
|
|
11
|
+
ownsCallback: data => data.startsWith('dsh:'),
|
|
12
|
+
getPendingApprovals: () => ui.pendingApprovals,
|
|
13
|
+
stop: () => ui.stop(),
|
|
14
|
+
renderCustomStatus: async (scopeId, locale) => ui.status(scopeId, locale),
|
|
15
|
+
renderSetupMenu: async (scopeId, locale, messageId) => { await ui.setup(scopeId, locale, host, messageId); return true; },
|
|
16
|
+
renderModelsMenu: async (scopeId, locale, messageId) => { await ui.models(scopeId, locale, host, messageId); return true; },
|
|
17
|
+
handleCustomCommand: (scopeId, command, args, locale) => ui.command(scopeId, command, args, locale, host),
|
|
18
|
+
handleCustomCallback: (scopeId, data, locale, _messageId, event) => ui.callback(scopeId, data, locale, host, event),
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
}
|
package/dist/dsh/runtime.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { BridgeStore } from '../store/database.js';
|
|
|
4
4
|
/** Dedicated DSH bot using the same backend and channel orchestration as other bots. */
|
|
5
5
|
export declare class DshTelegramRuntime {
|
|
6
6
|
private readonly bot;
|
|
7
|
-
private readonly ui;
|
|
8
7
|
private readonly orchestrator;
|
|
9
8
|
private running;
|
|
10
9
|
constructor(config: AppConfig, store: BridgeStore, logger: Logger);
|
package/dist/dsh/runtime.js
CHANGED
|
@@ -2,12 +2,11 @@ import { TelegramGateway } from '../telegram/gateway.js';
|
|
|
2
2
|
import { TelegramMessagingPort } from '../channels/telegram/telegram_messaging_port.js';
|
|
3
3
|
import { getDshTelegramCommands } from '../i18n.js';
|
|
4
4
|
import { UnifiedChannelOrchestrator } from '../core/orchestrator.js';
|
|
5
|
-
import {
|
|
5
|
+
import { createDshBackend } from './backend.js';
|
|
6
6
|
import { BRIDGE_SCOPE_TELEGRAM_PREFIX, parseTelegramTargetFromBridgeScope } from '../core/bridge_scope.js';
|
|
7
7
|
/** Dedicated DSH bot using the same backend and channel orchestration as other bots. */
|
|
8
8
|
export class DshTelegramRuntime {
|
|
9
9
|
bot;
|
|
10
|
-
ui;
|
|
11
10
|
orchestrator;
|
|
12
11
|
running = false;
|
|
13
12
|
constructor(config, store, logger) {
|
|
@@ -15,17 +14,10 @@ export class DshTelegramRuntime {
|
|
|
15
14
|
throw new Error('DSH_BOT_TOKEN is required');
|
|
16
15
|
this.bot = new TelegramGateway(config.dshBotToken, config.tgAllowedUserId, config.tgAllowedChatId, config.telegramPollIntervalMs, store, logger, true, getDshTelegramCommands);
|
|
17
16
|
const messaging = new TelegramMessagingPort(this.bot);
|
|
18
|
-
|
|
17
|
+
const backend = createDshBackend(config, store, logger, messaging);
|
|
19
18
|
this.orchestrator = new UnifiedChannelOrchestrator({
|
|
20
|
-
config, store, logger, bot: this.bot,
|
|
19
|
+
config, store, logger, bot: this.bot, backends: [backend], defaultBackendId: backend.id, messaging,
|
|
21
20
|
ownsScope: scopeId => scopeId.startsWith(BRIDGE_SCOPE_TELEGRAM_PREFIX) && parseTelegramTargetFromBridgeScope(scopeId).botId === this.bot.identity,
|
|
22
|
-
customUi: {
|
|
23
|
-
renderCustomStatus: async (scopeId, locale) => this.ui.status(scopeId, locale),
|
|
24
|
-
renderSetupMenu: async (scopeId, locale, messageId) => { await this.ui.setup(scopeId, locale, this.orchestrator, messageId); return true; },
|
|
25
|
-
renderModelsMenu: async (scopeId, locale, messageId) => { await this.ui.models(scopeId, locale, this.orchestrator, messageId); return true; },
|
|
26
|
-
handleCustomCommand: (scopeId, command, args, locale) => this.ui.command(scopeId, command, args, locale, this.orchestrator),
|
|
27
|
-
handleCustomCallback: (scopeId, data, locale, _messageId, event) => this.ui.callback(scopeId, data, locale, this.orchestrator, event),
|
|
28
|
-
},
|
|
29
21
|
});
|
|
30
22
|
this.orchestrator.registerInboundHandlers();
|
|
31
23
|
}
|
|
@@ -37,9 +29,8 @@ export class DshTelegramRuntime {
|
|
|
37
29
|
async stop() {
|
|
38
30
|
this.running = false;
|
|
39
31
|
await this.orchestrator.stop();
|
|
40
|
-
await this.ui.stop();
|
|
41
32
|
}
|
|
42
33
|
getRuntimeStatus() {
|
|
43
|
-
return { id: this.bot.identity ?? 'dsh', username: this.bot.username, connected: this.running, activeTurns: this.orchestrator.getActiveTurnsCount(), pendingApprovals: this.
|
|
34
|
+
return { id: this.bot.identity ?? 'dsh', username: this.bot.username, connected: this.running, activeTurns: this.orchestrator.getActiveTurnsCount(), pendingApprovals: this.orchestrator.getPendingApprovals(), defaultBackend: 'dsh' };
|
|
44
35
|
}
|
|
45
36
|
}
|
package/dist/dsh/ui.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { AppConfig } from '../config.js';
|
|
2
2
|
import type { BridgeStore } from '../store/database.js';
|
|
3
3
|
import type { Logger } from '../logger.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ChannelPort } from '../core/channel_port.js';
|
|
5
5
|
import type { AppLocale } from '../types.js';
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
6
|
+
import type { ChannelCallbackEvent } from '../core/channel_events.js';
|
|
7
|
+
import type { BackendUiHost } from '../core/backend_ui.js';
|
|
8
8
|
import { DshEngineAdapter } from './adapter.js';
|
|
9
9
|
/** DSH controls shared by the unified bot and the optional dedicated bot. */
|
|
10
10
|
export declare class DshUi {
|
|
@@ -16,14 +16,14 @@ export declare class DshUi {
|
|
|
16
16
|
private readonly approvals;
|
|
17
17
|
readonly adapter: DshEngineAdapter;
|
|
18
18
|
get pendingApprovals(): number;
|
|
19
|
-
constructor(config: AppConfig, store: BridgeStore, logger: Logger, messaging:
|
|
19
|
+
constructor(config: AppConfig, store: BridgeStore, logger: Logger, messaging: ChannelPort);
|
|
20
20
|
private defaultAccess;
|
|
21
21
|
private copy;
|
|
22
22
|
status(scopeId: string, locale: AppLocale): string;
|
|
23
23
|
private button;
|
|
24
24
|
private panel;
|
|
25
|
-
setup(scopeId: string, locale: AppLocale, orchestrator:
|
|
26
|
-
models(scopeId: string, locale: AppLocale, orchestrator:
|
|
25
|
+
setup(scopeId: string, locale: AppLocale, orchestrator: BackendUiHost, messageId?: number): Promise<void>;
|
|
26
|
+
models(scopeId: string, locale: AppLocale, orchestrator: BackendUiHost, messageId?: number): Promise<void>;
|
|
27
27
|
private efforts;
|
|
28
28
|
private selectModel;
|
|
29
29
|
private selectEffort;
|
|
@@ -31,8 +31,8 @@ export declare class DshUi {
|
|
|
31
31
|
private threads;
|
|
32
32
|
private open;
|
|
33
33
|
private plugins;
|
|
34
|
-
command(scopeId: string, command: string, args: string, locale: AppLocale, orchestrator:
|
|
35
|
-
callback(scopeId: string, data: string, locale: AppLocale, orchestrator:
|
|
34
|
+
command(scopeId: string, command: string, args: string, locale: AppLocale, orchestrator: BackendUiHost): Promise<boolean>;
|
|
35
|
+
callback(scopeId: string, data: string, locale: AppLocale, orchestrator: BackendUiHost, event?: ChannelCallbackEvent): Promise<boolean>;
|
|
36
36
|
private requestPermission;
|
|
37
37
|
stop(): Promise<void>;
|
|
38
38
|
}
|
package/dist/dsh/ui.js
CHANGED
|
@@ -251,7 +251,7 @@ export class DshUi {
|
|
|
251
251
|
async callback(scopeId, data, locale, orchestrator, event) {
|
|
252
252
|
const answer = (text = '') => this.messaging.answerCallback(event?.callbackQueryId ?? '', text);
|
|
253
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:')) {
|
|
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
255
|
await answer(this.copy(locale, '请重新打开 DSH 设置', 'Open DSH settings again'));
|
|
256
256
|
return true;
|
|
257
257
|
}
|
package/dist/main.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { RuntimeSupervisor } from './core/runtime_supervisor.js';
|
|
2
3
|
import crypto from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import os from 'node:os';
|
|
@@ -605,7 +606,7 @@ function truncateStatusLine(value, maxLength) {
|
|
|
605
606
|
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
606
607
|
}
|
|
607
608
|
async function runServeCli() {
|
|
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
|
+
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter }, { AuthCandidateMirror }, { CrossNodeAuthSync }, { OpencodeTelegramRuntime }, { DshTelegramRuntime }, { createDshBackend }, { UnifiedBridgeCore }, { AntigravityAppClient }, { AntigravityAuthManager, linkAntigravityAuthTokens },] = await Promise.all([
|
|
609
610
|
import('./channels/bridge_messaging_router.js'),
|
|
610
611
|
import('./channels/telegram/telegram_messaging_port.js'),
|
|
611
612
|
import('./channels/weixin/weixin_channel_adapter.js'),
|
|
@@ -622,7 +623,8 @@ async function runServeCli() {
|
|
|
622
623
|
import('./auth/cross_node_sync.js'),
|
|
623
624
|
import('./opencode/runtime.js'),
|
|
624
625
|
import('./dsh/runtime.js'),
|
|
625
|
-
import('./
|
|
626
|
+
import('./dsh/backend.js'),
|
|
627
|
+
import('./bridge/unified_bridge.js'),
|
|
626
628
|
import('./antigravity/client.js'),
|
|
627
629
|
import('./antigravity/auth.js'),
|
|
628
630
|
]);
|
|
@@ -632,11 +634,13 @@ async function runServeCli() {
|
|
|
632
634
|
const authNotificationAggregator = createAuthRefreshNotificationAggregator(logger);
|
|
633
635
|
attachIlinkRuntimeFromBridgeLogger(logger, config.wxIlinkRouteTag);
|
|
634
636
|
const processLock = acquireProcessLock(config.lockPath);
|
|
637
|
+
const supervisor = new RuntimeSupervisor();
|
|
638
|
+
supervisor.register('process-lock', { stop: () => processLock.release() });
|
|
635
639
|
let store = null;
|
|
636
640
|
let weixinAdapter = null;
|
|
637
641
|
let activeWeixinCore = null;
|
|
638
|
-
|
|
639
|
-
|
|
642
|
+
const activeTelegramAdapters = [];
|
|
643
|
+
const managedApps = [];
|
|
640
644
|
let activeAuthMirror = null;
|
|
641
645
|
let activeAuthSync = null;
|
|
642
646
|
let activeOpencodeRuntime = null;
|
|
@@ -646,6 +650,30 @@ async function runServeCli() {
|
|
|
646
650
|
let sharedAntigravityAuth = null;
|
|
647
651
|
try {
|
|
648
652
|
store = new BridgeStore(config.storePath);
|
|
653
|
+
supervisor.register('store', { stop: () => store?.close() });
|
|
654
|
+
supervisor.register('native-apps', { stop: async () => {
|
|
655
|
+
const apps = new Set([...managedApps, ...(sharedCodexApp ? [sharedCodexApp] : [])]);
|
|
656
|
+
const results = await Promise.allSettled([...apps].map(app => app.stop({ terminateServer: true })));
|
|
657
|
+
for (const result of results)
|
|
658
|
+
if (result.status === 'rejected')
|
|
659
|
+
logger.warn('codex.app-server.stop_failed', { error: serializeError(result.reason) });
|
|
660
|
+
} });
|
|
661
|
+
supervisor.register('channels-and-sync', { stop: async () => {
|
|
662
|
+
const failures = [];
|
|
663
|
+
const cleanups = [() => authNotificationAggregator.flushAll(), () => activeAuthSync?.stop(), () => activeAuthMirror?.stop(),
|
|
664
|
+
() => weixinAdapter?.stop(), () => activeWeixinCore?.stop({ keepTransports: true }),
|
|
665
|
+
...activeTelegramAdapters.map(adapter => () => adapter.stop()),
|
|
666
|
+
() => activeOpencodeRuntime?.stop(), () => activeDshRuntime?.stop()];
|
|
667
|
+
for (const cleanup of cleanups)
|
|
668
|
+
try {
|
|
669
|
+
await cleanup();
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
failures.push(error);
|
|
673
|
+
}
|
|
674
|
+
if (failures.length)
|
|
675
|
+
throw new AggregateError(failures, 'Runtime cleanup failed');
|
|
676
|
+
} });
|
|
649
677
|
if (config.dshBotToken) {
|
|
650
678
|
activeDshRuntime = new DshTelegramRuntime(config, store, logger);
|
|
651
679
|
await activeDshRuntime.start();
|
|
@@ -668,15 +696,15 @@ async function runServeCli() {
|
|
|
668
696
|
};
|
|
669
697
|
writeDshStatus();
|
|
670
698
|
const timer = setInterval(writeDshStatus, 5000);
|
|
671
|
-
|
|
699
|
+
let shutdownPromise = null;
|
|
700
|
+
const shutdown = () => shutdownPromise ??= (async () => {
|
|
672
701
|
clearInterval(timer);
|
|
673
|
-
await activeDshRuntime.stop();
|
|
674
|
-
await activeOpencodeRuntime?.stop();
|
|
675
702
|
writeDshStatus(false);
|
|
676
|
-
|
|
677
|
-
|
|
703
|
+
const failures = await supervisor.stop();
|
|
704
|
+
for (const failure of failures)
|
|
705
|
+
logger.warn('bridge.shutdown_failed', { resource: failure.name, error: serializeError(failure.error) });
|
|
678
706
|
process.exit(0);
|
|
679
|
-
};
|
|
707
|
+
})();
|
|
680
708
|
process.on('SIGINT', () => void shutdown());
|
|
681
709
|
process.on('SIGTERM', () => void shutdown());
|
|
682
710
|
return;
|
|
@@ -748,6 +776,7 @@ async function runServeCli() {
|
|
|
748
776
|
...codexApiProviderOverrides,
|
|
749
777
|
...(isCodexDefault ? [] : ['cli_auth_credentials_store="file"']),
|
|
750
778
|
]);
|
|
779
|
+
managedApps.push(codexApp);
|
|
751
780
|
// Antigravity environment
|
|
752
781
|
const isAntigravityDefault = backend === 'antigravity' && isDefaultRuntimeForBackend;
|
|
753
782
|
let antigravityHome;
|
|
@@ -819,9 +848,8 @@ async function runServeCli() {
|
|
|
819
848
|
restoreImportedAuthCandidateState(store, event.record.candidateName, authRuntimeIds);
|
|
820
849
|
},
|
|
821
850
|
});
|
|
822
|
-
await mirror.initialize();
|
|
823
851
|
activeAuthMirror = mirror;
|
|
824
|
-
|
|
852
|
+
await mirror.initialize();
|
|
825
853
|
const lastSelfUpdatePath = path.join(APP_HOME, 'runtime', 'last-self-update.json');
|
|
826
854
|
let lastSelfUpdate = readSelfUpdateStatus(lastSelfUpdatePath);
|
|
827
855
|
const runtimes = [];
|
|
@@ -841,7 +869,7 @@ async function runServeCli() {
|
|
|
841
869
|
...(first?.codexAppServer ? { codexAppServer: first.codexAppServer } : {}),
|
|
842
870
|
botUsername: first?.botUsername ?? null,
|
|
843
871
|
currentBindings: store.countBindings(),
|
|
844
|
-
pendingApprovals: store.countPendingApprovals() + statuses.reduce((sum, status) => sum + status.
|
|
872
|
+
pendingApprovals: store.countPendingApprovals() + statuses.reduce((sum, status) => sum + status.pendingApprovals, 0) + (activeDshRuntime?.getRuntimeStatus().pendingApprovals ?? 0),
|
|
845
873
|
pendingUserInputs: store.countPendingUserInputs(),
|
|
846
874
|
queuedTurns: store.countQueuedTurnInputs(),
|
|
847
875
|
activeTurns: statuses.reduce((sum, status) => sum + status.activeTurns, 0)
|
|
@@ -973,18 +1001,20 @@ async function runServeCli() {
|
|
|
973
1001
|
antigravityApp: seed.antigravityApp,
|
|
974
1002
|
antigravityAuth: seed.antigravityAuth,
|
|
975
1003
|
defaultBackendId: seed.defaultBackend,
|
|
1004
|
+
backends: seed.config.dsh ? [createDshBackend(seed.config, store, logger, telegramMessaging)] : [],
|
|
976
1005
|
selfUpdater,
|
|
977
1006
|
});
|
|
978
|
-
|
|
1007
|
+
const telegram = new TelegramChannelAdapter(core);
|
|
1008
|
+
activeTelegramAdapters.push(telegram);
|
|
1009
|
+
runtimes.push({ ...seed, core, telegram });
|
|
979
1010
|
}
|
|
980
1011
|
if (config.wxEnabled) {
|
|
981
1012
|
const weixinApp = new CodexAppClient(config.codexCliBin, config.codexAppLaunchCmd, config.codexAppAutolaunch, config.codexAppServerStatePath, config.codexAppServerLogPath, logger, null, codexApiProviderOverrides);
|
|
1013
|
+
managedApps.push(weixinApp);
|
|
982
1014
|
const outbound = new BridgeMessagingRouter(new TelegramMessagingPort(seeds[0].bot), new WeixinMessagingPort(store, (id) => loadWeixinAccount(config.weixinAccountsDir, id)));
|
|
983
1015
|
activeWeixinCore = new BridgeSessionCore(config, store, logger, seeds[0].bot, weixinApp, outbound, selfUpdater, coordinator, false);
|
|
984
|
-
managedApps.push(weixinApp);
|
|
985
1016
|
weixinAdapter = new WeixinChannelAdapter(activeWeixinCore, store, config, logger);
|
|
986
1017
|
}
|
|
987
|
-
activeTelegramAdapters = runtimes.map((runtime) => runtime.telegram);
|
|
988
1018
|
if (config.authSyncEnabled) {
|
|
989
1019
|
const authSyncTransportBot = seeds[0];
|
|
990
1020
|
const authSyncTransportLabel = authSyncTransportBot.bot.username
|
|
@@ -1051,40 +1081,22 @@ async function runServeCli() {
|
|
|
1051
1081
|
}
|
|
1052
1082
|
writeAggregateStatus();
|
|
1053
1083
|
logger.info('bridge.started', { bots: runtimes.map((runtime) => runtime.id) });
|
|
1054
|
-
|
|
1084
|
+
let shutdownPromise = null;
|
|
1085
|
+
const shutdown = (signal) => shutdownPromise ??= (async () => {
|
|
1055
1086
|
logger.info('bridge.shutting_down', { signal });
|
|
1056
|
-
await authNotificationAggregator.flushAll();
|
|
1057
|
-
authSync?.stop();
|
|
1058
|
-
mirror.stop();
|
|
1059
|
-
await weixinAdapter?.stop();
|
|
1060
|
-
await activeWeixinCore?.stop();
|
|
1061
|
-
await Promise.all(runtimes.map((runtime) => runtime.telegram.stop()));
|
|
1062
|
-
await activeOpencodeRuntime?.stop();
|
|
1063
|
-
await activeDshRuntime?.stop();
|
|
1064
1087
|
writeAggregateStatus(false);
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
logger.warn('
|
|
1068
|
-
})));
|
|
1069
|
-
store?.close();
|
|
1070
|
-
processLock.release();
|
|
1088
|
+
const failures = await supervisor.stop();
|
|
1089
|
+
for (const failure of failures)
|
|
1090
|
+
logger.warn('bridge.shutdown_failed', { resource: failure.name, error: serializeError(failure.error) });
|
|
1071
1091
|
process.exit(0);
|
|
1072
|
-
};
|
|
1092
|
+
})();
|
|
1073
1093
|
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
1074
1094
|
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
1075
1095
|
}
|
|
1076
1096
|
catch (error) {
|
|
1077
|
-
await
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
await weixinAdapter?.stop().catch(() => { });
|
|
1081
|
-
await activeWeixinCore?.stop().catch(() => { });
|
|
1082
|
-
await Promise.allSettled(activeTelegramAdapters.map((adapter) => adapter.stop()));
|
|
1083
|
-
await activeOpencodeRuntime?.stop().catch(() => { });
|
|
1084
|
-
await activeDshRuntime?.stop().catch(() => { });
|
|
1085
|
-
await Promise.allSettled(managedApps.map((app) => app.stop({ terminateServer: true })));
|
|
1086
|
-
store?.close();
|
|
1087
|
-
processLock.release();
|
|
1097
|
+
const failures = await supervisor.stop();
|
|
1098
|
+
for (const failure of failures)
|
|
1099
|
+
logger.warn('bridge.shutdown_failed', { resource: failure.name, error: serializeError(failure.error) });
|
|
1088
1100
|
throw error;
|
|
1089
1101
|
}
|
|
1090
1102
|
}
|