@xmanrui/dsh-im 0.10.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 +36 -6
- package/README.md +36 -6
- package/lib/client.js +377 -27
- package/lib/index.js +145 -135
- package/package.json +31 -2
- package/plugin-src/client/channels/dingtalk/api.js +19 -0
- package/plugin-src/client/channels/dingtalk/index.js +67 -10
- package/plugin-src/client/channels/feishu/index.js +35 -5
- package/plugin-src/client/channels/qq/api.js +21 -0
- package/plugin-src/client/channels/qq/index.js +39 -1
- package/plugin-src/client/channels/shared/token-channel.js +29 -3
- package/plugin-src/client/channels/wecom/api.js +11 -0
- package/plugin-src/client/channels/wecom/index.js +71 -1
- package/plugin-src/client/channels/weixin/api.js +11 -0
- package/plugin-src/client/channels/weixin/index.js +40 -4
- package/plugin-src/client/channels/whatsapp/index.js +41 -2
- package/plugin-src/client/i18n.js +13 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +8 -1
- package/plugin-src/host/channels/dingtalk/rpc.mjs +29 -3
- package/plugin-src/host/channels/feishu/production.mjs +8 -1
- package/plugin-src/host/channels/feishu/rpc.mjs +34 -3
- package/plugin-src/host/channels/qq/production.mjs +8 -1
- package/plugin-src/host/channels/qq/rpc.mjs +35 -2
- package/plugin-src/host/channels/shared/production.mjs +8 -1
- package/plugin-src/host/channels/shared/rpc.mjs +25 -1
- package/plugin-src/host/channels/slack/production.mjs +8 -1
- package/plugin-src/host/channels/slack/rpc.mjs +28 -2
- package/plugin-src/host/channels/wecom/production.mjs +8 -1
- package/plugin-src/host/channels/wecom/rpc.mjs +31 -2
- package/plugin-src/host/channels/weixin/production.mjs +8 -1
- package/plugin-src/host/channels/weixin/rpc.mjs +29 -3
- package/plugin-src/host/channels/whatsapp/production.mjs +8 -1
- package/plugin-src/host/channels/whatsapp/rpc.mjs +31 -3
- package/plugin-src/host/harness-session-coordinator.mjs +134 -0
- package/src/channels/dingtalk/dingtalk-bridge.mjs +85 -0
- package/src/channels/dingtalk/dingtalk-controller.mjs +18 -0
- package/src/channels/dingtalk/dingtalk-runtime.mjs +21 -0
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/discord/discord-runtime.mjs +10 -0
- package/src/channels/feishu/bridge.mjs +87 -0
- package/src/channels/feishu/feishu-runtime.mjs +45 -0
- package/src/channels/feishu/message-utils.mjs +94 -7
- package/src/channels/feishu/multi-bot-controller.mjs +21 -0
- package/src/channels/qq/qq-bridge.mjs +91 -0
- package/src/channels/qq/qq-controller.mjs +18 -0
- package/src/channels/qq/qq-runtime.mjs +26 -0
- package/src/channels/shared/bot-workspace-store.mjs +47 -1
- package/src/channels/shared/connection-test.mjs +54 -0
- 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 +342 -35
- 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 +100 -0
- package/src/channels/shared/token-bot-controller.mjs +21 -0
- package/src/channels/shared/workspace-session.mjs +21 -9
- package/src/channels/slack/slack-controller.mjs +19 -0
- package/src/channels/slack/slack-runtime.mjs +10 -0
- package/src/channels/telegram/telegram-runtime.mjs +10 -0
- package/src/channels/wecom/wecom-bridge.mjs +79 -0
- package/src/channels/wecom/wecom-controller.mjs +18 -0
- package/src/channels/wecom/wecom-runtime.mjs +18 -0
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +85 -0
- package/src/channels/weixin/weixin-controller.mjs +18 -0
- package/src/channels/weixin/weixin-runtime.mjs +25 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +20 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +16 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from 'node:fs/promises';
|
|
10
10
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
11
11
|
|
|
12
|
+
import { CONNECTION_TEST_STATE_IDENTITY } from './connection-test.mjs';
|
|
12
13
|
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
13
14
|
|
|
14
15
|
const EMPTY_DOCUMENT = Object.freeze({ version: 1, workspaces: Object.freeze({}) });
|
|
@@ -510,7 +511,9 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
510
511
|
}
|
|
511
512
|
};
|
|
512
513
|
}
|
|
513
|
-
if ((property === 'listWorkspaces'
|
|
514
|
+
if ((property === 'listWorkspaces'
|
|
515
|
+
|| property === 'listWorkspaceSessions'
|
|
516
|
+
|| property === 'listModels')
|
|
514
517
|
&& typeof target[property] === 'function') {
|
|
515
518
|
return async (...args) => {
|
|
516
519
|
if (!isCurrentScope()) {
|
|
@@ -626,6 +629,30 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
626
629
|
sessionGenerations.delete(sessionId);
|
|
627
630
|
const isCurrentSession = () => isCurrentScope()
|
|
628
631
|
&& generation === workspaces.generationFor(botId);
|
|
632
|
+
const invokeCurrentSession = async (method, args, action) => {
|
|
633
|
+
if (!isCurrentSession()) {
|
|
634
|
+
throw workspaceSessionStale(
|
|
635
|
+
`The bot workspace changed before this ${action} started.`,
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
const result = await target[method](sessionId, ...args);
|
|
639
|
+
if (!isCurrentSession()) {
|
|
640
|
+
throw workspaceSessionStale(
|
|
641
|
+
`The bot workspace changed while this ${action} was running.`,
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return result;
|
|
645
|
+
};
|
|
646
|
+
const invokeStartedSessionMutation = async (method, args, action) => {
|
|
647
|
+
if (!isCurrentSession()) {
|
|
648
|
+
throw workspaceSessionStale(
|
|
649
|
+
`The bot workspace changed before this ${action} started.`,
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
// Once an irreversible control mutation has started, preserve its
|
|
653
|
+
// actual outcome even if a workspace switch commits concurrently.
|
|
654
|
+
return target[method](sessionId, ...args);
|
|
655
|
+
};
|
|
629
656
|
return Object.freeze({
|
|
630
657
|
sessionId,
|
|
631
658
|
async sessionExists(...args) {
|
|
@@ -633,6 +660,24 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
633
660
|
const exists = await target.sessionExists(sessionId, ...args);
|
|
634
661
|
return isCurrentSession() && exists;
|
|
635
662
|
},
|
|
663
|
+
models(...args) {
|
|
664
|
+
return invokeCurrentSession('getSessionModels', args, 'model listing');
|
|
665
|
+
},
|
|
666
|
+
selectModel(...args) {
|
|
667
|
+
return invokeCurrentSession('selectSessionModel', args, 'model selection');
|
|
668
|
+
},
|
|
669
|
+
isRunning(...args) {
|
|
670
|
+
return invokeCurrentSession('isSessionRunning', args, 'run-state check');
|
|
671
|
+
},
|
|
672
|
+
hasActiveTurn(...args) {
|
|
673
|
+
return invokeCurrentSession('hasActiveTurn', args, 'turn ownership check');
|
|
674
|
+
},
|
|
675
|
+
stopActiveTurn(...args) {
|
|
676
|
+
return invokeStartedSessionMutation('stopActiveTurn', args, 'turn stop');
|
|
677
|
+
},
|
|
678
|
+
steerActiveTurn(...args) {
|
|
679
|
+
return invokeStartedSessionMutation('steerActiveTurn', args, 'turn steering');
|
|
680
|
+
},
|
|
636
681
|
ask(...args) {
|
|
637
682
|
if (!isCurrentSession()) {
|
|
638
683
|
throw workspaceSessionStale(
|
|
@@ -687,6 +732,7 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
687
732
|
});
|
|
688
733
|
const scopedState = new Proxy(state, {
|
|
689
734
|
get(target, property) {
|
|
735
|
+
if (property === CONNECTION_TEST_STATE_IDENTITY) return target;
|
|
690
736
|
if (property === 'sessionFor') {
|
|
691
737
|
return (key, ...args) => {
|
|
692
738
|
if (!isCurrentScope()) return null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const targets = new WeakMap();
|
|
2
|
+
|
|
3
|
+
export const CONNECTION_TEST_STATE_IDENTITY = Symbol('dsh-im.connection-test-state-identity');
|
|
4
|
+
|
|
5
|
+
function stateIdentity(state) {
|
|
6
|
+
return state?.[CONNECTION_TEST_STATE_IDENTITY] ?? state;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function cleanText(value) {
|
|
10
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function rememberConnectionTestTarget(state, target) {
|
|
14
|
+
if (!state || !target || typeof target !== 'object') return false;
|
|
15
|
+
try {
|
|
16
|
+
targets.set(stateIdentity(state), structuredClone(target));
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function connectionTestTarget(state) {
|
|
24
|
+
const target = state ? targets.get(stateIdentity(state)) : null;
|
|
25
|
+
return target ? structuredClone(target) : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function connectionTestMessage(botName, channelLabel = '机器人') {
|
|
29
|
+
const name = cleanText(botName) ?? channelLabel;
|
|
30
|
+
return `✅ DeepSeek Harness 连接测试成功\n这条消息由插件页面中的“${name}”机器人卡片发出。`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function connectionTestTargetUnavailable(channelLabel = '机器人') {
|
|
34
|
+
const error = new Error(`${channelLabel}尚未收到可用于测试的私聊消息。`);
|
|
35
|
+
error.code = 'test-target-unavailable';
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function sendRememberedConnectionTest({ state, send, text, channelLabel }) {
|
|
40
|
+
const target = connectionTestTarget(state);
|
|
41
|
+
if (!target) throw connectionTestTargetUnavailable(channelLabel);
|
|
42
|
+
await send(target, text);
|
|
43
|
+
return { sent: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function publicConnectionTestResult(error) {
|
|
47
|
+
if (!error) return Object.freeze({ sent: true });
|
|
48
|
+
return Object.freeze({
|
|
49
|
+
sent: false,
|
|
50
|
+
code: error?.code === 'test-target-unavailable'
|
|
51
|
+
? 'test-target-unavailable'
|
|
52
|
+
: 'test-message-failed',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const CONTROL_COMMAND = /^\/(?:stop|steer)(?=$|\s)/iu;
|
|
2
|
+
const STOP_COMMAND = /^\/stop(?=$|\s)/iu;
|
|
3
|
+
const STOP_USAGE = '用法:/stop(不带参数)';
|
|
4
|
+
const STEER_USAGE = '用法:/steer <补充指令>';
|
|
5
|
+
const TEXT_ONLY = '控制命令仅支持纯文字,请移除图片后重试。';
|
|
6
|
+
|
|
7
|
+
function commandResult(message, extra = {}) {
|
|
8
|
+
return { message, ...extra };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function requestOptions(signal) {
|
|
12
|
+
return signal ? { signal } : {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function boundSession(harness, state, key) {
|
|
16
|
+
if (typeof state?.sessionFor !== 'function') return null;
|
|
17
|
+
const sessionId = state.sessionFor(key);
|
|
18
|
+
if (typeof sessionId !== 'string' || !sessionId) return null;
|
|
19
|
+
if (typeof harness?.workspaceSession !== 'function') {
|
|
20
|
+
throw new TypeError('Harness does not support workspace sessions');
|
|
21
|
+
}
|
|
22
|
+
const session = harness.workspaceSession(sessionId);
|
|
23
|
+
if (!session || typeof session !== 'object') {
|
|
24
|
+
throw new TypeError('Harness returned an invalid workspace session');
|
|
25
|
+
}
|
|
26
|
+
return session;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isControlCommand(text) {
|
|
30
|
+
return typeof text === 'string' && CONTROL_COMMAND.test(text.trim());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function runControlCommand(text, harness, state, key, {
|
|
34
|
+
signal,
|
|
35
|
+
hasImages = false,
|
|
36
|
+
pendingInteraction = false,
|
|
37
|
+
control,
|
|
38
|
+
} = {}) {
|
|
39
|
+
if (!isControlCommand(text)) return null;
|
|
40
|
+
const command = text.trim();
|
|
41
|
+
const stop = STOP_COMMAND.test(command);
|
|
42
|
+
|
|
43
|
+
if (hasImages) return commandResult(TEXT_ONLY);
|
|
44
|
+
|
|
45
|
+
if (stop) {
|
|
46
|
+
if (!/^\/stop$/iu.test(command)) return commandResult(STOP_USAGE);
|
|
47
|
+
const session = boundSession(harness, state, key);
|
|
48
|
+
if (!session) return commandResult('当前聊天没有正在运行的任务。');
|
|
49
|
+
if (typeof session.stopActiveTurn !== 'function') {
|
|
50
|
+
throw new TypeError('Harness session does not support stopping active turns');
|
|
51
|
+
}
|
|
52
|
+
const stopped = await session.stopActiveTurn(control, requestOptions(signal));
|
|
53
|
+
return stopped
|
|
54
|
+
? commandResult('已请求停止当前任务。', { stopped: true })
|
|
55
|
+
: commandResult('当前聊天没有正在运行的任务。');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const match = /^\/steer(?:\s+([\s\S]*))?$/iu.exec(command);
|
|
59
|
+
const instruction = match?.[1]?.trim() ?? '';
|
|
60
|
+
if (!instruction) return commandResult(STEER_USAGE);
|
|
61
|
+
if (pendingInteraction) {
|
|
62
|
+
return commandResult([
|
|
63
|
+
'当前任务正在等待你的回答或审批。',
|
|
64
|
+
'',
|
|
65
|
+
'请先处理当前请求,或者发送 /stop 停止任务。',
|
|
66
|
+
].join('\n'));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const session = boundSession(harness, state, key);
|
|
70
|
+
if (!session) {
|
|
71
|
+
return commandResult('当前聊天没有正在运行的任务,请直接发送普通消息。');
|
|
72
|
+
}
|
|
73
|
+
if (typeof session.steerActiveTurn !== 'function') {
|
|
74
|
+
throw new TypeError('Harness session does not support steering active turns');
|
|
75
|
+
}
|
|
76
|
+
const steered = await session.steerActiveTurn(
|
|
77
|
+
instruction,
|
|
78
|
+
control,
|
|
79
|
+
requestOptions(signal),
|
|
80
|
+
);
|
|
81
|
+
return steered
|
|
82
|
+
? commandResult('已提交补充指令,Agent 会在下一步读取。')
|
|
83
|
+
: commandResult('当前聊天没有正在运行的任务,请直接发送普通消息。');
|
|
84
|
+
}
|