@xmanrui/dsh-im 4.7.0 → 4.9.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 +8 -79
- package/README.md +8 -79
- package/lib/client.js +46 -8
- package/lib/index.js +221 -217
- package/package.json +5 -1
- package/plugin-src/client/context-enhancement.js +14 -0
- package/plugin-src/client/i18n.js +9 -1
- package/plugin-src/client/styles.js +3 -2
- package/plugin-src/client/workspace-directory-picker.js +14 -3
- package/src/channels/dingtalk/dingtalk-bridge.mjs +2 -0
- package/src/channels/feishu/bridge.mjs +176 -20
- package/src/channels/feishu/feishu-cards.mjs +69 -0
- package/src/channels/feishu/feishu-runtime.mjs +5 -0
- package/src/channels/feishu/slash-command-registry.mjs +3 -0
- package/src/channels/qq/qq-bridge.mjs +2 -0
- package/src/channels/shared/context-enhancement.mjs +5 -4
- package/src/channels/shared/harness-approval.mjs +24 -1
- package/src/channels/shared/harness-client.mjs +45 -16
- package/src/channels/shared/i18n-en/feishu.mjs +11 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
- package/src/channels/shared/semantic/reply-reference.mjs +5 -11
- package/src/channels/shared/text-harness-bridge.mjs +6 -2
- package/src/channels/shared/workspace-command.mjs +2 -2
- package/src/channels/slack/slack-runtime.mjs +4 -0
- package/src/channels/telegram/telegram-runtime.mjs +8 -9
- package/src/channels/wecom/wecom-bridge.mjs +2 -0
- package/src/channels/weixin/weixin-bridge.mjs +2 -0
|
@@ -245,6 +245,10 @@ export class HarnessApprovalQueue {
|
|
|
245
245
|
await this.#rejectInteraction(interaction, payload);
|
|
246
246
|
return true;
|
|
247
247
|
}
|
|
248
|
+
// Optional channel-provided renderer. When present, the approval is shown
|
|
249
|
+
// as an interactive card (e.g. Feishu approve/reject buttons) instead of
|
|
250
|
+
// plain text. Channels that don't provide one keep the text-reply path.
|
|
251
|
+
const render = typeof context?.render === 'function' ? context.render : null;
|
|
248
252
|
|
|
249
253
|
const text = harnessApprovalText(payload, {
|
|
250
254
|
toolCall: interaction.toolCall,
|
|
@@ -267,6 +271,7 @@ export class HarnessApprovalQueue {
|
|
|
267
271
|
actor,
|
|
268
272
|
requiresMention: context.requiresMention === true,
|
|
269
273
|
send,
|
|
274
|
+
render,
|
|
270
275
|
text,
|
|
271
276
|
presented: false,
|
|
272
277
|
presentationTask: null,
|
|
@@ -287,6 +292,20 @@ export class HarnessApprovalQueue {
|
|
|
287
292
|
return true;
|
|
288
293
|
}
|
|
289
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Submit an approval decision by id, as triggered by a channel card button
|
|
297
|
+
* (e.g. Feishu approve/reject). Returns false when no matching pending
|
|
298
|
+
* approval is found. Callers may pass the acting user to enforce that only
|
|
299
|
+
* the originating actor can decide.
|
|
300
|
+
*/
|
|
301
|
+
async submitByApprovalId(approvalId, outcome, { actor } = {}) {
|
|
302
|
+
const pending = this.#byId.get(cleanText(approvalId));
|
|
303
|
+
if (!pending || pending.inactive || pending.resolving || pending.submitting) return false;
|
|
304
|
+
if (actor !== undefined && pending.actor !== actor) return false;
|
|
305
|
+
await this.#submit(pending, outcome);
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
290
309
|
async handleResolved(resolution) {
|
|
291
310
|
if (resolution?.kind !== 'approval') return false;
|
|
292
311
|
const pending = this.#byId.get(cleanText(resolution.interactionId));
|
|
@@ -353,7 +372,11 @@ export class HarnessApprovalQueue {
|
|
|
353
372
|
if (this.#routes.get(pending.key)?.items[0] !== pending
|
|
354
373
|
|| pending.inactive || pending.resolving || pending.presented) return;
|
|
355
374
|
if (pending.presentationTask) return pending.presentationTask;
|
|
356
|
-
|
|
375
|
+
// A channel-provided renderer shows the approval as an interactive card
|
|
376
|
+
// (e.g. approve/reject buttons); otherwise fall back to plain text.
|
|
377
|
+
const task = pending.render
|
|
378
|
+
? Promise.resolve().then(() => pending.render(pending, pending.send))
|
|
379
|
+
: Promise.resolve().then(() => pending.send(pending.text));
|
|
357
380
|
pending.presentationTask = task;
|
|
358
381
|
try {
|
|
359
382
|
await task;
|
|
@@ -434,6 +434,11 @@ export class HarnessReplyTracker {
|
|
|
434
434
|
return this.#finished;
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
/** The highest event seq consumed so far; advances as the turn produces events. */
|
|
438
|
+
get lastSeq() {
|
|
439
|
+
return this.#lastSeq;
|
|
440
|
+
}
|
|
441
|
+
|
|
437
442
|
get answer() {
|
|
438
443
|
return this.#latestText.trim();
|
|
439
444
|
}
|
|
@@ -1435,8 +1440,14 @@ export class HarnessClient {
|
|
|
1435
1440
|
promptAccepted = true;
|
|
1436
1441
|
|
|
1437
1442
|
try {
|
|
1438
|
-
|
|
1439
|
-
|
|
1443
|
+
// Treat timeoutMs as a stall window rather than a hard runtime limit.
|
|
1444
|
+
// Durable events are direct progress. Once a full quiet window elapses,
|
|
1445
|
+
// confirm the Session is still running before renewing the wait.
|
|
1446
|
+
// Interaction ownership is intentionally not a liveness signal: it stays
|
|
1447
|
+
// active until turn/end and can therefore outlive a stalled turn.
|
|
1448
|
+
let lastProgressAt = Date.now();
|
|
1449
|
+
let lastPollSeq = tracker.lastSeq;
|
|
1450
|
+
while (true) {
|
|
1440
1451
|
await sleep(300, signal);
|
|
1441
1452
|
const history = await this.rpc(
|
|
1442
1453
|
'session.history',
|
|
@@ -1450,6 +1461,9 @@ export class HarnessClient {
|
|
|
1450
1461
|
if (!wasActive && ownership.active) ownership.reconnect?.();
|
|
1451
1462
|
}
|
|
1452
1463
|
const updates = tracker.consumeAll(history.events ?? []);
|
|
1464
|
+
const seqAdvanced = tracker.lastSeq > lastPollSeq;
|
|
1465
|
+
lastPollSeq = tracker.lastSeq;
|
|
1466
|
+
if (seqAdvanced) lastProgressAt = Date.now();
|
|
1453
1467
|
if (onUpdate) {
|
|
1454
1468
|
const visibleUpdates = progressMode === 'all' ? updates : updates.slice(-1);
|
|
1455
1469
|
for (const update of visibleUpdates) {
|
|
@@ -1460,24 +1474,39 @@ export class HarnessClient {
|
|
|
1460
1474
|
}
|
|
1461
1475
|
}
|
|
1462
1476
|
}
|
|
1463
|
-
if (
|
|
1464
|
-
|
|
1465
|
-
|
|
1477
|
+
if (tracker.finished) {
|
|
1478
|
+
turnFinished = true;
|
|
1479
|
+
if (!ownership?.stopRequested && !harnessTurnSucceeded(tracker.reason)) {
|
|
1480
|
+
throw harnessTurnError(tracker.reason);
|
|
1481
|
+
}
|
|
1482
|
+
// An accepted /stop revokes attachment delivery even when Harness
|
|
1483
|
+
// preserved a useful partial text answer for the existing UX.
|
|
1484
|
+
const artifactCount = ownership?.stopRequested
|
|
1485
|
+
? 0
|
|
1486
|
+
: await deliverArtifacts();
|
|
1487
|
+
if (tracker.answer) {
|
|
1488
|
+
return tracker.answer;
|
|
1489
|
+
}
|
|
1490
|
+
if (artifactCount > 0) return '';
|
|
1491
|
+
if (ownership?.stopRequested) throw turnStoppedError();
|
|
1466
1492
|
throw harnessTurnError(tracker.reason);
|
|
1467
1493
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1494
|
+
|
|
1495
|
+
if (Date.now() - lastProgressAt < timeoutMs) continue;
|
|
1496
|
+
|
|
1497
|
+
let running = false;
|
|
1498
|
+
try {
|
|
1499
|
+
running = await this.isSessionRunning(sessionId, { signal });
|
|
1500
|
+
} catch (error) {
|
|
1501
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
1502
|
+
// A failed liveness probe is not evidence of progress.
|
|
1503
|
+
}
|
|
1504
|
+
if (running) {
|
|
1505
|
+
lastProgressAt = Date.now();
|
|
1506
|
+
continue;
|
|
1475
1507
|
}
|
|
1476
|
-
|
|
1477
|
-
if (ownership?.stopRequested) throw turnStoppedError();
|
|
1478
|
-
throw harnessTurnError(tracker.reason);
|
|
1508
|
+
throw new HarnessTurnError('harness-reply-timeout');
|
|
1479
1509
|
}
|
|
1480
|
-
throw new HarnessTurnError('harness-reply-timeout');
|
|
1481
1510
|
} catch (error) {
|
|
1482
1511
|
// Once cancellation was accepted, transport/poll failures and timeouts
|
|
1483
1512
|
// describe the convergence of that stop, not an unrelated ask failure.
|
|
@@ -356,4 +356,15 @@ export default {
|
|
|
356
356
|
'⚠️ Repair verification failed: the dedicated test card could not be sent, so card.action.trigger cannot be confirmed restored. Do not authorize again; check the bot message permission and connection status first.',
|
|
357
357
|
'⚠️ 修复验证中断:Runtime 已停止,未完成 card.action.trigger 实测,不能确认修复成功。请不要重复授权;先等待机器人恢复连接。':
|
|
358
358
|
'⚠️ Repair verification interrupted: the Runtime stopped before the card.action.trigger test completed, so the repair cannot be confirmed. Do not authorize again; wait for the bot to reconnect.',
|
|
359
|
+
|
|
360
|
+
// feishu/bridge.mjs — interaction cards (approve/reject / answer buttons)
|
|
361
|
+
'该审批已处理或不存在,无需重复操作。':
|
|
362
|
+
'This approval has already been processed or does not exist; no need to repeat the action.',
|
|
363
|
+
// feishu/feishu-cards.mjs — approval card
|
|
364
|
+
'操作参数:\n{operation}': 'Operation parameters:\n{operation}',
|
|
365
|
+
'✅ 批准': '✅ Approve',
|
|
366
|
+
'❌ 拒绝': '❌ Reject',
|
|
367
|
+
'🔐 工具审批': '🔐 Tool approval',
|
|
368
|
+
// feishu/feishu-cards.mjs — question card
|
|
369
|
+
'❓ 请补充信息{progress}': '❓ Please provide more information{progress}',
|
|
359
370
|
};
|
|
@@ -123,6 +123,7 @@ export default {
|
|
|
123
123
|
'/workspace 工作区序号或绝对路径 切换工作区':
|
|
124
124
|
'/workspace <workspace index or absolute path> Switch workspace',
|
|
125
125
|
'/workspacelist 列出工作区绝对路径': '/workspacelist List absolute workspace paths',
|
|
126
|
+
'/ws、/wsl、/workspaces 工作区命令别名': '/ws, /wsl, /workspaces Workspace command aliases',
|
|
126
127
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题':
|
|
127
128
|
'/sessionlist [workspace index or absolute path] List session IDs and titles',
|
|
128
129
|
'/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题':
|
|
@@ -2,7 +2,6 @@ import { promptContentForMessage } from '../image-prompt.mjs';
|
|
|
2
2
|
|
|
3
3
|
const REPLY_CONTENT_MAX_CODE_POINTS = 8_000;
|
|
4
4
|
const REPLY_ATTACHMENTS_MAX = 20;
|
|
5
|
-
const REPLY_ID_MAX_CODE_POINTS = 512;
|
|
6
5
|
const REPLY_AUTHOR_NAME_MAX_CODE_POINTS = 256;
|
|
7
6
|
const REPLY_ATTACHMENT_NAME_MAX_CODE_POINTS = 255;
|
|
8
7
|
|
|
@@ -105,8 +104,6 @@ async function resolveReference(reference, signal) {
|
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
function normalizeReference(reference) {
|
|
108
|
-
const messageId = cleanString(reference.messageId, REPLY_ID_MAX_CODE_POINTS);
|
|
109
|
-
const authorId = cleanString(reference.authorId, REPLY_ID_MAX_CODE_POINTS);
|
|
110
107
|
const authorName = cleanString(reference.authorName, REPLY_AUTHOR_NAME_MAX_CODE_POINTS);
|
|
111
108
|
const content = cleanString(reference.content, REPLY_CONTENT_MAX_CODE_POINTS, { multiline: true });
|
|
112
109
|
const { attachments, truncated: attachmentsTruncated } = cleanAttachments(reference.attachments);
|
|
@@ -114,19 +111,16 @@ function normalizeReference(reference) {
|
|
|
114
111
|
if (!content.value && attachments.length === 0 && !unavailableReason) {
|
|
115
112
|
unavailableReason = 'not-delivered';
|
|
116
113
|
}
|
|
114
|
+
const truncated = authorName.truncated
|
|
115
|
+
|| content.truncated
|
|
116
|
+
|| attachmentsTruncated;
|
|
117
117
|
return {
|
|
118
118
|
note: REPLY_NOTE,
|
|
119
|
-
...(messageId.value ? { messageId: messageId.value } : {}),
|
|
120
|
-
...(authorId.value ? { authorId: authorId.value } : {}),
|
|
121
119
|
...(authorName.value ? { authorName: authorName.value } : {}),
|
|
122
120
|
...(content.value ? { content: content.value } : {}),
|
|
123
|
-
attachments,
|
|
121
|
+
...(attachments.length > 0 ? { attachments } : {}),
|
|
124
122
|
...(unavailableReason ? { unavailableReason } : {}),
|
|
125
|
-
truncated:
|
|
126
|
-
|| authorId.truncated
|
|
127
|
-
|| authorName.truncated
|
|
128
|
-
|| content.truncated
|
|
129
|
-
|| attachmentsTruncated,
|
|
123
|
+
...(truncated ? { truncated: true } : {}),
|
|
130
124
|
};
|
|
131
125
|
}
|
|
132
126
|
|
|
@@ -596,6 +596,7 @@ export class TextHarnessBridge {
|
|
|
596
596
|
t('/history [数量] 查看最近历史消息(默认 3 条,最多 5 条)'),
|
|
597
597
|
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
|
598
598
|
t('/workspacelist 列出工作区绝对路径'),
|
|
599
|
+
t('/ws、/wsl、/workspaces 工作区命令别名'),
|
|
599
600
|
t('/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
|
600
601
|
t('/sessionlist --limit N 仅列出当前工作区前 N 个会话'),
|
|
601
602
|
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
|
@@ -683,11 +684,14 @@ export class TextHarnessBridge {
|
|
|
683
684
|
let contextEnhanced = false;
|
|
684
685
|
if (snapshot) {
|
|
685
686
|
const originalContent = content ?? text;
|
|
687
|
+
const contextSource = message.contextSource?.();
|
|
686
688
|
content = enhanceContextContent(originalContent, snapshot, () => ({
|
|
687
689
|
channel: this.#descriptor.key,
|
|
688
690
|
senderId,
|
|
689
|
-
senderName:
|
|
690
|
-
conversationTitle:
|
|
691
|
+
senderName: contextSource?.senderName,
|
|
692
|
+
conversationTitle: contextSource?.conversationTitle,
|
|
693
|
+
chatId: contextSource?.chatId ?? message.conversationId,
|
|
694
|
+
threadId: contextSource?.threadId,
|
|
691
695
|
}));
|
|
692
696
|
contextEnhanced = content !== originalContent;
|
|
693
697
|
}
|
|
@@ -4,8 +4,8 @@ import { isAbsolute, resolve } from 'node:path';
|
|
|
4
4
|
import { t } from './i18n.mjs';
|
|
5
5
|
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
6
6
|
|
|
7
|
-
const WORKSPACE_COMMAND = /^\/workspace(?:\s+([\s\S]+))?$/i;
|
|
8
|
-
const WORKSPACE_LIST_COMMAND = /^\/workspacelist(?:\s+([\s\S]+))?$/i;
|
|
7
|
+
const WORKSPACE_COMMAND = /^\/(?:workspace|ws)(?:\s+([\s\S]+))?$/i;
|
|
8
|
+
const WORKSPACE_LIST_COMMAND = /^\/(?:workspacelist|workspaces|wsl)(?:\s+([\s\S]+))?$/i;
|
|
9
9
|
const SESSION_LIST_COMMAND = /^\/(?:sessionlist|sessions)(?:\s+([\s\S]+))?$/i;
|
|
10
10
|
const SESSION_BIND_PREFIX = /^\/session(?=$|\s)/i;
|
|
11
11
|
const SESSION_BIND_COMMAND = /^\/session[ \t]+([^\s]+)$/i;
|
|
@@ -172,6 +172,10 @@ export function normalizeSlackEvent(payload, botUserId, {
|
|
|
172
172
|
senderIsBot: String(event.user) === String(botUserId),
|
|
173
173
|
kind: direct ? 'direct' : 'group',
|
|
174
174
|
conversationId: direct ? String(event.channel) : `${event.channel}:${threadTs}`,
|
|
175
|
+
contextSource: () => ({
|
|
176
|
+
chatId: String(event.channel),
|
|
177
|
+
threadId: event.thread_ts ? String(event.thread_ts) : undefined,
|
|
178
|
+
}),
|
|
175
179
|
content: stripBotMention(event.text ?? '', botUserId),
|
|
176
180
|
plainText: !Array.isArray(event.files) || event.files.length === 0,
|
|
177
181
|
images: Array.isArray(event.files)
|
|
@@ -21,7 +21,10 @@ export const TELEGRAM_COMMAND_MENU = Object.freeze([
|
|
|
21
21
|
{ command: 'new', description: '开启一个全新会话' },
|
|
22
22
|
{ command: 'compact', description: '压缩当前会话的较早上下文' },
|
|
23
23
|
{ command: 'workspace', description: '切换工作区' },
|
|
24
|
+
{ command: 'ws', description: '切换工作区' },
|
|
24
25
|
{ command: 'workspacelist', description: '列出工作区绝对路径' },
|
|
26
|
+
{ command: 'workspaces', description: '列出工作区绝对路径' },
|
|
27
|
+
{ command: 'wsl', description: '列出工作区绝对路径' },
|
|
25
28
|
{ command: 'sessionlist', description: '列出会话 ID 和标题' },
|
|
26
29
|
{ command: 'sessions', description: '列出会话 ID 和标题' },
|
|
27
30
|
{ command: 'session', description: '将当前聊天绑定到指定会话' },
|
|
@@ -139,11 +142,10 @@ function telegramFileSource(message, loadFile) {
|
|
|
139
142
|
};
|
|
140
143
|
}
|
|
141
144
|
|
|
142
|
-
function telegramReplyAttachment(kind, file
|
|
145
|
+
function telegramReplyAttachment(kind, file) {
|
|
143
146
|
if (!file || typeof file !== 'object') return null;
|
|
144
147
|
const name = typeof file.file_name === 'string' && file.file_name
|
|
145
|
-
? file.file_name :
|
|
146
|
-
? fallbackName : undefined;
|
|
148
|
+
? file.file_name : undefined;
|
|
147
149
|
return { kind, ...(name ? { name } : {}) };
|
|
148
150
|
}
|
|
149
151
|
|
|
@@ -153,11 +155,7 @@ function telegramReplyAttachments(message) {
|
|
|
153
155
|
const largest = message.photo.reduce((best, candidate) => (
|
|
154
156
|
photoScore(candidate) > photoScore(best) ? candidate : best
|
|
155
157
|
));
|
|
156
|
-
attachments.push(telegramReplyAttachment(
|
|
157
|
-
'image',
|
|
158
|
-
largest,
|
|
159
|
-
`${largest.file_unique_id ?? largest.file_id ?? 'telegram-photo'}.jpg`,
|
|
160
|
-
));
|
|
158
|
+
attachments.push(telegramReplyAttachment('image', largest));
|
|
161
159
|
} else if (message?.document) {
|
|
162
160
|
attachments.push(telegramReplyAttachment(
|
|
163
161
|
imageTypeForDocument(message.document) ? 'image' : 'file',
|
|
@@ -177,7 +175,6 @@ function telegramReplyAttachments(message) {
|
|
|
177
175
|
attachments.push(telegramReplyAttachment(
|
|
178
176
|
message.sticker.is_video === true ? 'video' : 'image',
|
|
179
177
|
message.sticker,
|
|
180
|
-
message.sticker.file_unique_id ?? message.sticker.file_id,
|
|
181
178
|
));
|
|
182
179
|
}
|
|
183
180
|
return attachments.filter(Boolean);
|
|
@@ -266,6 +263,8 @@ export function normalizeTelegramUpdate(update, {
|
|
|
266
263
|
.filter((value) => typeof value === 'string' && value.trim())
|
|
267
264
|
.map((value) => value.trim()).join(' ') || message.from?.username,
|
|
268
265
|
conversationTitle: direct ? undefined : message.chat?.title,
|
|
266
|
+
chatId: String(chatId),
|
|
267
|
+
threadId: messageThreadId === undefined ? undefined : String(messageThreadId),
|
|
269
268
|
}),
|
|
270
269
|
senderIsBot: message.from?.is_bot === true,
|
|
271
270
|
kind: direct ? 'direct' : 'group',
|
|
@@ -73,6 +73,7 @@ function helpText() {
|
|
|
73
73
|
t('/history [数量] 查看最近历史消息(默认 3 条,最多 5 条)'),
|
|
74
74
|
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
|
75
75
|
t('/workspacelist 列出工作区绝对路径'),
|
|
76
|
+
t('/ws、/wsl、/workspaces 工作区命令别名'),
|
|
76
77
|
t('/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
|
77
78
|
t('/sessionlist --limit N 仅列出当前工作区前 N 个会话'),
|
|
78
79
|
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
|
@@ -1056,6 +1057,7 @@ export class WecomHarnessBridge {
|
|
|
1056
1057
|
content = enhanceContextContent(originalContent, snapshot, () => ({
|
|
1057
1058
|
channel: 'wecom',
|
|
1058
1059
|
senderId,
|
|
1060
|
+
chatId,
|
|
1059
1061
|
}));
|
|
1060
1062
|
contextEnhanced = content !== originalContent;
|
|
1061
1063
|
}
|
|
@@ -87,6 +87,7 @@ const HELP_TEXT = () => [
|
|
|
87
87
|
t('/history [数量] 查看最近历史消息(默认 3 条,最多 5 条)'),
|
|
88
88
|
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
|
89
89
|
t('/workspacelist 列出工作区绝对路径'),
|
|
90
|
+
t('/ws、/wsl、/workspaces 工作区命令别名'),
|
|
90
91
|
t('/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
|
91
92
|
t('/sessionlist --limit N 仅列出当前工作区前 N 个会话'),
|
|
92
93
|
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
|
@@ -786,6 +787,7 @@ export class WeixinHarnessBridge {
|
|
|
786
787
|
content = enhanceContextContent(originalContent, snapshot, () => ({
|
|
787
788
|
channel: 'weixin',
|
|
788
789
|
senderId: sender,
|
|
790
|
+
chatId: sender,
|
|
789
791
|
}));
|
|
790
792
|
contextEnhanced = content !== originalContent;
|
|
791
793
|
}
|