@xmanrui/dsh-im 4.19.2 → 4.20.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.en.md +79 -1
- package/README.md +79 -1
- package/lib/client.js +594 -217
- package/lib/index.js +279 -276
- package/package.json +9 -1
- package/plugin-src/client/channels/weixin/api.js +35 -17
- package/plugin-src/client/channels/weixin/connection-error.js +68 -0
- package/plugin-src/client/channels/weixin/index.js +36 -12
- package/plugin-src/client/i18n.js +2 -0
- package/plugin-src/host/channels/shared/startup.mjs +7 -4
- package/plugin-src/host/channels/weixin/connection-supervisor.mjs +13 -1
- package/plugin-src/host/channels/weixin/index.mjs +12 -3
- package/plugin-src/host/channels/weixin/production.mjs +53 -3
- package/plugin-src/host/channels/weixin/rpc.mjs +22 -17
- package/src/channels/dingtalk/dingtalk-bridge.mjs +4 -1
- package/src/channels/dingtalk/dingtalk-menu.mjs +8 -4
- package/src/channels/feishu/bridge.mjs +44 -13
- package/src/channels/qq/qq-bridge.mjs +12 -4
- package/src/channels/qq/qq-menu.mjs +11 -8
- package/src/channels/shared/bot-workspace-store.mjs +532 -40
- package/src/channels/shared/command-catalog.mjs +5 -0
- package/src/channels/shared/compact-command.mjs +14 -4
- package/src/channels/shared/control-command.mjs +1 -1
- package/src/channels/shared/deferred-delivery-coordinator.mjs +1 -1
- package/src/channels/shared/history-command.mjs +1 -1
- package/src/channels/shared/i18n-en/shared-a.mjs +41 -0
- package/src/channels/shared/i18n-en/wecom.mjs +1 -1
- package/src/channels/shared/i18n-en/weixin.mjs +2 -0
- package/src/channels/shared/model-command.mjs +5 -3
- package/src/channels/shared/workspace-command.mjs +114 -9
- package/src/channels/shared/workspace-session.mjs +55 -5
- package/src/channels/telegram/telegram-runtime.mjs +1 -1
- package/src/channels/wecom/wecom-bridge.mjs +14 -46
- package/src/channels/wecom/wecom-runtime.mjs +1 -1
- package/src/channels/weixin/connection-error.en.mjs +116 -0
- package/src/channels/weixin/connection-error.mjs +204 -0
- package/src/channels/weixin/diagnostic-details.mjs +40 -0
- package/src/channels/weixin/state-store.mjs +4 -3
- package/src/channels/weixin/weixin-api.mjs +20 -8
- package/src/channels/weixin/weixin-bridge.mjs +3 -2
- package/src/channels/weixin/weixin-controller.mjs +133 -104
- package/src/channels/weixin/weixin-runtime.mjs +35 -24
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
|
|
2
2
|
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
3
3
|
import QRCode from 'qrcode';
|
|
4
|
+
import { createWeixinDiagnostics, weixinStageError } from '../../../../src/channels/weixin/connection-error.mjs';
|
|
4
5
|
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
|
5
6
|
import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/access-policy-rpc.mjs';
|
|
6
7
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
7
8
|
import {
|
|
8
|
-
publicWorkspaceError,
|
|
9
9
|
SET_WORKSPACE_ENDPOINT,
|
|
10
10
|
validWorkspacePayload,
|
|
11
11
|
} from '../shared/workspace-rpc.mjs';
|
|
@@ -118,13 +118,6 @@ function cancelled() {
|
|
|
118
118
|
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } };
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function internalFailure() {
|
|
122
|
-
return {
|
|
123
|
-
ok: false,
|
|
124
|
-
error: { code: 'weixin-operation-failed', message: '微信操作失败,请稍后重试。' },
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
121
|
async function qrDataUrl(value) {
|
|
129
122
|
return QRCode.toDataURL(value, {
|
|
130
123
|
type: 'image/png',
|
|
@@ -143,7 +136,9 @@ async function withEncodedQr(value, encodeQr) {
|
|
|
143
136
|
}
|
|
144
137
|
|
|
145
138
|
async function publicStatus(status, encodeQr) {
|
|
146
|
-
|
|
139
|
+
let safe;
|
|
140
|
+
try { safe = structuredClone(status); }
|
|
141
|
+
catch (error) { throw weixinStageError('status-read-failed', error); }
|
|
147
142
|
if (safe.provisioning) safe.provisioning = await withEncodedQr(safe.provisioning, encodeQr);
|
|
148
143
|
return safe;
|
|
149
144
|
}
|
|
@@ -161,14 +156,17 @@ function assertController(controller) {
|
|
|
161
156
|
}
|
|
162
157
|
}
|
|
163
158
|
|
|
164
|
-
export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
159
|
+
export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl, logger = console, diagnostics = createWeixinDiagnostics({ logger }) } = {}) {
|
|
165
160
|
assertController(controller);
|
|
166
161
|
const qrCache = new Map();
|
|
167
162
|
const cachedEncode = (url) => {
|
|
168
163
|
let encoded = qrCache.get(url);
|
|
169
164
|
if (!encoded) {
|
|
170
165
|
if (qrCache.size >= 16) qrCache.delete(qrCache.keys().next().value);
|
|
171
|
-
encoded = Promise.resolve().then(() => encodeQr(url))
|
|
166
|
+
encoded = Promise.resolve().then(() => encodeQr(url)).catch(error => {
|
|
167
|
+
qrCache.delete(url);
|
|
168
|
+
throw weixinStageError('qr-encode-failed', error);
|
|
169
|
+
});
|
|
172
170
|
qrCache.set(url, encoded);
|
|
173
171
|
}
|
|
174
172
|
return encoded;
|
|
@@ -193,7 +191,7 @@ export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl } = {}
|
|
|
193
191
|
value = await withEncodedQr(started, cachedEncode);
|
|
194
192
|
} else if (endpoint === WEIXIN_ENDPOINTS.pollProvisioning) {
|
|
195
193
|
const current = await controller.registrationStatus(payload.attemptId);
|
|
196
|
-
if (!current)
|
|
194
|
+
if (!current) throw weixinStageError('provision-attempt-not-found', undefined, 'qr.poll');
|
|
197
195
|
value = await withEncodedQr(current, cachedEncode);
|
|
198
196
|
} else if (endpoint === WEIXIN_ENDPOINTS.submitVerification) {
|
|
199
197
|
value = await withEncodedQr(
|
|
@@ -202,7 +200,7 @@ export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl } = {}
|
|
|
202
200
|
);
|
|
203
201
|
} else if (endpoint === WEIXIN_ENDPOINTS.cancelProvisioning) {
|
|
204
202
|
value = await controller.cancelProvisioning(payload.attemptId);
|
|
205
|
-
if (!value)
|
|
203
|
+
if (!value) throw weixinStageError('provision-attempt-not-found', undefined, 'qr.cancel');
|
|
206
204
|
} else if (endpoint === WEIXIN_ENDPOINTS.reconnectBot) {
|
|
207
205
|
const snapshot = await controller.reconnectBot(payload.botId);
|
|
208
206
|
if (signal?.aborted) return cancelled();
|
|
@@ -263,10 +261,17 @@ export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl } = {}
|
|
|
263
261
|
}
|
|
264
262
|
return signal?.aborted ? cancelled() : { ok: true, value };
|
|
265
263
|
} catch (error) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
:
|
|
264
|
+
if (signal?.aborted) return cancelled();
|
|
265
|
+
const context = {
|
|
266
|
+
'connection.status': ['status.read', 'status-read-failed'],
|
|
267
|
+
'provision.begin': ['qr.begin', 'qr-start-failed'],
|
|
268
|
+
'provision.poll': ['qr.poll'], 'provision.verify': ['qr.verify'], 'provision.cancel': ['qr.cancel'],
|
|
269
|
+
'bot.reconnect': ['connection.start', 'connection-start-failed'],
|
|
270
|
+
'bot.delete': ['account.remove'], 'bot.workspace.set': ['workspace.write', 'workspace-save-failed'],
|
|
271
|
+
}[endpoint] ?? ['workspace.write', 'workspace-save-failed'];
|
|
272
|
+
return { ok: false, error: diagnostics.report(error, {
|
|
273
|
+
operation: endpoint, stage: context[0], code: context[1], botId: payload.botId,
|
|
274
|
+
}).publicError };
|
|
270
275
|
}
|
|
271
276
|
};
|
|
272
277
|
}
|
|
@@ -852,7 +852,10 @@ export class DingtalkHarnessBridge {
|
|
|
852
852
|
});
|
|
853
853
|
return;
|
|
854
854
|
}
|
|
855
|
+
const sessionWorkspace = typeof this.#harness.currentConversationWorkspace === 'function'
|
|
856
|
+
? this.#harness.currentConversationWorkspace(key) : this.#harness.currentWorkspace?.();
|
|
855
857
|
if (entry.workspace !== this.#harness.currentWorkspace?.()
|
|
858
|
+
|| entry.sessionWorkspace !== sessionWorkspace
|
|
856
859
|
|| entry.sessionId !== this.#state.sessionFor(key)) {
|
|
857
860
|
result = { message: t('会话或工作区已变化,菜单已刷新,请重新选择。') };
|
|
858
861
|
} else if ((this.#queues.has(key) || options.pendingInteraction || this.#batchInputs.status(key).phase !== 'idle')
|
|
@@ -1029,7 +1032,7 @@ export class DingtalkHarnessBridge {
|
|
|
1029
1032
|
if (quotedAt === null) return { unavailableReason: 'not-delivered' };
|
|
1030
1033
|
const sessionId = this.#state.sessionFor(key);
|
|
1031
1034
|
const session = typeof sessionId === 'string' && sessionId
|
|
1032
|
-
? this.#harness.workspaceSession?.(sessionId)
|
|
1035
|
+
? this.#harness.workspaceSession?.(sessionId, key)
|
|
1033
1036
|
: null;
|
|
1034
1037
|
const text = await recoverAssistantTextByTimestamp({
|
|
1035
1038
|
session,
|
|
@@ -19,20 +19,24 @@ const presetCommand = (id) => `/preset ${/^\d+$/u.test(id) ? 'id:' : ''}${id}`;
|
|
|
19
19
|
// as a command supplied by the client or as a prompt for the model.
|
|
20
20
|
export async function dingtalkMenuSnapshot(harness, state, key, signal) {
|
|
21
21
|
const workspace = harness.currentWorkspace?.();
|
|
22
|
+
const currentSessionWorkspace = () => typeof harness.currentConversationWorkspace === 'function'
|
|
23
|
+
? harness.currentConversationWorkspace(key) : harness.currentWorkspace?.();
|
|
24
|
+
const sessionWorkspace = currentSessionWorkspace();
|
|
22
25
|
const sessionId = state.sessionFor(key);
|
|
23
26
|
const options = { signal };
|
|
24
27
|
const results = await Promise.allSettled([
|
|
25
28
|
workspacePathSnapshot(harness, options),
|
|
26
|
-
harness.listWorkspaceSessions?.(
|
|
29
|
+
harness.listWorkspaceSessions?.(sessionWorkspace, options),
|
|
27
30
|
(async () => {
|
|
28
|
-
const session = sessionId ? harness.workspaceSession?.(sessionId) : null;
|
|
31
|
+
const session = sessionId ? harness.workspaceSession?.(sessionId, key) : null;
|
|
29
32
|
return typeof session?.models === 'function'
|
|
30
33
|
? session.models(options) : harness.listModels?.(options);
|
|
31
34
|
})(),
|
|
32
35
|
harness.agentPresetSettings?.(options),
|
|
33
36
|
]);
|
|
34
37
|
signal?.throwIfAborted();
|
|
35
|
-
if (harness.currentWorkspace?.() !== workspace
|
|
38
|
+
if (harness.currentWorkspace?.() !== workspace
|
|
39
|
+
|| currentSessionWorkspace() !== sessionWorkspace || state.sessionFor(key) !== sessionId) {
|
|
36
40
|
throw new Error(t('会话或工作区已变化,请重新发送 /m。'));
|
|
37
41
|
}
|
|
38
42
|
const [paths, listed, catalog, settings] = results.map((r) => r.status === 'fulfilled' ? r.value : null);
|
|
@@ -65,7 +69,7 @@ export async function dingtalkMenuSnapshot(harness, state, key, signal) {
|
|
|
65
69
|
}));
|
|
66
70
|
data[`${name}_index`] = entries.findIndex(([, command]) => command === current[name]);
|
|
67
71
|
}
|
|
68
|
-
return { workspace, sessionId, selections, data };
|
|
72
|
+
return { workspace, sessionWorkspace, sessionId, selections, data };
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
export function dingtalkMenuCommand(entry, callback) {
|
|
@@ -2266,6 +2266,7 @@ export class FeishuHarnessBridge {
|
|
|
2266
2266
|
chatId,
|
|
2267
2267
|
key,
|
|
2268
2268
|
messageId = null,
|
|
2269
|
+
conversationWorkspace,
|
|
2269
2270
|
sessionWorkspace = null,
|
|
2270
2271
|
sessionPage = 0,
|
|
2271
2272
|
sessionLimit = null,
|
|
@@ -2513,6 +2514,10 @@ export class FeishuHarnessBridge {
|
|
|
2513
2514
|
return;
|
|
2514
2515
|
}
|
|
2515
2516
|
if (action.startsWith('use:')) {
|
|
2517
|
+
if (conversationWorkspace !== undefined && conversationWorkspace !== this.#conversationWorkspace(key)) {
|
|
2518
|
+
await reply(t('这个菜单已过期,请回复 /m 重新打开。'));
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2516
2521
|
await this.#bindSession(key, chatId, action.slice('use:'.length), { updateMessageId: messageId, replyTo: messageId });
|
|
2517
2522
|
return;
|
|
2518
2523
|
}
|
|
@@ -2594,7 +2599,9 @@ export class FeishuHarnessBridge {
|
|
|
2594
2599
|
return;
|
|
2595
2600
|
}
|
|
2596
2601
|
// The number label sits on the session (bind) button of the row.
|
|
2597
|
-
await this.#handleCardAction(`use:${session.sessionId}`, {
|
|
2602
|
+
await this.#handleCardAction(`use:${session.sessionId}`, {
|
|
2603
|
+
chatId, key, messageId: replyTo, conversationWorkspace: menu.conversationWorkspace,
|
|
2604
|
+
});
|
|
2598
2605
|
return;
|
|
2599
2606
|
}
|
|
2600
2607
|
if (menu.kind === 'workspaces') {
|
|
@@ -2624,6 +2631,12 @@ export class FeishuHarnessBridge {
|
|
|
2624
2631
|
return sessions;
|
|
2625
2632
|
}
|
|
2626
2633
|
|
|
2634
|
+
#conversationWorkspace(key) {
|
|
2635
|
+
return typeof this.#harness.currentConversationWorkspace === 'function'
|
|
2636
|
+
? this.#harness.currentConversationWorkspace(key)
|
|
2637
|
+
: this.#harness.currentWorkspace?.();
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2627
2640
|
async #showSessions(
|
|
2628
2641
|
{ chatId, key, replyTo = null },
|
|
2629
2642
|
selector,
|
|
@@ -2631,13 +2644,20 @@ export class FeishuHarnessBridge {
|
|
|
2631
2644
|
{ updateMessageId = null, limit = null } = {},
|
|
2632
2645
|
) {
|
|
2633
2646
|
try {
|
|
2647
|
+
const conversationWorkspace = this.#conversationWorkspace(key);
|
|
2634
2648
|
const signal = this.#cardDataSignal();
|
|
2635
|
-
const resolved = await resolveSessionListWorkspace(selector ?? '', this.#harness, {
|
|
2649
|
+
const resolved = await resolveSessionListWorkspace(selector ?? '', this.#harness, {
|
|
2650
|
+
signal, conversationKey: key,
|
|
2651
|
+
});
|
|
2636
2652
|
if (resolved.error) {
|
|
2637
2653
|
await this.#send(chatId, resolved.error, { replyTo });
|
|
2638
2654
|
return;
|
|
2639
2655
|
}
|
|
2640
2656
|
const listed = await this.#harness.listWorkspaceSessions(resolved.workspace, { signal });
|
|
2657
|
+
if (conversationWorkspace !== undefined && conversationWorkspace !== this.#conversationWorkspace(key)) {
|
|
2658
|
+
await this.#send(chatId, t('这个菜单已过期,请回复 /m 重新打开。'), { replyTo });
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2641
2661
|
const visibleSessions = this.#visibleSessions(Array.isArray(listed?.sessions) ? listed.sessions : []);
|
|
2642
2662
|
const sessionLimit = Number.isSafeInteger(limit) && limit > 0 ? limit : null;
|
|
2643
2663
|
const sessions = sessionLimit === null
|
|
@@ -2656,6 +2676,7 @@ export class FeishuHarnessBridge {
|
|
|
2656
2676
|
const pageSlice = sessions.slice(safePage * MENU_PAGE_SIZE, (safePage + 1) * MENU_PAGE_SIZE);
|
|
2657
2677
|
this.#rememberMenu(key, {
|
|
2658
2678
|
kind: 'sessions',
|
|
2679
|
+
conversationWorkspace,
|
|
2659
2680
|
sessions: pageSlice.map((session) => ({ ...session, watched: watchedSet.has(session.sessionId) })),
|
|
2660
2681
|
});
|
|
2661
2682
|
await this.#sendCard(
|
|
@@ -2665,6 +2686,9 @@ export class FeishuHarnessBridge {
|
|
|
2665
2686
|
key,
|
|
2666
2687
|
updateMessageId,
|
|
2667
2688
|
replyTo,
|
|
2689
|
+
// The effective conversation workspace is separate from an explicit
|
|
2690
|
+
// list selector, which may intentionally point at another workspace.
|
|
2691
|
+
conversationWorkspace,
|
|
2668
2692
|
// Keep the canonical selector result for later page callbacks. The
|
|
2669
2693
|
// list response's workspace is display data and is not authoritative.
|
|
2670
2694
|
sessionWorkspace: resolved.workspace,
|
|
@@ -2732,6 +2756,7 @@ export class FeishuHarnessBridge {
|
|
|
2732
2756
|
this.#cardKeys.set(messageId, {
|
|
2733
2757
|
key: options.key,
|
|
2734
2758
|
chatId,
|
|
2759
|
+
conversationWorkspace: options.conversationWorkspace,
|
|
2735
2760
|
sessionWorkspace: typeof options.sessionWorkspace === 'string' && options.sessionWorkspace
|
|
2736
2761
|
? options.sessionWorkspace
|
|
2737
2762
|
: null,
|
|
@@ -2850,13 +2875,14 @@ export class FeishuHarnessBridge {
|
|
|
2850
2875
|
}
|
|
2851
2876
|
|
|
2852
2877
|
async #sendMenuCard(key, chatId, { updateMessageId = null, replyTo = null } = {}) {
|
|
2878
|
+
const conversationWorkspace = this.#conversationWorkspace(key);
|
|
2853
2879
|
let currentSessionId = null;
|
|
2854
2880
|
let directSessionTitle = null;
|
|
2855
2881
|
try {
|
|
2856
2882
|
const sessionId = this.#state.sessionFor(key);
|
|
2857
2883
|
if (typeof sessionId === 'string' && sessionId) {
|
|
2858
2884
|
currentSessionId = sessionId;
|
|
2859
|
-
const session = this.#harness.workspaceSession?.(sessionId);
|
|
2885
|
+
const session = this.#harness.workspaceSession?.(sessionId, key);
|
|
2860
2886
|
directSessionTitle = nonEmptyString(session?.title)
|
|
2861
2887
|
?? nonEmptyString(session?.name)
|
|
2862
2888
|
?? nonEmptyString(session?.displayName);
|
|
@@ -2874,12 +2900,13 @@ export class FeishuHarnessBridge {
|
|
|
2874
2900
|
return { current, paths: current ? [current] : [] };
|
|
2875
2901
|
});
|
|
2876
2902
|
const sessionTask = (async () => {
|
|
2877
|
-
|
|
2878
|
-
? this.#harness.currentWorkspace()
|
|
2879
|
-
: null;
|
|
2880
|
-
if (!current || typeof this.#harness.listWorkspaceSessions !== 'function') return [];
|
|
2903
|
+
if (typeof this.#harness.listWorkspaceSessions !== 'function') return [];
|
|
2881
2904
|
try {
|
|
2882
|
-
const
|
|
2905
|
+
const resolved = await resolveSessionListWorkspace('', this.#harness, {
|
|
2906
|
+
signal: dataSignal, conversationKey: key,
|
|
2907
|
+
});
|
|
2908
|
+
if (resolved.error) return [];
|
|
2909
|
+
const listed = await this.#harness.listWorkspaceSessions(resolved.workspace, { signal: dataSignal });
|
|
2883
2910
|
return this.#visibleSessions(Array.isArray(listed?.sessions) ? listed.sessions : []);
|
|
2884
2911
|
} catch {
|
|
2885
2912
|
return [];
|
|
@@ -2896,7 +2923,7 @@ export class FeishuHarnessBridge {
|
|
|
2896
2923
|
const modelTask = (async () => {
|
|
2897
2924
|
try {
|
|
2898
2925
|
if (currentSessionId) {
|
|
2899
|
-
const session = this.#harness.workspaceSession?.(currentSessionId);
|
|
2926
|
+
const session = this.#harness.workspaceSession?.(currentSessionId, key);
|
|
2900
2927
|
if (typeof session?.models === 'function') {
|
|
2901
2928
|
return await session.models({ signal: dataSignal });
|
|
2902
2929
|
}
|
|
@@ -2913,6 +2940,10 @@ export class FeishuHarnessBridge {
|
|
|
2913
2940
|
presetTask,
|
|
2914
2941
|
modelTask,
|
|
2915
2942
|
]);
|
|
2943
|
+
if (conversationWorkspace !== undefined && conversationWorkspace !== this.#conversationWorkspace(key)) {
|
|
2944
|
+
await this.#send(chatId, t('这个菜单已过期,请回复 /m 重新打开。'), { replyTo });
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2916
2947
|
const workspaces = Array.isArray(snapshot.paths) ? snapshot.paths : [];
|
|
2917
2948
|
const currentWorkspace = snapshot.current ?? null;
|
|
2918
2949
|
const currentMatch = listedSessions.find((session) => session.sessionId === currentSessionId);
|
|
@@ -2944,7 +2975,7 @@ export class FeishuHarnessBridge {
|
|
|
2944
2975
|
currentSession: currentSessionId ? { id: currentSessionId, title: currentSessionTitle } : null,
|
|
2945
2976
|
sessions, archiveVisible, presetCatalog, modelCatalog,
|
|
2946
2977
|
}),
|
|
2947
|
-
{ key, updateMessageId, replyTo },
|
|
2978
|
+
{ key, updateMessageId, replyTo, conversationWorkspace },
|
|
2948
2979
|
);
|
|
2949
2980
|
}
|
|
2950
2981
|
|
|
@@ -2956,7 +2987,7 @@ export class FeishuHarnessBridge {
|
|
|
2956
2987
|
async #resolveSessionTitle(key, sessionId) {
|
|
2957
2988
|
try {
|
|
2958
2989
|
if (typeof this.#harness.workspaceSession === 'function') {
|
|
2959
|
-
const session = this.#harness.workspaceSession(sessionId);
|
|
2990
|
+
const session = this.#harness.workspaceSession(sessionId, key);
|
|
2960
2991
|
if (session && typeof session === 'object') {
|
|
2961
2992
|
const direct = nonEmptyString(session.title)
|
|
2962
2993
|
?? nonEmptyString(session.name)
|
|
@@ -3009,7 +3040,7 @@ export class FeishuHarnessBridge {
|
|
|
3009
3040
|
const sessionId = this.#state?.sessionFor?.(key);
|
|
3010
3041
|
let catalog;
|
|
3011
3042
|
if (typeof sessionId === 'string' && sessionId) {
|
|
3012
|
-
const session = this.#harness.workspaceSession(sessionId);
|
|
3043
|
+
const session = this.#harness.workspaceSession(sessionId, key);
|
|
3013
3044
|
if (session?.models) {
|
|
3014
3045
|
catalog = await session.models({ signal });
|
|
3015
3046
|
}
|
|
@@ -3078,7 +3109,7 @@ export class FeishuHarnessBridge {
|
|
|
3078
3109
|
try {
|
|
3079
3110
|
const sessionId = this.#state?.sessionFor?.(key);
|
|
3080
3111
|
if (typeof sessionId === 'string' && sessionId) {
|
|
3081
|
-
const session = this.#harness.workspaceSession(sessionId);
|
|
3112
|
+
const session = this.#harness.workspaceSession(sessionId, key);
|
|
3082
3113
|
if (session?.models) {
|
|
3083
3114
|
const cat = await session.models({ signal });
|
|
3084
3115
|
if (cat.current) info.model = `${cat.current.provider}/${cat.current.model}`;
|
|
@@ -702,7 +702,10 @@ export class QqHarnessBridge {
|
|
|
702
702
|
}
|
|
703
703
|
|
|
704
704
|
#menuContext(key) {
|
|
705
|
-
|
|
705
|
+
const workspace = this.#harness.currentWorkspace?.();
|
|
706
|
+
const sessionWorkspace = typeof this.#harness.currentConversationWorkspace === 'function'
|
|
707
|
+
? this.#harness.currentConversationWorkspace(key) : workspace;
|
|
708
|
+
return { workspace, sessionWorkspace, sessionId: this.#state.sessionFor(key) };
|
|
706
709
|
}
|
|
707
710
|
|
|
708
711
|
async #showMenu(message, key, name, pageView = null) {
|
|
@@ -716,7 +719,8 @@ export class QqHarnessBridge {
|
|
|
716
719
|
this.#signal?.throwIfAborted();
|
|
717
720
|
this.#harness.assertWorkspaceScope?.();
|
|
718
721
|
const current = this.#menuContext(key);
|
|
719
|
-
if (current.workspace !== context.workspace || current.
|
|
722
|
+
if (current.workspace !== context.workspace || current.sessionWorkspace !== context.sessionWorkspace
|
|
723
|
+
|| current.sessionId !== context.sessionId) {
|
|
720
724
|
return { message: t('会话或工作区已变化,请重新发送 /m。') };
|
|
721
725
|
}
|
|
722
726
|
if (!this.#menus.publish(key, actor, entry, view)) return { messages: [] };
|
|
@@ -753,7 +757,9 @@ export class QqHarnessBridge {
|
|
|
753
757
|
const execute = async () => {
|
|
754
758
|
this.#signal?.throwIfAborted();
|
|
755
759
|
const current = this.#menuContext(key);
|
|
756
|
-
if (current.workspace !== choice.context.workspace
|
|
760
|
+
if (current.workspace !== choice.context.workspace
|
|
761
|
+
|| current.sessionWorkspace !== choice.context.sessionWorkspace
|
|
762
|
+
|| current.sessionId !== choice.context.sessionId) {
|
|
757
763
|
return { message: t('会话或工作区已变化,请重新发送 /m。') };
|
|
758
764
|
}
|
|
759
765
|
// A prompt may have started while this action waited for a prior menu command.
|
|
@@ -764,7 +770,9 @@ export class QqHarnessBridge {
|
|
|
764
770
|
if (command === '/new') return withSessionBindingLock(this.#state, key, async () => {
|
|
765
771
|
if (isBusy()) return { message: t('当前任务仍在运行,请先停止任务或等待任务完成后再执行此操作。') };
|
|
766
772
|
const locked = this.#menuContext(key);
|
|
767
|
-
if (locked.workspace !== choice.context.workspace
|
|
773
|
+
if (locked.workspace !== choice.context.workspace
|
|
774
|
+
|| locked.sessionWorkspace !== choice.context.sessionWorkspace
|
|
775
|
+
|| locked.sessionId !== choice.context.sessionId) {
|
|
768
776
|
return { message: t('会话或工作区已变化,请重新发送 /m。') };
|
|
769
777
|
}
|
|
770
778
|
await this.#state.clearSession(key);
|
|
@@ -55,7 +55,8 @@ export class QqMenuStore {
|
|
|
55
55
|
const entry = this.#entries.get(this.#key(route, actor));
|
|
56
56
|
if (!entry || entry.used || !entry.view || entry.expiresAt <= this.#now()
|
|
57
57
|
|| (token && token !== entry.token)) return { error: t('这个菜单已过期,请回复 /m 重新打开。') };
|
|
58
|
-
if (entry.workspace !== context.workspace || entry.
|
|
58
|
+
if (entry.workspace !== context.workspace || entry.sessionWorkspace !== context.sessionWorkspace
|
|
59
|
+
|| entry.sessionId !== context.sessionId) {
|
|
59
60
|
entry.used = true;
|
|
60
61
|
return { error: t('会话或工作区已变化,请重新发送 /m。') };
|
|
61
62
|
}
|
|
@@ -80,13 +81,15 @@ export function qqMenuPage(list, requestedPage = 0) {
|
|
|
80
81
|
list.choices.length ? '' : t('暂无可用选项。')].filter(Boolean).join('\n'), entries, columns: 2 };
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
async function catalogFor(harness, sessionId, options) {
|
|
84
|
-
const session = sessionId ? harness.workspaceSession?.(sessionId) : null;
|
|
84
|
+
async function catalogFor(harness, sessionId, key, options) {
|
|
85
|
+
const session = sessionId ? harness.workspaceSession?.(sessionId, key) : null;
|
|
85
86
|
return typeof session?.models === 'function' ? session.models(options) : harness.listModels(options);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
export async function qqMenuView(name, harness, state, key, { signal, busy = false } = {}) {
|
|
89
90
|
const workspace = harness.currentWorkspace?.();
|
|
91
|
+
const sessionWorkspace = typeof harness.currentConversationWorkspace === 'function'
|
|
92
|
+
? harness.currentConversationWorkspace(key) : workspace;
|
|
90
93
|
const sessionId = state.sessionFor(key);
|
|
91
94
|
const options = { signal };
|
|
92
95
|
const archived = state.includesArchivedSessions?.() === true;
|
|
@@ -94,8 +97,8 @@ export async function qqMenuView(name, harness, state, key, { signal, busy = fal
|
|
|
94
97
|
if (name === 'main' || name === 'status') {
|
|
95
98
|
if (name === 'status') await harness.ensureRunning(options);
|
|
96
99
|
const results = await Promise.allSettled([
|
|
97
|
-
harness.listWorkspaceSessions?.(workspace, options),
|
|
98
|
-
catalogFor(harness, sessionId, options),
|
|
100
|
+
harness.listWorkspaceSessions?.(name === 'main' ? sessionWorkspace : workspace, options),
|
|
101
|
+
catalogFor(harness, sessionId, key, options),
|
|
99
102
|
harness.agentPresetSettings?.(options),
|
|
100
103
|
]);
|
|
101
104
|
signal?.throwIfAborted();
|
|
@@ -125,8 +128,8 @@ export async function qqMenuView(name, harness, state, key, { signal, busy = fal
|
|
|
125
128
|
] };
|
|
126
129
|
}
|
|
127
130
|
if (name === 'sessions') {
|
|
128
|
-
const listed = await harness.listWorkspaceSessions(
|
|
129
|
-
return qqMenuPage({ title: t('📋 会话列表'), detail: clean(
|
|
131
|
+
const listed = await harness.listWorkspaceSessions(sessionWorkspace, options);
|
|
132
|
+
return qqMenuPage({ title: t('📋 会话列表'), detail: clean(sessionWorkspace), choices: visibleSessions(listed).map((item) =>
|
|
130
133
|
command(`${item.sessionId === sessionId ? '✓ ' : ''}${clean(item.title || item.sessionId, 70)}${item.archived ? t('(已归档)') : ''}`, `/session ${item.sessionId}`)) });
|
|
131
134
|
}
|
|
132
135
|
if (name === 'workspaces') {
|
|
@@ -135,7 +138,7 @@ export async function qqMenuView(name, harness, state, key, { signal, busy = fal
|
|
|
135
138
|
command(`${path === workspace ? '✓ ' : ''}${clean(path)}`, `/workspace ${path}`)) });
|
|
136
139
|
}
|
|
137
140
|
if (name === 'models') {
|
|
138
|
-
const catalog = await catalogFor(harness, sessionId, options);
|
|
141
|
+
const catalog = await catalogFor(harness, sessionId, key, options);
|
|
139
142
|
return qqMenuPage({ title: t('🧠 模型'), detail: catalog.failures?.length ? t('部分模型暂不可用,可稍后重试。') : '',
|
|
140
143
|
choices: catalog.groups.flatMap((group) => group.models.map((model) => command(
|
|
141
144
|
`${catalog.current?.provider === group.id && catalog.current?.model === model.id ? '✓ ' : ''}${clean(group.name, 30)} · ${clean(model.name, 60)}`,
|