@xmanrui/dsh-im 0.6.0 → 0.7.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/README.md +22 -4
- package/lib/client.js +649 -357
- package/lib/index.js +120 -111
- package/package.json +1 -1
- package/plugin-src/client/build.mjs +1 -1
- package/plugin-src/client/i18n.js +14 -0
- package/plugin-src/client/index.js +11 -3
- package/plugin-src/client/styles.js +49 -8
- package/plugin-src/client/workspace-directory-picker.js +230 -0
- package/plugin-src/client/workspace-editor.js +38 -65
- package/scripts/verify-package.mjs +5 -0
- package/src/channels/dingtalk/dingtalk-bridge.mjs +363 -9
- package/src/channels/dingtalk/harness-client.mjs +16 -310
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/discord/discord-runtime.mjs +11 -1
- package/src/channels/discord/harness-client.mjs +10 -2
- package/src/channels/feishu/bridge.mjs +525 -51
- package/src/channels/feishu/feishu-runtime.mjs +41 -1
- package/src/channels/feishu/harness-client.mjs +16 -279
- package/src/channels/qq/harness-client.mjs +10 -2
- package/src/channels/qq/qq-bridge.mjs +383 -29
- package/src/channels/qq/qq-runtime.mjs +14 -3
- package/src/channels/shared/bot-workspace-store.mjs +185 -4
- package/src/channels/shared/harness-client.mjs +825 -0
- package/src/channels/shared/harness-question.mjs +85 -0
- package/src/channels/shared/harness-session-binding.mjs +110 -0
- package/src/channels/shared/text-harness-bridge.mjs +439 -25
- package/src/channels/shared/workspace-command.mjs +212 -16
- package/src/channels/shared/workspace-session.mjs +22 -9
- package/src/channels/slack/harness-client.mjs +10 -2
- package/src/channels/slack/slack-runtime.mjs +11 -1
- package/src/channels/telegram/harness-client.mjs +10 -2
- package/src/channels/telegram/telegram-runtime.mjs +15 -4
- package/src/channels/wecom/harness-client.mjs +10 -2
- package/src/channels/wecom/wecom-bridge.mjs +389 -15
- package/src/channels/wecom/wecom-runtime.mjs +6 -0
- package/src/channels/weixin/harness-client.mjs +16 -270
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +406 -24
- package/src/channels/weixin/weixin-runtime.mjs +56 -7
- package/src/channels/whatsapp/harness-client.mjs +10 -2
- package/src/channels/whatsapp/whatsapp-runtime.mjs +1 -0
|
@@ -1,10 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
mkdir,
|
|
3
|
+
readFile,
|
|
4
|
+
realpath,
|
|
5
|
+
rename,
|
|
6
|
+
stat,
|
|
7
|
+
unlink,
|
|
8
|
+
writeFile,
|
|
9
|
+
} from 'node:fs/promises';
|
|
2
10
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
3
11
|
|
|
4
12
|
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
5
13
|
|
|
6
14
|
const EMPTY_DOCUMENT = Object.freeze({ version: 1, workspaces: Object.freeze({}) });
|
|
7
15
|
|
|
16
|
+
function workspaceSessionStale(message) {
|
|
17
|
+
const error = new Error(message);
|
|
18
|
+
error.code = WORKSPACE_SESSION_STALE;
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function canonicalWorkspacePath(value) {
|
|
23
|
+
return resolve(await realpath(value));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function sameWorkspacePath(left, right) {
|
|
27
|
+
if (left === right) return true;
|
|
28
|
+
try {
|
|
29
|
+
return await canonicalWorkspacePath(left) === await canonicalWorkspacePath(right);
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
8
35
|
function botIdOf(value) {
|
|
9
36
|
if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(value)) {
|
|
10
37
|
throw new TypeError('Invalid bot id');
|
|
@@ -179,6 +206,72 @@ export class BotWorkspaceStore {
|
|
|
179
206
|
});
|
|
180
207
|
}
|
|
181
208
|
|
|
209
|
+
async bindWorkspaceSession(botId, value, {
|
|
210
|
+
conversationKey,
|
|
211
|
+
sessionId,
|
|
212
|
+
clearSessions,
|
|
213
|
+
setSession,
|
|
214
|
+
incarnation,
|
|
215
|
+
expectedGeneration,
|
|
216
|
+
} = {}) {
|
|
217
|
+
const id = botIdOf(botId);
|
|
218
|
+
if (typeof conversationKey !== 'string' || !conversationKey
|
|
219
|
+
|| typeof sessionId !== 'string' || !sessionId) {
|
|
220
|
+
throw new TypeError('conversationKey and sessionId are required');
|
|
221
|
+
}
|
|
222
|
+
if (typeof clearSessions !== 'function' || typeof setSession !== 'function') {
|
|
223
|
+
throw new TypeError('session state callbacks are required');
|
|
224
|
+
}
|
|
225
|
+
if (!this.has(id)
|
|
226
|
+
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
|
|
227
|
+
const error = new Error('找不到要修改的机器人。');
|
|
228
|
+
error.code = 'workspace-bot-not-found';
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
const workspace = await canonicalWorkspacePath(await validateWorkspacePath(value));
|
|
232
|
+
return this.#enqueue(id, async () => {
|
|
233
|
+
if (!this.has(id)
|
|
234
|
+
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
|
|
235
|
+
const error = new Error('找不到要修改的机器人。');
|
|
236
|
+
error.code = 'workspace-bot-not-found';
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
if (expectedGeneration !== undefined
|
|
240
|
+
&& expectedGeneration !== this.generationFor(id)) {
|
|
241
|
+
throw workspaceSessionStale(
|
|
242
|
+
'The bot workspace changed before the session binding could be committed.',
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!(await sameWorkspacePath(workspace, this.workspaceFor(id)))) {
|
|
247
|
+
const previous = this.#workspaces[id];
|
|
248
|
+
// Fence every session resolved before this transition, then remove
|
|
249
|
+
// the old workspace mappings before publishing the new workspace.
|
|
250
|
+
this.#generations.set(id, this.#freshGeneration());
|
|
251
|
+
await clearSessions();
|
|
252
|
+
this.#workspaces[id] = workspace;
|
|
253
|
+
try {
|
|
254
|
+
await this.#persist();
|
|
255
|
+
} catch (error) {
|
|
256
|
+
// Session mappings stay cleared and the advanced generation stays
|
|
257
|
+
// fenced. Restoring either could pair an old session with a state
|
|
258
|
+
// transition whose durable outcome is unknown.
|
|
259
|
+
this.#workspaces[id] = previous;
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// This write remains inside the same bot transition as the workspace
|
|
265
|
+
// mutation, so another switch or bind cannot interleave between them.
|
|
266
|
+
await setSession(conversationKey, sessionId);
|
|
267
|
+
return {
|
|
268
|
+
workspace,
|
|
269
|
+
sessionId,
|
|
270
|
+
generation: this.#generations.get(id),
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
182
275
|
async invalidateSessions(botId, { clearSessions } = {}) {
|
|
183
276
|
const id = botIdOf(botId);
|
|
184
277
|
return this.#enqueue(id, async () => {
|
|
@@ -417,20 +510,21 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
417
510
|
}
|
|
418
511
|
};
|
|
419
512
|
}
|
|
420
|
-
if (property === 'listWorkspaces'
|
|
513
|
+
if ((property === 'listWorkspaces' || property === 'listWorkspaceSessions')
|
|
514
|
+
&& typeof target[property] === 'function') {
|
|
421
515
|
return async (...args) => {
|
|
422
516
|
if (!isCurrentScope()) {
|
|
423
517
|
const error = new Error('找不到要修改的机器人。');
|
|
424
518
|
error.code = 'workspace-bot-not-found';
|
|
425
519
|
throw error;
|
|
426
520
|
}
|
|
427
|
-
const
|
|
521
|
+
const result = await target[property](...args);
|
|
428
522
|
if (!isCurrentScope()) {
|
|
429
523
|
const error = new Error('找不到要修改的机器人。');
|
|
430
524
|
error.code = 'workspace-bot-not-found';
|
|
431
525
|
throw error;
|
|
432
526
|
}
|
|
433
|
-
return
|
|
527
|
+
return result;
|
|
434
528
|
};
|
|
435
529
|
}
|
|
436
530
|
if (property === 'switchWorkspace') {
|
|
@@ -446,6 +540,62 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
446
540
|
});
|
|
447
541
|
};
|
|
448
542
|
}
|
|
543
|
+
if (property === 'bindWorkspaceSession') {
|
|
544
|
+
return async (conversationKey, sessionId) => {
|
|
545
|
+
if (typeof conversationKey !== 'string' || !conversationKey
|
|
546
|
+
|| typeof sessionId !== 'string' || !sessionId) {
|
|
547
|
+
throw new TypeError('conversationKey and sessionId are required');
|
|
548
|
+
}
|
|
549
|
+
if (!isCurrentScope()) {
|
|
550
|
+
const error = new Error('找不到要修改的机器人。');
|
|
551
|
+
error.code = 'workspace-bot-not-found';
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
if (typeof target.adoptWorkspaceSession !== 'function') {
|
|
555
|
+
throw new TypeError('Harness does not support adopting workspace sessions');
|
|
556
|
+
}
|
|
557
|
+
const expectedGeneration = workspaces.generationFor(botId);
|
|
558
|
+
const adopted = await target.adoptWorkspaceSession(sessionId);
|
|
559
|
+
if (!isCurrentScope()) {
|
|
560
|
+
const error = new Error('找不到要修改的机器人。');
|
|
561
|
+
error.code = 'workspace-bot-not-found';
|
|
562
|
+
throw error;
|
|
563
|
+
}
|
|
564
|
+
if (expectedGeneration !== workspaces.generationFor(botId)) {
|
|
565
|
+
throw workspaceSessionStale(
|
|
566
|
+
'The bot workspace changed while the session was being adopted.',
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
if (!adopted || typeof adopted !== 'object'
|
|
570
|
+
|| adopted.sessionId !== sessionId || typeof adopted.workspace !== 'string') {
|
|
571
|
+
throw new TypeError('Harness returned an invalid adopted workspace session');
|
|
572
|
+
}
|
|
573
|
+
const bound = await workspaces.bindWorkspaceSession(botId, adopted.workspace, {
|
|
574
|
+
conversationKey,
|
|
575
|
+
sessionId,
|
|
576
|
+
clearSessions: () => state.clearSessions(),
|
|
577
|
+
setSession: (key, selectedSessionId) => state.setSession(key, selectedSessionId),
|
|
578
|
+
incarnation,
|
|
579
|
+
expectedGeneration,
|
|
580
|
+
});
|
|
581
|
+
if (!isCurrentScope()) {
|
|
582
|
+
const error = new Error('找不到要修改的机器人。');
|
|
583
|
+
error.code = 'workspace-bot-not-found';
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
586
|
+
if (bound.generation !== workspaces.generationFor(botId)) {
|
|
587
|
+
throw workspaceSessionStale(
|
|
588
|
+
'The bot workspace changed before the session binding completed.',
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
sessionGenerations.set(sessionId, bound.generation);
|
|
592
|
+
return {
|
|
593
|
+
...adopted,
|
|
594
|
+
workspace: bound.workspace,
|
|
595
|
+
sessionId: bound.sessionId,
|
|
596
|
+
};
|
|
597
|
+
};
|
|
598
|
+
}
|
|
449
599
|
if (property === 'createSession') {
|
|
450
600
|
return async (options = {}) => {
|
|
451
601
|
await workspaces.whenBotIdle(botId);
|
|
@@ -463,6 +613,37 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
463
613
|
return sessionId;
|
|
464
614
|
};
|
|
465
615
|
}
|
|
616
|
+
if (property === 'workspaceSession') {
|
|
617
|
+
return (sessionId) => {
|
|
618
|
+
if (typeof sessionId !== 'string' || !sessionId) {
|
|
619
|
+
throw new TypeError('sessionId is required');
|
|
620
|
+
}
|
|
621
|
+
const generation = sessionGenerations.get(sessionId)
|
|
622
|
+
?? workspaces.generationFor(botId);
|
|
623
|
+
// Transfer the mutable provenance entry into this immutable handle.
|
|
624
|
+
// A later handle for the same id captures its own generation instead
|
|
625
|
+
// of sharing deletion or rebinding state with this call.
|
|
626
|
+
sessionGenerations.delete(sessionId);
|
|
627
|
+
const isCurrentSession = () => isCurrentScope()
|
|
628
|
+
&& generation === workspaces.generationFor(botId);
|
|
629
|
+
return Object.freeze({
|
|
630
|
+
sessionId,
|
|
631
|
+
async sessionExists(...args) {
|
|
632
|
+
if (!isCurrentSession()) return false;
|
|
633
|
+
const exists = await target.sessionExists(sessionId, ...args);
|
|
634
|
+
return isCurrentSession() && exists;
|
|
635
|
+
},
|
|
636
|
+
ask(...args) {
|
|
637
|
+
if (!isCurrentSession()) {
|
|
638
|
+
throw workspaceSessionStale(
|
|
639
|
+
'The bot workspace changed before this prompt started.',
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
return target.ask(sessionId, ...args);
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
};
|
|
646
|
+
}
|
|
466
647
|
if (property === 'sessionExists') {
|
|
467
648
|
return (sessionId, ...args) => {
|
|
468
649
|
if (!isCurrentScope()) return false;
|