@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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
function nonEmptyString(value) {
|
|
2
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function validHarnessQuestion(question) {
|
|
6
|
+
return question && typeof question.id === 'string' && typeof question.question === 'string'
|
|
7
|
+
&& (question.header === undefined || typeof question.header === 'string')
|
|
8
|
+
&& (question.detail === undefined || typeof question.detail === 'string')
|
|
9
|
+
&& (question.multiSelect === undefined || typeof question.multiSelect === 'boolean')
|
|
10
|
+
&& (question.options === undefined || (Array.isArray(question.options)
|
|
11
|
+
&& question.options.every((option) => (
|
|
12
|
+
option && typeof option.label === 'string'
|
|
13
|
+
&& (option.description === undefined || typeof option.description === 'string')
|
|
14
|
+
))));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function harnessQuestionText(question, index, total, { requiresMention = false } = {}) {
|
|
18
|
+
const lines = [];
|
|
19
|
+
const progress = total > 1 ? `(${index + 1}/${total})` : '';
|
|
20
|
+
lines.push(`DeepSeek Harness 需要你补充信息${progress}:`);
|
|
21
|
+
if (nonEmptyString(question.header)) lines.push('', question.header.trim());
|
|
22
|
+
lines.push('', nonEmptyString(question.question) ?? '请输入你的回答。');
|
|
23
|
+
if (nonEmptyString(question.detail)) lines.push('', question.detail.trim());
|
|
24
|
+
|
|
25
|
+
const options = Array.isArray(question.options) ? question.options : [];
|
|
26
|
+
if (options.length > 0) {
|
|
27
|
+
lines.push('');
|
|
28
|
+
options.forEach((option, optionIndex) => {
|
|
29
|
+
const label = typeof option?.label === 'string' ? option.label : '';
|
|
30
|
+
const description = nonEmptyString(option?.description);
|
|
31
|
+
lines.push(`${optionIndex + 1}. ${label}${description ? ` — ${description}` : ''}`);
|
|
32
|
+
});
|
|
33
|
+
lines.push('', question.multiSelect === true
|
|
34
|
+
? '请回复选项序号或文字;多选用逗号分隔,也可补充其他内容。'
|
|
35
|
+
: '请回复一个选项序号或文字,也可直接输入其他答案。');
|
|
36
|
+
} else {
|
|
37
|
+
lines.push('', '请直接回复你的答案。');
|
|
38
|
+
}
|
|
39
|
+
if (requiresMention) lines.push('', '群聊中请 @机器人 后发送答案。');
|
|
40
|
+
return lines.join('\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function optionLabel(token, options) {
|
|
44
|
+
const normalized = token.trim();
|
|
45
|
+
if (!normalized) return null;
|
|
46
|
+
if (/^\d+$/.test(normalized)) {
|
|
47
|
+
const option = options[Number(normalized) - 1];
|
|
48
|
+
return typeof option?.label === 'string' ? option.label : null;
|
|
49
|
+
}
|
|
50
|
+
const exact = options.find((option) => option?.label === normalized);
|
|
51
|
+
return typeof exact?.label === 'string' ? exact.label : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function harnessAnswerForQuestion(question, text) {
|
|
55
|
+
const options = Array.isArray(question.options) ? question.options : [];
|
|
56
|
+
if (options.length === 0) {
|
|
57
|
+
return { id: question.id, selected: [], custom: text };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const wholeLabel = optionLabel(text, options);
|
|
61
|
+
if (question.multiSelect !== true) {
|
|
62
|
+
return wholeLabel
|
|
63
|
+
? { id: question.id, selected: [wholeLabel] }
|
|
64
|
+
: { id: question.id, selected: [], custom: text };
|
|
65
|
+
}
|
|
66
|
+
if (wholeLabel) return { id: question.id, selected: [wholeLabel] };
|
|
67
|
+
|
|
68
|
+
const selected = [];
|
|
69
|
+
const custom = [];
|
|
70
|
+
for (const token of text.split(/[,,、;;\n]+/)) {
|
|
71
|
+
const value = token.trim();
|
|
72
|
+
if (!value) continue;
|
|
73
|
+
const label = optionLabel(value, options);
|
|
74
|
+
if (label) {
|
|
75
|
+
if (!selected.includes(label)) selected.push(label);
|
|
76
|
+
} else {
|
|
77
|
+
custom.push(value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
id: question.id,
|
|
82
|
+
selected,
|
|
83
|
+
...(custom.length > 0 ? { custom: custom.join('、') } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { isAbsolute } from 'node:path';
|
|
2
|
+
|
|
3
|
+
const MAX_SESSION_ID_LENGTH = 256;
|
|
4
|
+
const UNSAFE_SESSION_ID = /[\p{White_Space}\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
|
|
5
|
+
const UNSAFE_WORKSPACE_PATH = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
|
|
6
|
+
|
|
7
|
+
function bindingError(code, message) {
|
|
8
|
+
const error = new Error(message);
|
|
9
|
+
error.code = code;
|
|
10
|
+
return error;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function validatedSessionId(value) {
|
|
14
|
+
if (typeof value !== 'string' || !value || value.length > MAX_SESSION_ID_LENGTH
|
|
15
|
+
|| UNSAFE_SESSION_ID.test(value)) {
|
|
16
|
+
throw bindingError('session-id-invalid', 'A non-empty, safe session id is required');
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sessionWorkspace(sessionId, value) {
|
|
22
|
+
if (!Array.isArray(value?.items) || !Array.isArray(value?.archivedSessionIds)
|
|
23
|
+
|| value.archivedSessionIds.some((id) => typeof id !== 'string' || !id)) {
|
|
24
|
+
throw new Error('Harness returned an invalid response for workspace.list');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const owners = [];
|
|
28
|
+
for (const workspace of value.items) {
|
|
29
|
+
if (typeof workspace?.workspaceId !== 'string' || !workspace.workspaceId
|
|
30
|
+
|| typeof workspace.path !== 'string' || !isAbsolute(workspace.path)
|
|
31
|
+
|| !Array.isArray(workspace.sessionIds)
|
|
32
|
+
|| workspace.sessionIds.some((id) => typeof id !== 'string' || !id)) {
|
|
33
|
+
throw new Error('Harness returned an invalid response for workspace.list');
|
|
34
|
+
}
|
|
35
|
+
for (const accountedId of workspace.sessionIds) {
|
|
36
|
+
if (accountedId === sessionId) owners.push(workspace);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (owners.length === 0) {
|
|
41
|
+
throw bindingError('session-not-registered', 'The session is not registered to a Harness workspace');
|
|
42
|
+
}
|
|
43
|
+
if (owners.length !== 1) {
|
|
44
|
+
throw bindingError(
|
|
45
|
+
'session-workspace-ambiguous',
|
|
46
|
+
'The session is registered to more than one Harness workspace',
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (UNSAFE_WORKSPACE_PATH.test(owners[0].path)) {
|
|
50
|
+
throw new Error('Harness returned an unsafe workspace path for the session');
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
workspace: owners[0],
|
|
54
|
+
archived: value.archivedSessionIds.includes(sessionId),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function sessionSummary(sessionId, value) {
|
|
59
|
+
if (!Array.isArray(value?.items)
|
|
60
|
+
|| value.items.some((item) => typeof item?.sessionId !== 'string' || !item.sessionId)) {
|
|
61
|
+
throw new Error('Harness returned an invalid response for session.list');
|
|
62
|
+
}
|
|
63
|
+
const matches = value.items.filter((item) => item.sessionId === sessionId);
|
|
64
|
+
if (matches.length === 0) {
|
|
65
|
+
throw bindingError('session-summary-unavailable', 'The session is no longer available from Harness');
|
|
66
|
+
}
|
|
67
|
+
if (matches.length !== 1) {
|
|
68
|
+
throw new Error('Harness returned duplicate session summaries for session.list');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const [summary] = matches;
|
|
72
|
+
if (summary.origin === 'subagent') {
|
|
73
|
+
throw bindingError(
|
|
74
|
+
'session-subagent-unsupported',
|
|
75
|
+
'Subagent sessions cannot be adopted as a bot conversation',
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (summary.origin !== undefined) {
|
|
79
|
+
throw new Error('Harness returned an invalid session origin for session.list');
|
|
80
|
+
}
|
|
81
|
+
const title = summary.projections?.values?.title;
|
|
82
|
+
if (title !== undefined && title !== null && typeof title !== 'string') {
|
|
83
|
+
throw new Error('Harness returned an invalid session title for session.list');
|
|
84
|
+
}
|
|
85
|
+
return { title: typeof title === 'string' ? title : null };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function adoptRegisteredWorkspaceSession(client, value, options = {}, timeoutMs = 30_000) {
|
|
89
|
+
const sessionId = validatedSessionId(value);
|
|
90
|
+
await client.ensureRunning(options);
|
|
91
|
+
const workspaceList = await client.rpc('workspace.list', {}, timeoutMs, options);
|
|
92
|
+
const { workspace, archived } = sessionWorkspace(sessionId, workspaceList);
|
|
93
|
+
const summary = sessionSummary(
|
|
94
|
+
sessionId,
|
|
95
|
+
await client.rpc('session.list', {}, timeoutMs, options),
|
|
96
|
+
);
|
|
97
|
+
const adopted = await client.rpc('session.create', {
|
|
98
|
+
workspaceId: workspace.workspaceId,
|
|
99
|
+
sessionId,
|
|
100
|
+
}, timeoutMs, options);
|
|
101
|
+
if (!adopted || adopted.sessionId !== sessionId) {
|
|
102
|
+
throw new Error('Harness returned an invalid response for session.create');
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
sessionId,
|
|
106
|
+
workspace: workspace.path,
|
|
107
|
+
title: summary.title,
|
|
108
|
+
archived,
|
|
109
|
+
};
|
|
110
|
+
}
|