@xmanrui/dsh-im 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/README.en.md +34 -6
- package/README.md +34 -6
- package/lib/index.js +133 -124
- package/package.json +31 -2
- package/plugin-src/host/channels/dingtalk/production.mjs +7 -0
- package/plugin-src/host/channels/feishu/production.mjs +7 -0
- package/plugin-src/host/channels/qq/production.mjs +7 -0
- package/plugin-src/host/channels/shared/production.mjs +7 -0
- package/plugin-src/host/channels/slack/production.mjs +7 -0
- package/plugin-src/host/channels/wecom/production.mjs +7 -0
- package/plugin-src/host/channels/weixin/production.mjs +7 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +7 -0
- package/plugin-src/host/harness-session-coordinator.mjs +134 -0
- package/src/channels/dingtalk/dingtalk-bridge.mjs +81 -0
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/feishu/bridge.mjs +81 -0
- package/src/channels/qq/qq-bridge.mjs +84 -0
- package/src/channels/shared/bot-workspace-store.mjs +45 -1
- package/src/channels/shared/control-command.mjs +84 -0
- package/src/channels/shared/harness-approval.mjs +4 -0
- package/src/channels/shared/harness-client.mjs +336 -29
- package/src/channels/shared/model-command.mjs +314 -0
- package/src/channels/shared/session-binding-lock.mjs +40 -0
- package/src/channels/shared/text-harness-bridge.mjs +81 -0
- package/src/channels/shared/workspace-session.mjs +21 -9
- package/src/channels/wecom/wecom-bridge.mjs +75 -0
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +81 -0
|
@@ -11,6 +11,14 @@ import {
|
|
|
11
11
|
} from '../shared/harness-question.mjs';
|
|
12
12
|
import { HarnessApprovalQueue } from '../shared/harness-approval.mjs';
|
|
13
13
|
import { runCompactCommand } from '../shared/compact-command.mjs';
|
|
14
|
+
import {
|
|
15
|
+
isControlCommand,
|
|
16
|
+
runControlCommand,
|
|
17
|
+
} from '../shared/control-command.mjs';
|
|
18
|
+
import {
|
|
19
|
+
isModelCommand,
|
|
20
|
+
runModelCommand,
|
|
21
|
+
} from '../shared/model-command.mjs';
|
|
14
22
|
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
15
23
|
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
16
24
|
import {
|
|
@@ -32,6 +40,10 @@ const HELP_TEXT = [
|
|
|
32
40
|
'/workspacelist 列出工作区绝对路径',
|
|
33
41
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
34
42
|
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
43
|
+
'/models 列出所有可用模型',
|
|
44
|
+
'/model [模型ID] 查看或切换当前会话模型',
|
|
45
|
+
'/stop 停止当前任务',
|
|
46
|
+
'/steer 补充指令 纠偏当前任务',
|
|
35
47
|
'/status 检查连接状态',
|
|
36
48
|
'/help 显示本帮助',
|
|
37
49
|
].join('\n');
|
|
@@ -85,6 +97,7 @@ export class WeixinHarnessBridge {
|
|
|
85
97
|
#interactionKeys = new Map();
|
|
86
98
|
#acceptedMessageIds = new Set();
|
|
87
99
|
#approvalTasks = new Set();
|
|
100
|
+
#commandTasks = new Set();
|
|
88
101
|
#approvals;
|
|
89
102
|
|
|
90
103
|
constructor({
|
|
@@ -136,6 +149,34 @@ export class WeixinHarnessBridge {
|
|
|
136
149
|
const contextToken = nonEmptyString(message?.context_token) ?? undefined;
|
|
137
150
|
const runId = nonEmptyString(message?.run_id) ?? undefined;
|
|
138
151
|
const pending = this.#pendingInteractions.get(key);
|
|
152
|
+
const commandText = nonEmptyString(extractWeixinText(message)) ?? '';
|
|
153
|
+
const commandRunner = isControlCommand(commandText)
|
|
154
|
+
? runControlCommand
|
|
155
|
+
: (isModelCommand(commandText) ? runModelCommand : null);
|
|
156
|
+
if (commandRunner && sender === this.#ownerUserId) {
|
|
157
|
+
let task;
|
|
158
|
+
task = this.#processFastCommand(
|
|
159
|
+
message,
|
|
160
|
+
messageId,
|
|
161
|
+
key,
|
|
162
|
+
sender,
|
|
163
|
+
contextToken,
|
|
164
|
+
runId,
|
|
165
|
+
commandText,
|
|
166
|
+
commandRunner,
|
|
167
|
+
).catch((error) => {
|
|
168
|
+
if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
|
|
169
|
+
this.#status.lastError = error?.message ?? String(error);
|
|
170
|
+
this.#logger.error?.('[dsh-weixin] failed to process a command:', error);
|
|
171
|
+
return this.#send(sender, '消息处理失败,请稍后重试。', contextToken, runId)
|
|
172
|
+
.catch(() => undefined);
|
|
173
|
+
}).finally(() => {
|
|
174
|
+
this.#acceptedMessageIds.delete(messageId);
|
|
175
|
+
this.#commandTasks.delete(task);
|
|
176
|
+
});
|
|
177
|
+
this.#commandTasks.add(task);
|
|
178
|
+
return task;
|
|
179
|
+
}
|
|
139
180
|
const approval = this.#approvals.claimReply({
|
|
140
181
|
key,
|
|
141
182
|
actor: sender,
|
|
@@ -211,9 +252,44 @@ export class WeixinHarnessBridge {
|
|
|
211
252
|
pending.queue ? [pending.queue] : []
|
|
212
253
|
)),
|
|
213
254
|
...this.#approvalTasks,
|
|
255
|
+
...this.#commandTasks,
|
|
214
256
|
]);
|
|
215
257
|
}
|
|
216
258
|
|
|
259
|
+
async #processFastCommand(
|
|
260
|
+
message,
|
|
261
|
+
messageId,
|
|
262
|
+
key,
|
|
263
|
+
sender,
|
|
264
|
+
contextToken,
|
|
265
|
+
runId,
|
|
266
|
+
text,
|
|
267
|
+
runner,
|
|
268
|
+
) {
|
|
269
|
+
this.#signal?.throwIfAborted();
|
|
270
|
+
if (this.#state.hasSeen(messageId)) return;
|
|
271
|
+
await this.#state.markSeen(messageId);
|
|
272
|
+
this.#status.messagesReceived += 1;
|
|
273
|
+
this.#status.lastMessageAt = new Date().toISOString();
|
|
274
|
+
const result = await runner(text, this.#harness, this.#state, key, {
|
|
275
|
+
signal: this.#signal,
|
|
276
|
+
hasImages: hasWeixinImageItems(message),
|
|
277
|
+
pendingInteraction: this.#pendingInteractions.has(key)
|
|
278
|
+
|| this.#approvals.hasPending(key),
|
|
279
|
+
control: { owner: this, key },
|
|
280
|
+
});
|
|
281
|
+
if (result?.stopped) {
|
|
282
|
+
await Promise.allSettled([
|
|
283
|
+
this.#cancelPendingInteraction(key),
|
|
284
|
+
this.#approvals.closeRoute(key),
|
|
285
|
+
]);
|
|
286
|
+
}
|
|
287
|
+
for (const reply of result?.messages ?? [result?.message]) {
|
|
288
|
+
if (reply) await this.#send(sender, reply, contextToken, runId);
|
|
289
|
+
}
|
|
290
|
+
this.#status.lastError = null;
|
|
291
|
+
}
|
|
292
|
+
|
|
217
293
|
async #process(message, key, { alreadyRecorded = false } = {}) {
|
|
218
294
|
this.#signal?.throwIfAborted();
|
|
219
295
|
const messageId = weixinMessageId(message);
|
|
@@ -303,6 +379,7 @@ export class WeixinHarnessBridge {
|
|
|
303
379
|
askOptions: {
|
|
304
380
|
timeoutMs: this.#replyTimeoutMs,
|
|
305
381
|
signal: this.#signal,
|
|
382
|
+
control: { owner: this, key },
|
|
306
383
|
onInteraction: (interaction) => this.#handleInteraction(interaction, {
|
|
307
384
|
key,
|
|
308
385
|
actor: sender,
|
|
@@ -324,6 +401,10 @@ export class WeixinHarnessBridge {
|
|
|
324
401
|
this.#status.lastReplyAt = new Date().toISOString();
|
|
325
402
|
this.#status.lastError = null;
|
|
326
403
|
} catch (error) {
|
|
404
|
+
if (error?.code === 'turn-stopped') {
|
|
405
|
+
await this.#state.markSeen(messageId);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
327
408
|
if (this.#signal?.aborted) return;
|
|
328
409
|
this.#status.lastError = error?.message ?? String(error);
|
|
329
410
|
this.#logger.error?.('[dsh-weixin] failed to process an inbound message:', error);
|