@xmanrui/dsh-im 0.4.0 → 0.5.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.md +30 -0
- package/lib/client.js +709 -285
- package/lib/index.js +112 -110
- package/package.json +1 -1
- package/plugin-src/client/channels/dingtalk/api.js +2 -0
- package/plugin-src/client/channels/dingtalk/index.js +67 -14
- package/plugin-src/client/channels/feishu/api.js +2 -0
- package/plugin-src/client/channels/feishu/index.js +70 -22
- package/plugin-src/client/channels/qq/api.js +2 -0
- package/plugin-src/client/channels/qq/index.js +45 -8
- package/plugin-src/client/channels/shared/token-api.js +2 -0
- package/plugin-src/client/channels/shared/token-channel.js +34 -8
- package/plugin-src/client/channels/wecom/api.js +2 -0
- package/plugin-src/client/channels/wecom/index.js +45 -8
- package/plugin-src/client/channels/weixin/api.js +2 -0
- package/plugin-src/client/channels/weixin/index.js +68 -8
- package/plugin-src/client/channels/whatsapp/api.js +2 -0
- package/plugin-src/client/channels/whatsapp/index.js +27 -5
- package/plugin-src/client/i18n.js +13 -0
- package/plugin-src/client/styles.js +13 -0
- package/plugin-src/client/workspace-editor.js +96 -0
- package/plugin-src/client/workspace-snapshot-fence.js +28 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +34 -11
- package/plugin-src/host/channels/dingtalk/rpc.mjs +17 -2
- package/plugin-src/host/channels/feishu/production.mjs +42 -5
- package/plugin-src/host/channels/feishu/rpc.mjs +17 -2
- package/plugin-src/host/channels/qq/production.mjs +48 -22
- package/plugin-src/host/channels/qq/rpc.mjs +16 -2
- package/plugin-src/host/channels/shared/production.mjs +48 -22
- package/plugin-src/host/channels/shared/rpc.mjs +15 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +25 -0
- package/plugin-src/host/channels/slack/production.mjs +48 -23
- package/plugin-src/host/channels/slack/rpc.mjs +16 -0
- package/plugin-src/host/channels/wecom/production.mjs +49 -23
- package/plugin-src/host/channels/wecom/rpc.mjs +16 -2
- package/plugin-src/host/channels/weixin/production.mjs +31 -11
- package/plugin-src/host/channels/weixin/rpc.mjs +21 -2
- package/plugin-src/host/channels/whatsapp/production.mjs +49 -23
- package/plugin-src/host/channels/whatsapp/rpc.mjs +16 -2
- package/src/channels/dingtalk/dingtalk-bridge.mjs +22 -11
- package/src/channels/dingtalk/dingtalk-controller.mjs +6 -1
- package/src/channels/dingtalk/harness-client.mjs +4 -3
- package/src/channels/dingtalk/state-store.mjs +5 -0
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/feishu/bridge.mjs +36 -16
- package/src/channels/feishu/harness-client.mjs +6 -5
- package/src/channels/feishu/state-store.mjs +5 -0
- package/src/channels/qq/qq-bridge.mjs +25 -15
- package/src/channels/qq/qq-controller.mjs +8 -1
- package/src/channels/qq/state-store.mjs +5 -0
- package/src/channels/shared/bot-workspace-store.mjs +584 -0
- package/src/channels/shared/conversation-state-store.mjs +5 -0
- package/src/channels/shared/text-harness-bridge.mjs +23 -13
- package/src/channels/shared/workspace-command.mjs +26 -0
- package/src/channels/shared/workspace-session.mjs +44 -0
- package/src/channels/wecom/state-store.mjs +5 -0
- package/src/channels/wecom/wecom-bridge.mjs +22 -13
- package/src/channels/wecom/wecom-controller.mjs +8 -1
- package/src/channels/weixin/harness-client.mjs +6 -5
- package/src/channels/weixin/state-store.mjs +5 -0
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +16 -6
- package/src/channels/weixin/weixin-controller.mjs +6 -1
- package/src/channels/whatsapp/whatsapp-controller.mjs +8 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const WORKSPACE_SESSION_STALE = 'workspace-session-stale';
|
|
2
|
+
|
|
3
|
+
async function sessionExists(harness, sessionId, options) {
|
|
4
|
+
return options === undefined
|
|
5
|
+
? harness.sessionExists(sessionId)
|
|
6
|
+
: harness.sessionExists(sessionId, options);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function createSession(harness, options) {
|
|
10
|
+
return options === undefined
|
|
11
|
+
? harness.createSession()
|
|
12
|
+
: harness.createSession(options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve, persist, and ask through a session that belongs to the bot's
|
|
17
|
+
* current workspace. A concurrent workspace switch invalidates the scoped
|
|
18
|
+
* session and retries before any prompt is sent to the stale session.
|
|
19
|
+
*/
|
|
20
|
+
export async function askInWorkspaceSession({
|
|
21
|
+
harness,
|
|
22
|
+
state,
|
|
23
|
+
key,
|
|
24
|
+
text,
|
|
25
|
+
createOptions,
|
|
26
|
+
existsOptions,
|
|
27
|
+
askOptions,
|
|
28
|
+
}) {
|
|
29
|
+
while (true) {
|
|
30
|
+
let sessionId = state.sessionFor(key);
|
|
31
|
+
if (!sessionId || !(await sessionExists(harness, sessionId, existsOptions))) {
|
|
32
|
+
sessionId = await createSession(harness, createOptions);
|
|
33
|
+
if (await state.setSession(key, sessionId) === false) continue;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return {
|
|
37
|
+
sessionId,
|
|
38
|
+
answer: await harness.ask(sessionId, text, askOptions),
|
|
39
|
+
};
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error?.code !== WORKSPACE_SESSION_STALE) throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { generateReqId } from '@wecom/aibot-node-sdk';
|
|
2
|
+
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
3
|
+
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
2
4
|
|
|
3
5
|
const HELP_TEXT = [
|
|
4
6
|
'企业微信机器人已连接 DeepSeek Harness。',
|
|
5
7
|
'',
|
|
6
8
|
'直接发送文字即可继续当前会话。',
|
|
7
9
|
'/new 开启一个全新会话',
|
|
10
|
+
'/workspace 工作区绝对路径 切换工作区',
|
|
8
11
|
'/status 检查连接状态',
|
|
9
12
|
'/help 显示本帮助',
|
|
10
13
|
].join('\n');
|
|
@@ -182,11 +185,11 @@ export class WecomHarnessBridge {
|
|
|
182
185
|
await this.#state.markSeen(messageId);
|
|
183
186
|
return;
|
|
184
187
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
|
|
189
|
+
if (workspaceCommand) {
|
|
190
|
+
await this.#sendImmediate(frame, chatId, workspaceCommand.message);
|
|
191
|
+
await this.#state.markSeen(messageId);
|
|
192
|
+
return;
|
|
190
193
|
}
|
|
191
194
|
|
|
192
195
|
streamId = this.#generateReqId('stream');
|
|
@@ -197,14 +200,20 @@ export class WecomHarnessBridge {
|
|
|
197
200
|
this.#logger.warn?.('[dsh-im:wecom] unable to start a stream; using an active reply:', error);
|
|
198
201
|
}
|
|
199
202
|
|
|
200
|
-
const answer = await
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
:
|
|
203
|
+
const { answer } = await askInWorkspaceSession({
|
|
204
|
+
harness: this.#harness,
|
|
205
|
+
state: this.#state,
|
|
206
|
+
key,
|
|
207
|
+
text,
|
|
208
|
+
askOptions: {
|
|
209
|
+
timeoutMs: this.#replyTimeoutMs,
|
|
210
|
+
onUpdate: streamStarted && typeof this.#client.replyStreamNonBlocking === 'function'
|
|
211
|
+
? async (update) => {
|
|
212
|
+
const progress = splitUtf8(progressText(update))[0];
|
|
213
|
+
if (progress) await this.#client.replyStreamNonBlocking(frame, streamId, progress, false);
|
|
214
|
+
}
|
|
215
|
+
: undefined,
|
|
216
|
+
},
|
|
208
217
|
});
|
|
209
218
|
|
|
210
219
|
const chunks = splitUtf8(answer || '任务已完成,但没有生成可显示的文本。');
|
|
@@ -400,7 +400,14 @@ export class WecomController {
|
|
|
400
400
|
if (record.controller.signal.aborted) {
|
|
401
401
|
await this.#stopRuntime(identity.botId);
|
|
402
402
|
if (previousConfig) await this.#configStore.save(previousConfig).catch(() => undefined);
|
|
403
|
-
else
|
|
403
|
+
else {
|
|
404
|
+
const removed = await this.#configStore.remove(identity.botId).catch(() => null);
|
|
405
|
+
if (removed) {
|
|
406
|
+
await this.#deleteState({ botId: identity.botId, config }).catch((cleanupError) => {
|
|
407
|
+
this.#logger.warn?.('[dsh-im:wecom] cancelled bot state cleanup failed:', cleanupError);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
404
411
|
await this.#restoreCredential(identity.secretRef, previousSecret);
|
|
405
412
|
throw error;
|
|
406
413
|
}
|
|
@@ -187,17 +187,18 @@ export class HarnessClient {
|
|
|
187
187
|
throw new Error(`Harness did not become ready: ${lastError?.message ?? 'timeout'}`);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
async workspaceId() {
|
|
190
|
+
async workspaceId(options = {}) {
|
|
191
|
+
const workspace = options.workspace ?? this.#workspace;
|
|
191
192
|
const { items } = await this.rpc('workspace.list', {});
|
|
192
|
-
const existing = items.find((item) => item.path ===
|
|
193
|
+
const existing = items.find((item) => item.path === workspace);
|
|
193
194
|
if (existing) return existing.workspaceId;
|
|
194
|
-
const created = await this.rpc('workspace.create', { path:
|
|
195
|
+
const created = await this.rpc('workspace.create', { path: workspace });
|
|
195
196
|
return created.workspace.workspaceId;
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
async createSession() {
|
|
199
|
+
async createSession(options = {}) {
|
|
199
200
|
await this.ensureRunning();
|
|
200
|
-
const workspaceId = await this.workspaceId();
|
|
201
|
+
const workspaceId = await this.workspaceId(options);
|
|
201
202
|
const created = await this.rpc('session.create', {
|
|
202
203
|
workspaceId,
|
|
203
204
|
agentPreset: this.#agentPreset,
|
|
@@ -3,12 +3,15 @@ import {
|
|
|
3
3
|
splitWeixinText,
|
|
4
4
|
weixinMessageId,
|
|
5
5
|
} from './weixin-api.mjs';
|
|
6
|
+
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
7
|
+
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
6
8
|
|
|
7
9
|
const HELP_TEXT = [
|
|
8
10
|
'微信已连接 DeepSeek Harness。',
|
|
9
11
|
'',
|
|
10
12
|
'直接发送文字或带文字识别结果的语音即可继续当前会话。',
|
|
11
13
|
'/new 开启一个全新会话',
|
|
14
|
+
'/workspace 工作区绝对路径 切换工作区',
|
|
12
15
|
'/status 检查连接状态',
|
|
13
16
|
'/help 显示本帮助',
|
|
14
17
|
].join('\n');
|
|
@@ -133,14 +136,21 @@ export class WeixinHarnessBridge {
|
|
|
133
136
|
await this.#state.markSeen(messageId);
|
|
134
137
|
return;
|
|
135
138
|
}
|
|
139
|
+
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
|
|
140
|
+
if (workspaceCommand) {
|
|
141
|
+
await this.#send(sender, workspaceCommand.message, contextToken, runId);
|
|
142
|
+
await this.#state.markSeen(messageId);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
136
145
|
|
|
137
146
|
const key = conversationKey(sender);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
const { answer } = await askInWorkspaceSession({
|
|
148
|
+
harness: this.#harness,
|
|
149
|
+
state: this.#state,
|
|
150
|
+
key,
|
|
151
|
+
text,
|
|
152
|
+
askOptions: { timeoutMs: this.#replyTimeoutMs },
|
|
153
|
+
});
|
|
144
154
|
await this.#send(sender, answer, contextToken, runId);
|
|
145
155
|
await this.#state.markSeen(messageId);
|
|
146
156
|
this.#status.messagesReplied += 1;
|
|
@@ -468,7 +468,12 @@ export class WeixinController {
|
|
|
468
468
|
await this.#stopRuntime(identity.botId);
|
|
469
469
|
if (previousConfig) await this.#configStore.save(previousConfig).catch(() => undefined);
|
|
470
470
|
else if (this.#configStore.get(identity.botId)) {
|
|
471
|
-
await this.#configStore.remove(identity.botId).catch(() =>
|
|
471
|
+
const removed = await this.#configStore.remove(identity.botId).catch(() => null);
|
|
472
|
+
if (removed) {
|
|
473
|
+
await this.#deleteState({ botId: identity.botId, config }).catch((cleanupError) => {
|
|
474
|
+
this.#logger.warn?.('[dsh-weixin] failed to clean up cancelled bot state:', cleanupError);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
472
477
|
}
|
|
473
478
|
await this.#restoreCredential(identity.tokenRef, previousToken);
|
|
474
479
|
if (previousConfig && previousToken?.value) {
|
|
@@ -311,7 +311,14 @@ export class WhatsappController {
|
|
|
311
311
|
if (record.controller.signal.aborted || this.#closed || error?.name === 'AbortError') {
|
|
312
312
|
await this.#stopRuntime(botId);
|
|
313
313
|
if (previous) await this.#configStore.save(previous).catch(() => undefined);
|
|
314
|
-
else
|
|
314
|
+
else {
|
|
315
|
+
const removed = await this.#configStore.remove(botId).catch(() => null);
|
|
316
|
+
if (removed) {
|
|
317
|
+
await this.#deleteState({ botId, config }).catch((cleanupError) => {
|
|
318
|
+
this.#logger.warn?.('[dsh-im:whatsapp] cancelled bot state cleanup failed:', cleanupError);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
315
322
|
await this.#deleteAuth(record.authDirectory).catch(() => undefined);
|
|
316
323
|
if (previous) await this.#startRuntime(previous).catch(() => undefined);
|
|
317
324
|
record.state = 'cancelled';
|