@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,25 @@
|
|
|
1
|
-
import { stat } from 'node:fs/promises';
|
|
1
|
+
import { realpath, stat } from 'node:fs/promises';
|
|
2
2
|
import { isAbsolute, resolve } from 'node:path';
|
|
3
3
|
|
|
4
|
+
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
5
|
+
|
|
4
6
|
const WORKSPACE_COMMAND = /^\/workspace(?:\s+([\s\S]+))?$/i;
|
|
5
7
|
const WORKSPACE_LIST_COMMAND = /^\/workspacelist(?:\s+([\s\S]+))?$/i;
|
|
8
|
+
const SESSION_LIST_COMMAND = /^\/sessionlist(?:\s+([\s\S]+))?$/i;
|
|
9
|
+
const SESSION_BIND_PREFIX = /^\/session(?=$|\s)/i;
|
|
10
|
+
const SESSION_BIND_COMMAND = /^\/session[ \t]+([^\s]+)$/i;
|
|
6
11
|
const MAX_WORKSPACE_PATH_LENGTH = 4_096;
|
|
7
12
|
const MAX_COMMAND_MESSAGE_LENGTH = 1_800;
|
|
13
|
+
const MAX_SESSION_ID_LENGTH = 256;
|
|
14
|
+
const UNSAFE_DISPLAY_TEXT = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
|
|
15
|
+
const UNSAFE_DISPLAY_TEXT_GLOBAL = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]+/gu;
|
|
16
|
+
const SESSION_BIND_USAGE = '用法:/session Session ID';
|
|
17
|
+
const SESSION_LIST_USAGE = [
|
|
18
|
+
'用法:',
|
|
19
|
+
'/sessionlist 列出当前工作区会话',
|
|
20
|
+
'/sessionlist 工作区序号 按 /workspacelist 序号列出会话',
|
|
21
|
+
'/sessionlist 工作区绝对路径 列出指定工作区会话',
|
|
22
|
+
].join('\n');
|
|
8
23
|
|
|
9
24
|
function commandResult(message, messages = [message]) {
|
|
10
25
|
return { handled: true, message, messages };
|
|
@@ -12,20 +27,74 @@ function commandResult(message, messages = [message]) {
|
|
|
12
27
|
|
|
13
28
|
function normalizedWorkspacePath(value) {
|
|
14
29
|
if (typeof value !== 'string' || value.length > MAX_WORKSPACE_PATH_LENGTH
|
|
15
|
-
|| !isAbsolute(value) ||
|
|
30
|
+
|| !isAbsolute(value) || UNSAFE_DISPLAY_TEXT.test(value)) return null;
|
|
16
31
|
return resolve(value);
|
|
17
32
|
}
|
|
18
33
|
|
|
34
|
+
function safeDisplayText(value) {
|
|
35
|
+
if (typeof value !== 'string') return '';
|
|
36
|
+
return value.replace(UNSAFE_DISPLAY_TEXT_GLOBAL, ' ').replace(/\s+/gu, ' ').trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function validSessionId(value) {
|
|
40
|
+
return typeof value === 'string'
|
|
41
|
+
&& value.length > 0
|
|
42
|
+
&& value.length <= MAX_SESSION_ID_LENGTH
|
|
43
|
+
&& !/\p{White_Space}/u.test(value)
|
|
44
|
+
&& !UNSAFE_DISPLAY_TEXT.test(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
19
47
|
async function existingWorkspacePaths(values) {
|
|
20
|
-
const
|
|
21
|
-
|
|
48
|
+
const checked = await Promise.all(values.map(async (value) => {
|
|
49
|
+
const workspace = normalizedWorkspacePath(value);
|
|
50
|
+
if (!workspace) return null;
|
|
22
51
|
try {
|
|
23
|
-
|
|
52
|
+
if (!(await stat(workspace)).isDirectory()) return null;
|
|
53
|
+
return normalizedWorkspacePath(await realpath(workspace));
|
|
24
54
|
} catch {
|
|
25
55
|
return null;
|
|
26
56
|
}
|
|
27
57
|
}));
|
|
28
|
-
return checked.filter(Boolean);
|
|
58
|
+
return [...new Set(checked.filter(Boolean))];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function selectedWorkspacePath(value) {
|
|
62
|
+
if (typeof value !== 'string' || !isAbsolute(value.trim())) {
|
|
63
|
+
return { error: `工作区必须是绝对路径。\n${SESSION_LIST_USAGE}` };
|
|
64
|
+
}
|
|
65
|
+
const workspace = normalizedWorkspacePath(value.trim());
|
|
66
|
+
if (!workspace) {
|
|
67
|
+
return { error: `工作区路径包含不支持的字符或长度超过限制。\n${SESSION_LIST_USAGE}` };
|
|
68
|
+
}
|
|
69
|
+
let info;
|
|
70
|
+
try {
|
|
71
|
+
info = await stat(workspace);
|
|
72
|
+
} catch {
|
|
73
|
+
return { error: `工作区路径不存在。\n${SESSION_LIST_USAGE}` };
|
|
74
|
+
}
|
|
75
|
+
if (!info.isDirectory()) {
|
|
76
|
+
return { error: `工作区路径必须指向一个目录。\n${SESSION_LIST_USAGE}` };
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const canonical = normalizedWorkspacePath(await realpath(workspace));
|
|
80
|
+
return canonical
|
|
81
|
+
? { workspace: canonical }
|
|
82
|
+
: { error: `工作区路径包含不支持的字符或长度超过限制。\n${SESSION_LIST_USAGE}` };
|
|
83
|
+
} catch {
|
|
84
|
+
return { error: `工作区路径不存在。\n${SESSION_LIST_USAGE}` };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function workspacePathSnapshot(harness) {
|
|
89
|
+
const listed = await harness.listWorkspaces();
|
|
90
|
+
const currentValue = typeof harness?.currentWorkspace === 'function'
|
|
91
|
+
? harness.currentWorkspace()
|
|
92
|
+
: null;
|
|
93
|
+
const [current] = currentValue ? await existingWorkspacePaths([currentValue]) : [];
|
|
94
|
+
const registered = await existingWorkspacePaths(Array.isArray(listed) ? listed : []);
|
|
95
|
+
const paths = [...new Set([...(current ? [current] : []), ...registered])];
|
|
96
|
+
harness.assertWorkspaceScope?.();
|
|
97
|
+
return { current: current ?? null, paths };
|
|
29
98
|
}
|
|
30
99
|
|
|
31
100
|
export function splitWorkspaceCommandMessage(message) {
|
|
@@ -56,15 +125,7 @@ async function runWorkspaceListCommand(match, harness) {
|
|
|
56
125
|
return commandResult('当前机器人暂不支持列出工作区。');
|
|
57
126
|
}
|
|
58
127
|
try {
|
|
59
|
-
const
|
|
60
|
-
const current = typeof harness.currentWorkspace === 'function'
|
|
61
|
-
? normalizedWorkspacePath(harness.currentWorkspace())
|
|
62
|
-
: null;
|
|
63
|
-
const paths = await existingWorkspacePaths([
|
|
64
|
-
...(current ? [current] : []),
|
|
65
|
-
...(Array.isArray(listed) ? listed : []),
|
|
66
|
-
]);
|
|
67
|
-
harness.assertWorkspaceScope?.();
|
|
128
|
+
const { current, paths } = await workspacePathSnapshot(harness);
|
|
68
129
|
if (paths.length === 0) {
|
|
69
130
|
return commandResult('当前 Harness Host 上没有仍然存在的已登记工作区。');
|
|
70
131
|
}
|
|
@@ -75,6 +136,7 @@ async function runWorkspaceListCommand(match, harness) {
|
|
|
75
136
|
)),
|
|
76
137
|
'',
|
|
77
138
|
'切换用法:/workspace 工作区绝对路径',
|
|
139
|
+
'查看会话:/sessionlist 工作区序号或绝对路径',
|
|
78
140
|
];
|
|
79
141
|
const message = lines.join('\n');
|
|
80
142
|
return commandResult(message, splitWorkspaceCommandMessage(message));
|
|
@@ -86,9 +148,143 @@ async function runWorkspaceListCommand(match, harness) {
|
|
|
86
148
|
}
|
|
87
149
|
}
|
|
88
150
|
|
|
89
|
-
|
|
151
|
+
async function resolveSessionListWorkspace(selector, harness) {
|
|
152
|
+
if (!selector) {
|
|
153
|
+
if (typeof harness?.currentWorkspace !== 'function') {
|
|
154
|
+
return { error: '当前机器人没有可用的工作区。' };
|
|
155
|
+
}
|
|
156
|
+
const selected = await selectedWorkspacePath(harness.currentWorkspace());
|
|
157
|
+
harness.assertWorkspaceScope?.();
|
|
158
|
+
return selected;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (/^\d+$/u.test(selector)) {
|
|
162
|
+
if (typeof harness?.listWorkspaces !== 'function') {
|
|
163
|
+
return { error: '当前机器人暂不支持按序号选择工作区。' };
|
|
164
|
+
}
|
|
165
|
+
const { paths } = await workspacePathSnapshot(harness);
|
|
166
|
+
const position = Number(selector);
|
|
167
|
+
if (!Number.isSafeInteger(position) || position < 1 || position > paths.length) {
|
|
168
|
+
return { error: '工作区序号不存在,请先执行 /workspacelist。' };
|
|
169
|
+
}
|
|
170
|
+
return { workspace: paths[position - 1] };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const selected = await selectedWorkspacePath(selector);
|
|
174
|
+
harness.assertWorkspaceScope?.();
|
|
175
|
+
return selected;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function sessionListMessage(workspace, sessions) {
|
|
179
|
+
const rows = sessions.map((session) => {
|
|
180
|
+
const sessionId = safeDisplayText(session?.sessionId);
|
|
181
|
+
if (!sessionId) throw new TypeError('Harness returned an invalid session id');
|
|
182
|
+
const title = session?.summaryAvailable === false
|
|
183
|
+
? '标题暂不可用'
|
|
184
|
+
: safeDisplayText(session?.title) || '暂无标题';
|
|
185
|
+
return `${title}${session?.archived === true ? '(已归档)' : ''}\n ID: ${sessionId}`;
|
|
186
|
+
});
|
|
187
|
+
if (rows.length === 0) return `工作区:${workspace}\n该工作区暂无会话。`;
|
|
188
|
+
return [
|
|
189
|
+
`工作区:${workspace}`,
|
|
190
|
+
`会话(${rows.length}):`,
|
|
191
|
+
'',
|
|
192
|
+
...rows.map((row, index) => `${index + 1}. ${row}`),
|
|
193
|
+
'',
|
|
194
|
+
'绑定用法:/session Session ID',
|
|
195
|
+
].join('\n');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function runSessionListCommand(match, harness) {
|
|
199
|
+
if (typeof harness?.listWorkspaceSessions !== 'function') {
|
|
200
|
+
return commandResult('当前机器人暂不支持列出工作区会话。');
|
|
201
|
+
}
|
|
202
|
+
const selector = match[1]?.trim() ?? '';
|
|
203
|
+
try {
|
|
204
|
+
const resolved = await resolveSessionListWorkspace(selector, harness);
|
|
205
|
+
if (resolved.error) return commandResult(resolved.error);
|
|
206
|
+
const listed = await harness.listWorkspaceSessions(resolved.workspace);
|
|
207
|
+
if (!listed || !Array.isArray(listed.sessions)) {
|
|
208
|
+
throw new TypeError('Harness returned an invalid workspace session list');
|
|
209
|
+
}
|
|
210
|
+
harness.assertWorkspaceScope?.();
|
|
211
|
+
const workspace = normalizedWorkspacePath(listed.workspace) ?? resolved.workspace;
|
|
212
|
+
const message = sessionListMessage(workspace, listed.sessions);
|
|
213
|
+
return commandResult(message, splitWorkspaceCommandMessage(message));
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error?.code === 'workspace-bot-not-found') {
|
|
216
|
+
return commandResult('机器人正在移除或已重新接入,无法列出原会话的工作区会话。');
|
|
217
|
+
}
|
|
218
|
+
return commandResult('暂时无法获取工作区会话列表,请稍后重试。');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function sessionBindErrorMessage(error) {
|
|
223
|
+
if (error?.code === 'session-id-invalid') {
|
|
224
|
+
return `Session ID 格式无效。\n${SESSION_BIND_USAGE}`;
|
|
225
|
+
}
|
|
226
|
+
if (['session-not-registered', 'session-not-found'].includes(error?.code)) {
|
|
227
|
+
return '未找到该会话,请先执行 /sessionlist 确认 Session ID。';
|
|
228
|
+
}
|
|
229
|
+
if (error?.code === 'session-subagent-unsupported') {
|
|
230
|
+
return '子代理会话不能绑定到机器人对话,请选择普通会话。';
|
|
231
|
+
}
|
|
232
|
+
if (error?.code === 'session-workspace-ambiguous') {
|
|
233
|
+
return '该会话的工作区归属不明确,暂时无法绑定。';
|
|
234
|
+
}
|
|
235
|
+
if (error?.code === 'session-summary-unavailable') {
|
|
236
|
+
return '暂时无法读取该会话的信息,请稍后重试。';
|
|
237
|
+
}
|
|
238
|
+
if (error?.code === 'workspace-bot-not-found') {
|
|
239
|
+
return '机器人正在移除或已重新接入,无法绑定原对话的会话。';
|
|
240
|
+
}
|
|
241
|
+
if ([WORKSPACE_SESSION_STALE, 'agent-busy', 'session-conflict', 'workspace-conflict']
|
|
242
|
+
.includes(error?.code)) {
|
|
243
|
+
return '工作区或会话状态已发生变化,请重试。';
|
|
244
|
+
}
|
|
245
|
+
return '暂时无法绑定会话,请稍后重试。';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function runSessionBindCommand(command, harness, conversationKey) {
|
|
249
|
+
const match = SESSION_BIND_COMMAND.exec(command);
|
|
250
|
+
const sessionId = match?.[1];
|
|
251
|
+
if (!validSessionId(sessionId)) return commandResult(SESSION_BIND_USAGE);
|
|
252
|
+
if (typeof harness?.bindWorkspaceSession !== 'function') {
|
|
253
|
+
return commandResult('当前机器人暂不支持绑定已有会话。');
|
|
254
|
+
}
|
|
255
|
+
if (typeof conversationKey !== 'string' || !conversationKey) {
|
|
256
|
+
return commandResult('当前消息缺少可绑定的会话上下文。');
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const bound = await harness.bindWorkspaceSession(conversationKey, sessionId);
|
|
260
|
+
harness.assertWorkspaceScope?.();
|
|
261
|
+
const workspace = normalizedWorkspacePath(bound?.workspace);
|
|
262
|
+
const boundSessionId = safeDisplayText(bound?.sessionId);
|
|
263
|
+
if (!workspace || !boundSessionId) {
|
|
264
|
+
throw new TypeError('Harness returned an invalid bound session');
|
|
265
|
+
}
|
|
266
|
+
const title = safeDisplayText(bound?.title) || '暂无标题';
|
|
267
|
+
const message = [
|
|
268
|
+
'当前聊天已绑定会话:',
|
|
269
|
+
`工作区:${workspace}`,
|
|
270
|
+
`标题:${title}`,
|
|
271
|
+
`ID:${boundSessionId}`,
|
|
272
|
+
`归档:${bound?.archived === true ? '是' : '否'}`,
|
|
273
|
+
].join('\n');
|
|
274
|
+
return commandResult(message, splitWorkspaceCommandMessage(message));
|
|
275
|
+
} catch (error) {
|
|
276
|
+
return commandResult(sessionBindErrorMessage(error));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function runWorkspaceCommand(text, harness, conversationKey) {
|
|
90
281
|
if (typeof text !== 'string') return null;
|
|
91
282
|
const command = text.trim();
|
|
283
|
+
if (SESSION_BIND_PREFIX.test(command)) {
|
|
284
|
+
return runSessionBindCommand(command, harness, conversationKey);
|
|
285
|
+
}
|
|
286
|
+
const sessionListMatch = SESSION_LIST_COMMAND.exec(command);
|
|
287
|
+
if (sessionListMatch) return runSessionListCommand(sessionListMatch, harness);
|
|
92
288
|
const listMatch = WORKSPACE_LIST_COMMAND.exec(command);
|
|
93
289
|
if (listMatch) return runWorkspaceListCommand(listMatch, harness);
|
|
94
290
|
const match = WORKSPACE_COMMAND.exec(command);
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
export const WORKSPACE_SESSION_STALE = 'workspace-session-stale';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function workspaceSession(harness, sessionId) {
|
|
4
|
+
if (typeof harness.workspaceSession === 'function') {
|
|
5
|
+
return harness.workspaceSession(sessionId);
|
|
6
|
+
}
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
sessionId,
|
|
9
|
+
sessionExists: (...args) => harness.sessionExists(sessionId, ...args),
|
|
10
|
+
ask: (...args) => harness.ask(sessionId, ...args),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function sessionExists(session, options) {
|
|
4
15
|
return options === undefined
|
|
5
|
-
?
|
|
6
|
-
:
|
|
16
|
+
? session.sessionExists()
|
|
17
|
+
: session.sessionExists(options);
|
|
7
18
|
}
|
|
8
19
|
|
|
9
20
|
async function createSession(harness, options) {
|
|
@@ -27,15 +38,17 @@ export async function askInWorkspaceSession({
|
|
|
27
38
|
askOptions,
|
|
28
39
|
}) {
|
|
29
40
|
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
41
|
try {
|
|
42
|
+
let sessionId = state.sessionFor(key);
|
|
43
|
+
let session = sessionId ? workspaceSession(harness, sessionId) : null;
|
|
44
|
+
if (!session || !(await sessionExists(session, existsOptions))) {
|
|
45
|
+
sessionId = await createSession(harness, createOptions);
|
|
46
|
+
if (await state.setSession(key, sessionId) === false) continue;
|
|
47
|
+
session = workspaceSession(harness, sessionId);
|
|
48
|
+
}
|
|
36
49
|
return {
|
|
37
50
|
sessionId,
|
|
38
|
-
answer: await
|
|
51
|
+
answer: await session.ask(text, askOptions),
|
|
39
52
|
};
|
|
40
53
|
} catch (error) {
|
|
41
54
|
if (error?.code !== WORKSPACE_SESSION_STALE) throw error;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
import { HarnessClient } from '../
|
|
1
|
+
import { HarnessClient } from '../shared/harness-client.mjs';
|
|
2
2
|
|
|
3
|
-
export class SlackHarnessClient extends HarnessClient {
|
|
3
|
+
export class SlackHarnessClient extends HarnessClient {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super({
|
|
6
|
+
...options,
|
|
7
|
+
rpcIdPrefix: 'slack',
|
|
8
|
+
logPrefix: 'dsh-slack',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -309,6 +309,7 @@ export class SlackRuntime {
|
|
|
309
309
|
status: this.#status,
|
|
310
310
|
logger: this.#logger,
|
|
311
311
|
replyTimeoutMs: this.#replyTimeoutMs,
|
|
312
|
+
signal: controller.signal,
|
|
312
313
|
});
|
|
313
314
|
let timer;
|
|
314
315
|
try {
|
|
@@ -389,7 +390,16 @@ export class SlackRuntime {
|
|
|
389
390
|
if (this.#appId && packet.payload.api_app_id
|
|
390
391
|
&& packet.payload.api_app_id !== this.#appId) return;
|
|
391
392
|
const message = normalizeSlackEvent(packet.payload, this.#config.platformId.split(':')[1]);
|
|
392
|
-
|
|
393
|
+
const bridge = this.#bridge;
|
|
394
|
+
if (message && bridge) {
|
|
395
|
+
void bridge.accept(message).catch((error) => {
|
|
396
|
+
if (generation !== this.#generation || this.#stopped) return;
|
|
397
|
+
this.#logger.error?.(
|
|
398
|
+
`[dsh-im:slack] bot ${this.#config.botId} message handling failed:`,
|
|
399
|
+
error,
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
393
403
|
});
|
|
394
404
|
|
|
395
405
|
addSocketListener(socket, 'close', (event = {}) => {
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
import { HarnessClient } from '../
|
|
1
|
+
import { HarnessClient } from '../shared/harness-client.mjs';
|
|
2
2
|
|
|
3
|
-
export class TelegramHarnessClient extends HarnessClient {
|
|
3
|
+
export class TelegramHarnessClient extends HarnessClient {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super({
|
|
6
|
+
...options,
|
|
7
|
+
rpcIdPrefix: 'telegram',
|
|
8
|
+
logPrefix: 'dsh-telegram',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -34,19 +34,21 @@ export function normalizeTelegramUpdate(update, { botId, username }) {
|
|
|
34
34
|
const addressed = direct
|
|
35
35
|
|| String(message.reply_to_message?.from?.id ?? '') === String(botId)
|
|
36
36
|
|| mentionedUsername(message, username);
|
|
37
|
+
const messageThreadId = Number.isSafeInteger(message.message_thread_id)
|
|
38
|
+
? message.message_thread_id : undefined;
|
|
37
39
|
return {
|
|
38
40
|
messageId: String(update.update_id),
|
|
39
41
|
senderId: String(senderId),
|
|
40
42
|
senderIsBot: message.from?.is_bot === true,
|
|
41
43
|
kind: direct ? 'direct' : 'group',
|
|
42
|
-
conversationId:
|
|
44
|
+
conversationId: messageThreadId === undefined
|
|
45
|
+
? String(chatId) : `${chatId}:${messageThreadId}`,
|
|
43
46
|
content: withoutBotMention(message.text ?? message.caption ?? '', username),
|
|
44
47
|
addressed,
|
|
45
48
|
replyTarget: {
|
|
46
49
|
chatId,
|
|
47
50
|
replyToMessageId: messageId,
|
|
48
|
-
messageThreadId
|
|
49
|
-
? message.message_thread_id : undefined,
|
|
51
|
+
messageThreadId,
|
|
50
52
|
},
|
|
51
53
|
};
|
|
52
54
|
}
|
|
@@ -206,6 +208,7 @@ export class TelegramRuntime {
|
|
|
206
208
|
status: this.#status,
|
|
207
209
|
logger: this.#logger,
|
|
208
210
|
replyTimeoutMs: this.#replyTimeoutMs,
|
|
211
|
+
signal: controller.signal,
|
|
209
212
|
});
|
|
210
213
|
|
|
211
214
|
let cursor = this.#state.cursor();
|
|
@@ -248,7 +251,15 @@ export class TelegramRuntime {
|
|
|
248
251
|
botId: this.#config.platformId,
|
|
249
252
|
username: this.#config.username,
|
|
250
253
|
});
|
|
251
|
-
if (message)
|
|
254
|
+
if (message) {
|
|
255
|
+
void this.#bridge.accept(message).catch((error) => {
|
|
256
|
+
if (signal.aborted) return;
|
|
257
|
+
this.#logger.error?.(
|
|
258
|
+
`[dsh-im:telegram] bot ${this.#config.botId} message handling failed:`,
|
|
259
|
+
error,
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
252
263
|
cursor = update.update_id + 1;
|
|
253
264
|
await this.#state.setCursor(cursor);
|
|
254
265
|
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
import { HarnessClient } from '../
|
|
1
|
+
import { HarnessClient } from '../shared/harness-client.mjs';
|
|
2
2
|
|
|
3
|
-
export class WecomHarnessClient extends HarnessClient {
|
|
3
|
+
export class WecomHarnessClient extends HarnessClient {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super({
|
|
6
|
+
...options,
|
|
7
|
+
rpcIdPrefix: 'wecom',
|
|
8
|
+
logPrefix: 'dsh-wecom',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|