@xmanrui/dsh-im 4.24.0 → 4.25.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 +11 -3
- package/README.md +11 -3
- package/THIRD_PARTY_NOTICES.md +2 -0
- package/lib/client.js +965 -607
- package/lib/index.js +296 -287
- package/package.json +8 -2
- package/plugin-src/client/channel-card-meta.js +3 -9
- package/plugin-src/client/channel-logos.js +11 -0
- package/plugin-src/client/channels/dingtalk/index.js +9 -8
- package/plugin-src/client/channels/feishu/index.js +14 -13
- package/plugin-src/client/channels/matrix/api.js +11 -0
- package/plugin-src/client/channels/matrix/index.js +151 -0
- package/plugin-src/client/channels/matrix/styles.js +36 -0
- package/plugin-src/client/channels/qq/index.js +9 -8
- package/plugin-src/client/channels/shared/collapsible-account.js +49 -26
- package/plugin-src/client/channels/shared/token-channel.js +9 -8
- package/plugin-src/client/channels/wecom/index.js +9 -8
- package/plugin-src/client/channels/wecom-app/index.js +9 -8
- package/plugin-src/client/channels/weixin/index.js +9 -8
- package/plugin-src/client/channels/whatsapp/index.js +9 -8
- package/plugin-src/client/i18n.js +21 -0
- package/plugin-src/client/index.js +20 -0
- package/plugin-src/client/session-channel-logos.js +2 -1
- package/plugin-src/client/styles.js +15 -9
- package/plugin-src/client/update-panel.js +42 -18
- package/plugin-src/host/channels/matrix/index.mjs +31 -0
- package/plugin-src/host/channels/matrix/production.mjs +227 -0
- package/plugin-src/host/channels/matrix/rpc.mjs +228 -0
- package/plugin-src/host/channels/shared/access-policy-production.mjs +1 -1
- package/plugin-src/host/channels/shared/startup-error.mjs +2 -1
- package/plugin-src/host/delivery-adapter.mjs +18 -0
- package/plugin-src/host/delivery-suggestions.mjs +13 -0
- package/plugin-src/host/index.mjs +3 -0
- package/plugin-src/host/update-service.mjs +19 -14
- package/scripts/verify-lan-management.mjs +1 -1
- package/scripts/verify-package.mjs +5 -1
- package/src/channels/feishu/bridge.mjs +91 -42
- package/src/channels/matrix/matrix-api.mjs +696 -0
- package/src/channels/matrix/matrix-bridge.mjs +20 -0
- package/src/channels/matrix/matrix-config-store.mjs +356 -0
- package/src/channels/matrix/matrix-controller.mjs +404 -0
- package/src/channels/matrix/matrix-crypto-store.mjs +279 -0
- package/src/channels/matrix/matrix-crypto.mjs +1014 -0
- package/src/channels/matrix/matrix-harness-client.mjs +11 -0
- package/src/channels/matrix/matrix-normalize.mjs +357 -0
- package/src/channels/matrix/matrix-rich-text.mjs +313 -0
- package/src/channels/matrix/matrix-runtime.mjs +900 -0
- package/src/channels/shared/command-catalog.mjs +1 -1
- package/src/channels/shared/i18n-en/matrix.mjs +75 -0
- package/src/channels/shared/i18n-en.mjs +2 -0
- package/src/channels/shared/session-channel-labels.mjs +1 -0
- package/src/channels/shared/text-harness-bridge.mjs +3 -0
- package/src/channels/shared/workspace-session.mjs +7 -1
|
@@ -45,6 +45,8 @@ const required = [
|
|
|
45
45
|
'plugin-src/host/channels/telegram/index.mjs',
|
|
46
46
|
'plugin-src/host/channels/discord/index.mjs',
|
|
47
47
|
'plugin-src/host/channels/whatsapp/index.mjs',
|
|
48
|
+
'plugin-src/host/channels/matrix/index.mjs',
|
|
49
|
+
'plugin-src/client/channels/matrix/index.js',
|
|
48
50
|
'src/channels/feishu/feishu-runtime.mjs',
|
|
49
51
|
'src/channels/weixin/weixin-runtime.mjs',
|
|
50
52
|
'src/channels/dingtalk/dingtalk-runtime.mjs',
|
|
@@ -56,6 +58,8 @@ const required = [
|
|
|
56
58
|
'src/channels/discord/discord-runtime.mjs',
|
|
57
59
|
'src/channels/whatsapp/whatsapp-runtime.mjs',
|
|
58
60
|
'src/channels/whatsapp/whatsapp-web-session.mjs',
|
|
61
|
+
'src/channels/matrix/matrix-runtime.mjs',
|
|
62
|
+
'src/channels/matrix/matrix-api.mjs',
|
|
59
63
|
'src/channels/shared/context-enhancement.mjs',
|
|
60
64
|
];
|
|
61
65
|
await Promise.all(required.map((path) => access(resolve(root, path))));
|
|
@@ -174,7 +178,7 @@ if (!client.includes('container-type: inline-size')
|
|
|
174
178
|
|| !client.includes('@container (max-width: 680px)')) {
|
|
175
179
|
throw new Error('client bundle does not contain the narrow-panel DingTalk QR layout');
|
|
176
180
|
}
|
|
177
|
-
for (const marker of ['/feishu', '/weixin', '/dingtalk', '/wecom', '/qq', '/slack', '/telegram', '/discord', '/whatsapp']) {
|
|
181
|
+
for (const marker of ['/feishu', '/weixin', '/dingtalk', '/wecom', '/qq', '/slack', '/telegram', '/discord', '/whatsapp', '/matrix']) {
|
|
178
182
|
if (!host.includes(marker)) {
|
|
179
183
|
throw new Error(`host bundle does not contain the internal ${marker} RPC provider`);
|
|
180
184
|
}
|
|
@@ -124,6 +124,7 @@ const RESOLVED_REPLY_TTL_MS = 30 * 60_000;
|
|
|
124
124
|
const ROOT_CANDIDATE_TTL_MS = 30 * 60_000;
|
|
125
125
|
/** Bound main-feed roots awaiting a topic so silent drops cannot grow them. */
|
|
126
126
|
const MAX_ROOT_CANDIDATES = 512;
|
|
127
|
+
const MAX_TOPIC_ANCHORS = 4_000;
|
|
127
128
|
|
|
128
129
|
const MENU_COMMAND = /^\/m(?:enu)?$/i;
|
|
129
130
|
const REPAIR_COMMAND_PREFIX = /^\/repair(?:\s|$)/i;
|
|
@@ -624,6 +625,8 @@ export class FeishuHarnessBridge {
|
|
|
624
625
|
#anchorTopicReply = new Map();
|
|
625
626
|
/** Main-feed roots this bot intends to auto-open as topics, awaiting thread_id. */
|
|
626
627
|
#rootCandidates = new Map();
|
|
628
|
+
/** Last successfully injected topic root per conversation and AI session. */
|
|
629
|
+
#topicAnchors = new Map();
|
|
627
630
|
#repair;
|
|
628
631
|
#repairAttempt = null;
|
|
629
632
|
#repairMonitorVersion = 0;
|
|
@@ -4875,6 +4878,72 @@ export class FeishuHarnessBridge {
|
|
|
4875
4878
|
};
|
|
4876
4879
|
}
|
|
4877
4880
|
|
|
4881
|
+
async #preparePrompt(event, key, message) {
|
|
4882
|
+
const snapshot = this.#acceptedMessageIds.get(event.message.message_id);
|
|
4883
|
+
const build = async (input) => {
|
|
4884
|
+
const rich = hasInboundImages(input) || hasReplyReference(input);
|
|
4885
|
+
const original = rich
|
|
4886
|
+
? await promptContentForInboundMessage(input, { signal: this.#signal, deferImages: true })
|
|
4887
|
+
: input.content;
|
|
4888
|
+
const enhanced = enhanceContextContent(original, snapshot, () => ({
|
|
4889
|
+
channel: 'feishu',
|
|
4890
|
+
senderId: senderOpenId(event),
|
|
4891
|
+
chatId: event.message.chat_id,
|
|
4892
|
+
threadId: event.message.thread_id,
|
|
4893
|
+
}));
|
|
4894
|
+
return {
|
|
4895
|
+
content: rich || enhanced !== original ? enhanced : undefined,
|
|
4896
|
+
contextEnhanced: enhanced !== original,
|
|
4897
|
+
sourceGuidance: snapshot?.config?.guidance,
|
|
4898
|
+
};
|
|
4899
|
+
};
|
|
4900
|
+
const rootId = nonEmptyString(event.message.root_id);
|
|
4901
|
+
const topicAnchor = event.message.chat_type === 'group'
|
|
4902
|
+
&& nonEmptyString(event.message.thread_id)
|
|
4903
|
+
&& rootId && message.replyTo?.messageId === rootId
|
|
4904
|
+
// A reference-only message must not become an empty prompt.
|
|
4905
|
+
&& (nonEmptyString(message.content) || hasInboundImages(message) || hasInboundFiles(message));
|
|
4906
|
+
if (!topicAnchor) return build(message);
|
|
4907
|
+
|
|
4908
|
+
const withoutAnchor = { ...message };
|
|
4909
|
+
delete withoutAnchor.replyTo;
|
|
4910
|
+
const base = await build(withoutAnchor);
|
|
4911
|
+
let preparedSessionId;
|
|
4912
|
+
let anchorLoaded = false;
|
|
4913
|
+
return {
|
|
4914
|
+
...base,
|
|
4915
|
+
prepareContent: async ({ sessionId }) => {
|
|
4916
|
+
preparedSessionId = sessionId;
|
|
4917
|
+
anchorLoaded = false;
|
|
4918
|
+
const previous = this.#topicAnchors.get(key);
|
|
4919
|
+
if (previous?.sessionId === sessionId && previous.rootMessageId === rootId) {
|
|
4920
|
+
return base.content ?? message.content;
|
|
4921
|
+
}
|
|
4922
|
+
const prepared = await build({
|
|
4923
|
+
...message,
|
|
4924
|
+
replyTo: {
|
|
4925
|
+
...message.replyTo,
|
|
4926
|
+
load: async (options) => {
|
|
4927
|
+
const reference = await message.replyTo.load(options);
|
|
4928
|
+
anchorLoaded = !reference?.unavailableReason && Boolean(
|
|
4929
|
+
nonEmptyString(reference?.content) || reference?.attachments?.length,
|
|
4930
|
+
);
|
|
4931
|
+
return reference;
|
|
4932
|
+
},
|
|
4933
|
+
},
|
|
4934
|
+
});
|
|
4935
|
+
return prepared.content;
|
|
4936
|
+
},
|
|
4937
|
+
onAskComplete: (sessionId) => {
|
|
4938
|
+
if (!anchorLoaded || sessionId !== preparedSessionId) return;
|
|
4939
|
+
if (!this.#topicAnchors.has(key) && this.#topicAnchors.size >= MAX_TOPIC_ANCHORS) {
|
|
4940
|
+
this.#topicAnchors.delete(this.#topicAnchors.keys().next().value);
|
|
4941
|
+
}
|
|
4942
|
+
this.#topicAnchors.set(key, { sessionId, rootMessageId: rootId });
|
|
4943
|
+
},
|
|
4944
|
+
};
|
|
4945
|
+
}
|
|
4946
|
+
|
|
4878
4947
|
/**
|
|
4879
4948
|
* 分步直推:`#answerWithStream` 流式分支在开关开启时的完整替代路径。
|
|
4880
4949
|
* 过程(工具调用、助手中间说明)与最终答案均以富文本 post 逐条直推
|
|
@@ -4886,30 +4955,18 @@ export class FeishuHarnessBridge {
|
|
|
4886
4955
|
const messageId = event.message.message_id;
|
|
4887
4956
|
const text = message.content;
|
|
4888
4957
|
let askCompleted = false;
|
|
4889
|
-
const markAskComplete = () => {
|
|
4958
|
+
const markAskComplete = (sessionId) => {
|
|
4890
4959
|
if (askCompleted) return;
|
|
4891
4960
|
askCompleted = true;
|
|
4961
|
+
prompt.onAskComplete?.(sessionId);
|
|
4892
4962
|
this.#endImTurn(key);
|
|
4893
4963
|
onAskComplete?.();
|
|
4894
4964
|
};
|
|
4895
4965
|
// 与流式分支一致的提示内容构造:图片与回复引用展开为富提示内容,已接受
|
|
4896
4966
|
// 的上下文增强按原样重放。直推分流发生在 `#answerWithStream` 构造之前,
|
|
4897
4967
|
// 这里就是本回合唯一一次构造(无重复的 prompt 往返)。
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
: undefined;
|
|
4901
|
-
const snapshot = this.#acceptedMessageIds.get(messageId);
|
|
4902
|
-
let contextEnhanced = false;
|
|
4903
|
-
if (snapshot) {
|
|
4904
|
-
const originalContent = content ?? text;
|
|
4905
|
-
content = enhanceContextContent(originalContent, snapshot, () => ({
|
|
4906
|
-
channel: 'feishu',
|
|
4907
|
-
senderId: senderOpenId(event),
|
|
4908
|
-
chatId: event.message.chat_id,
|
|
4909
|
-
threadId: event.message.thread_id,
|
|
4910
|
-
}));
|
|
4911
|
-
contextEnhanced = content !== originalContent;
|
|
4912
|
-
}
|
|
4968
|
+
const prompt = await this.#preparePrompt(event, key, message);
|
|
4969
|
+
const { content, contextEnhanced } = prompt;
|
|
4913
4970
|
// Every turn reopens the per-conversation circuit breaker.
|
|
4914
4971
|
const priorState = this.#stepPushSendState.get(key);
|
|
4915
4972
|
if (this.#stepPushSendState.size >= MAX_STEP_PUSH_SEND_STATES && !priorState) {
|
|
@@ -5010,7 +5067,8 @@ export class FeishuHarnessBridge {
|
|
|
5010
5067
|
text,
|
|
5011
5068
|
content,
|
|
5012
5069
|
titleText: event.batchSubmission?.title,
|
|
5013
|
-
sourceGuidance:
|
|
5070
|
+
sourceGuidance: prompt.sourceGuidance,
|
|
5071
|
+
prepareContent: prompt.prepareContent,
|
|
5014
5072
|
contextEnhanced,
|
|
5015
5073
|
createOptions: { signal: this.#signal },
|
|
5016
5074
|
existsOptions: { signal: this.#signal },
|
|
@@ -5089,8 +5147,8 @@ export class FeishuHarnessBridge {
|
|
|
5089
5147
|
// 回合结束(成功/失败/中断):停看门狗并撤回残留思考中心跳。
|
|
5090
5148
|
await watchdog?.stop();
|
|
5091
5149
|
}
|
|
5150
|
+
markAskComplete(completed.sessionId);
|
|
5092
5151
|
await cot?.finish();
|
|
5093
|
-
markAskComplete();
|
|
5094
5152
|
const finalStepText = pendingStep ? pendingStep.text : null;
|
|
5095
5153
|
pendingStep = null;
|
|
5096
5154
|
const finalText = (typeof finalStepText === 'string' && finalStepText.trim())
|
|
@@ -5228,9 +5286,10 @@ export class FeishuHarnessBridge {
|
|
|
5228
5286
|
const messageId = event.message.message_id;
|
|
5229
5287
|
const text = message.content;
|
|
5230
5288
|
let askCompleted = false;
|
|
5231
|
-
const markAskComplete = () => {
|
|
5289
|
+
const markAskComplete = (sessionId) => {
|
|
5232
5290
|
if (askCompleted) return;
|
|
5233
5291
|
askCompleted = true;
|
|
5292
|
+
prompt.onAskComplete?.(sessionId);
|
|
5234
5293
|
this.#endImTurn(key);
|
|
5235
5294
|
onAskComplete?.();
|
|
5236
5295
|
};
|
|
@@ -5241,23 +5300,10 @@ export class FeishuHarnessBridge {
|
|
|
5241
5300
|
if (this.#stepPush && this.#channel?.stream) {
|
|
5242
5301
|
return this.#answerWithStepPush(event, key, message, { onAskComplete });
|
|
5243
5302
|
}
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
: undefined;
|
|
5247
|
-
const snapshot = this.#acceptedMessageIds.get(messageId);
|
|
5248
|
-
let contextEnhanced = false;
|
|
5249
|
-
if (snapshot) {
|
|
5250
|
-
const originalContent = content ?? text;
|
|
5251
|
-
content = enhanceContextContent(originalContent, snapshot, () => ({
|
|
5252
|
-
channel: 'feishu',
|
|
5253
|
-
senderId: senderOpenId(event),
|
|
5254
|
-
chatId: event.message.chat_id,
|
|
5255
|
-
threadId: event.message.thread_id,
|
|
5256
|
-
}));
|
|
5257
|
-
contextEnhanced = content !== originalContent;
|
|
5258
|
-
}
|
|
5303
|
+
const prompt = await this.#preparePrompt(event, key, message);
|
|
5304
|
+
const { content, contextEnhanced } = prompt;
|
|
5259
5305
|
if (!this.#channel?.stream) {
|
|
5260
|
-
const { answer, artifacts = [] } = await askInWorkspaceSession({
|
|
5306
|
+
const { sessionId, answer, artifacts = [] } = await askInWorkspaceSession({
|
|
5261
5307
|
deferredDelivery: () => ({ coordinator: this.#deferred, chatId, replyToMessageId: messageId }),
|
|
5262
5308
|
harness: this.#harness,
|
|
5263
5309
|
state: this.#state,
|
|
@@ -5265,13 +5311,14 @@ export class FeishuHarnessBridge {
|
|
|
5265
5311
|
text,
|
|
5266
5312
|
content,
|
|
5267
5313
|
titleText: event.batchSubmission?.title,
|
|
5268
|
-
sourceGuidance:
|
|
5314
|
+
sourceGuidance: prompt.sourceGuidance,
|
|
5315
|
+
prepareContent: prompt.prepareContent,
|
|
5269
5316
|
contextEnhanced,
|
|
5270
5317
|
createOptions: { signal: this.#signal },
|
|
5271
5318
|
existsOptions: { signal: this.#signal },
|
|
5272
5319
|
askOptions: this.#interactionAskOptions(event, key, message.files, message.images),
|
|
5273
5320
|
});
|
|
5274
|
-
markAskComplete();
|
|
5321
|
+
markAskComplete(sessionId);
|
|
5275
5322
|
let textReceipt;
|
|
5276
5323
|
let textSendError = null;
|
|
5277
5324
|
try {
|
|
@@ -5346,13 +5393,14 @@ export class FeishuHarnessBridge {
|
|
|
5346
5393
|
text,
|
|
5347
5394
|
content,
|
|
5348
5395
|
titleText: event.batchSubmission?.title,
|
|
5349
|
-
sourceGuidance:
|
|
5396
|
+
sourceGuidance: prompt.sourceGuidance,
|
|
5397
|
+
prepareContent: prompt.prepareContent,
|
|
5350
5398
|
contextEnhanced,
|
|
5351
5399
|
createOptions: { signal: this.#signal },
|
|
5352
5400
|
existsOptions: { signal: this.#signal },
|
|
5353
5401
|
askOptions,
|
|
5354
5402
|
});
|
|
5355
|
-
markAskComplete();
|
|
5403
|
+
markAskComplete(completed.sessionId);
|
|
5356
5404
|
completedAnswer = completed.answer;
|
|
5357
5405
|
completedArtifacts = completed.artifacts ?? [];
|
|
5358
5406
|
await controller.setContent(answerTextForDelivery(completedAnswer, completedArtifacts));
|
|
@@ -5410,7 +5458,7 @@ export class FeishuHarnessBridge {
|
|
|
5410
5458
|
if (promptStarted) throw error;
|
|
5411
5459
|
|
|
5412
5460
|
this.#logger.warn?.('[dsh-feishu] native stream unavailable; using text fallback:', error.message);
|
|
5413
|
-
const { answer, artifacts = [] } = await askInWorkspaceSession({
|
|
5461
|
+
const { sessionId, answer, artifacts = [] } = await askInWorkspaceSession({
|
|
5414
5462
|
deferredDelivery: () => ({ coordinator: this.#deferred, chatId, replyToMessageId: messageId }),
|
|
5415
5463
|
harness: this.#harness,
|
|
5416
5464
|
state: this.#state,
|
|
@@ -5418,13 +5466,14 @@ export class FeishuHarnessBridge {
|
|
|
5418
5466
|
text,
|
|
5419
5467
|
content,
|
|
5420
5468
|
titleText: event.batchSubmission?.title,
|
|
5421
|
-
sourceGuidance:
|
|
5469
|
+
sourceGuidance: prompt.sourceGuidance,
|
|
5470
|
+
prepareContent: prompt.prepareContent,
|
|
5422
5471
|
contextEnhanced,
|
|
5423
5472
|
createOptions: { signal: this.#signal },
|
|
5424
5473
|
existsOptions: { signal: this.#signal },
|
|
5425
5474
|
askOptions: this.#interactionAskOptions(event, key, message.files, message.images),
|
|
5426
5475
|
});
|
|
5427
|
-
markAskComplete();
|
|
5476
|
+
markAskComplete(sessionId);
|
|
5428
5477
|
let textReceipt;
|
|
5429
5478
|
let textSendError = null;
|
|
5430
5479
|
try {
|