@xmanrui/dsh-im 4.20.2 → 4.21.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 +21 -9
- package/README.md +21 -9
- package/lib/client.js +108 -21
- package/lib/index.js +276 -276
- package/package.json +17 -1
- package/plugin-src/client/channel-logos.js +18 -5
- package/plugin-src/client/channels/imessage/styles.js +1 -1
- package/plugin-src/client/channels/slack/styles.js +1 -1
- package/plugin-src/client/channels/weixin/connection-error.js +4 -1
- package/plugin-src/client/i18n.js +8 -0
- package/plugin-src/client/model-setting.js +4 -2
- package/plugin-src/client/session-channel-logos.js +1 -2
- package/plugin-src/client/styles.js +3 -2
- package/plugin-src/host/channels/qq/production.mjs +1 -1
- package/plugin-src/host/channels/qq/rpc.mjs +2 -1
- package/plugin-src/host/modern-harness-api.mjs +91 -3
- package/scripts/verify-model-setting.mjs +4 -1
- package/scripts/verify-package.mjs +3 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +1 -0
- package/src/channels/discord/discord-runtime.mjs +4 -1
- package/src/channels/feishu/bridge.mjs +9 -1
- package/src/channels/feishu/feishu-channel.mjs +1 -1
- package/src/channels/qq/qq-bridge.mjs +9 -1
- package/src/channels/qq/qq-controller.mjs +11 -5
- package/src/channels/qq/state-error.mjs +17 -0
- package/src/channels/qq/state-store.mjs +35 -9
- package/src/channels/shared/batch-input.mjs +22 -2
- package/src/channels/shared/bot-workspace-store.mjs +46 -26
- package/src/channels/shared/config-read-error.mjs +24 -0
- package/src/channels/shared/harness-client.mjs +13 -12
- package/src/channels/shared/harness-question.mjs +10 -2
- package/src/channels/shared/i18n-en/qq.mjs +3 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +11 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +2 -0
- package/src/channels/shared/semantic/artifact.mjs +1 -1
- package/src/channels/shared/text-harness-bridge.mjs +198 -3
- package/src/channels/shared/token-config-store.mjs +23 -8
- package/src/channels/shared/workspace-session.mjs +7 -1
- package/src/channels/slack/slack-runtime.mjs +3 -1
- package/src/channels/telegram/telegram-api.mjs +62 -2
- package/src/channels/telegram/telegram-bridge.mjs +66 -1
- package/src/channels/telegram/telegram-rich-message.mjs +6 -4
- package/src/channels/telegram/telegram-runtime.mjs +128 -6
- package/src/channels/wecom/wecom-bridge.mjs +4 -0
- package/src/channels/wecom-app/config-store.mjs +3 -1
- package/src/channels/wecom-app/wecom-app-bridge.mjs +1 -0
- package/src/channels/weixin/config-store.mjs +24 -15
- package/src/channels/weixin/connection-error.en.mjs +21 -0
- package/src/channels/weixin/connection-error.mjs +21 -6
- package/src/channels/weixin/diagnostic-details.mjs +24 -1
- package/src/channels/weixin/weixin-bridge.mjs +1 -0
|
@@ -33,6 +33,18 @@ function submissionPrompt(messages) {
|
|
|
33
33
|
].join('\n\n');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Session title for one submission.
|
|
38
|
+
*
|
|
39
|
+
* The submitted prompt is dsh-im's own composition -- a framing sentence plus
|
|
40
|
+
* `[消息 N]` labels -- so using it as the conversation title would put plugin
|
|
41
|
+
* text where the user's own words belong. The first collected message is what
|
|
42
|
+
* the user actually said first.
|
|
43
|
+
*/
|
|
44
|
+
function submissionTitle(messages) {
|
|
45
|
+
return messages.find((message) => message.trim()) ?? '';
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
export function isBatchInputCommand(text) {
|
|
37
49
|
return commandName(text) !== null;
|
|
38
50
|
}
|
|
@@ -73,7 +85,10 @@ export class BatchInputManager {
|
|
|
73
85
|
|
|
74
86
|
if (!batch) {
|
|
75
87
|
if (!name) return { handled: false };
|
|
76
|
-
|
|
88
|
+
// Only starting a batch requires the plain-text command itself; /send and
|
|
89
|
+
// /cancel carry no content, so they must answer with their own state
|
|
90
|
+
// message instead of being refused as uncollectable content.
|
|
91
|
+
if (!plainText && name === 'batch') {
|
|
77
92
|
return result('unsupported-content', t('批量输入命令仅支持纯文字,请移除图片、文件或引用消息后重试。'));
|
|
78
93
|
}
|
|
79
94
|
if (name === 'send') {
|
|
@@ -90,7 +105,11 @@ export class BatchInputManager {
|
|
|
90
105
|
});
|
|
91
106
|
}
|
|
92
107
|
|
|
93
|
-
|
|
108
|
+
// A command is never collected content: /send, /cancel and a repeated
|
|
109
|
+
// /batch must keep working even when the message that carries them is a
|
|
110
|
+
// quoted reply, an image caption or a file. Only the collected text itself
|
|
111
|
+
// has to be plain.
|
|
112
|
+
if (!plainText && batch.phase === 'collecting' && !name) {
|
|
94
113
|
return result('unsupported-content', t(`批量输入模式目前仅支持文字,不支持图片、文件或引用消息,这条消息未收录。
|
|
95
114
|
请继续发送文字,或使用 /send、/cancel。`), {
|
|
96
115
|
count: batch.messages.length,
|
|
@@ -141,6 +160,7 @@ export class BatchInputManager {
|
|
|
141
160
|
token,
|
|
142
161
|
messages,
|
|
143
162
|
prompt: submissionPrompt(messages),
|
|
163
|
+
title: submissionTitle(messages),
|
|
144
164
|
count: messages.length,
|
|
145
165
|
});
|
|
146
166
|
}
|
|
@@ -32,11 +32,16 @@ import {
|
|
|
32
32
|
validateModelSelection,
|
|
33
33
|
} from './model-setting.mjs';
|
|
34
34
|
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
35
|
+
import { configValidationError, withConfigResource } from './config-read-error.mjs';
|
|
35
36
|
|
|
36
37
|
const DELIVERY_DOCUMENT_VERSION = 2;
|
|
37
38
|
export const CURRENT_DOCUMENT_VERSION = 3;
|
|
38
39
|
const EMPTY_DOCUMENT = Object.freeze({ version: 1, workspaces: Object.freeze({}) });
|
|
39
40
|
|
|
41
|
+
function invalidWorkspaceConfig(field, issue) {
|
|
42
|
+
throw configValidationError('dsh-im workspace config is invalid', field, issue);
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
function workspaceSessionStale(message) {
|
|
41
46
|
const error = new Error(message);
|
|
42
47
|
error.code = WORKSPACE_SESSION_STALE;
|
|
@@ -185,13 +190,17 @@ function sameDeliveryRoute(left, right) {
|
|
|
185
190
|
function normalizeDeliveryTargets(value, { version } = {}) {
|
|
186
191
|
const deliveryTargets = Object.create(null);
|
|
187
192
|
if (value === undefined) return deliveryTargets;
|
|
188
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return
|
|
193
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return invalidWorkspaceConfig('deliveryTargets', 'expected-object');
|
|
194
|
+
let field = 'deliveryTargets';
|
|
189
195
|
try {
|
|
190
|
-
for (const [botId, targets] of Object.entries(value)) {
|
|
196
|
+
for (const [botIndex, [botId, targets]] of Object.entries(value).entries()) {
|
|
197
|
+
field = `deliveryTargets[${botIndex}].key`;
|
|
191
198
|
botIdOf(botId);
|
|
192
|
-
|
|
199
|
+
field = `deliveryTargets[${botIndex}].targets`;
|
|
200
|
+
if (!targets || typeof targets !== 'object' || Array.isArray(targets)) throw new TypeError('Invalid targets');
|
|
193
201
|
const normalizedTargets = Object.create(null);
|
|
194
|
-
for (const [targetId, target] of Object.entries(targets)) {
|
|
202
|
+
for (const [targetIndex, [targetId, target]] of Object.entries(targets).entries()) {
|
|
203
|
+
field = `deliveryTargets[${botIndex}].targets[${targetIndex}]`;
|
|
195
204
|
// Backward compatibility: some released builds persisted the target id
|
|
196
205
|
// inside the stored object as well. Accept a redundant targetId that
|
|
197
206
|
// matches the map key when loading a stored document; a mismatch stays
|
|
@@ -215,7 +224,7 @@ function normalizeDeliveryTargets(value, { version } = {}) {
|
|
|
215
224
|
deliveryTargets[botId] = normalizedTargets;
|
|
216
225
|
}
|
|
217
226
|
} catch {
|
|
218
|
-
return
|
|
227
|
+
return invalidWorkspaceConfig(field, 'invalid-delivery-target');
|
|
219
228
|
}
|
|
220
229
|
return deliveryTargets;
|
|
221
230
|
}
|
|
@@ -241,44 +250,44 @@ function normalizeAccessPolicies(value, workspaces) {
|
|
|
241
250
|
}
|
|
242
251
|
|
|
243
252
|
function normalizeDocument(value) {
|
|
244
|
-
if (!value ||
|
|
245
|
-
|
|
246
|
-
|
|
253
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return invalidWorkspaceConfig('$', 'expected-object');
|
|
254
|
+
if (![1, DELIVERY_DOCUMENT_VERSION, CURRENT_DOCUMENT_VERSION].includes(value.version)) return invalidWorkspaceConfig('version', 'unsupported-version');
|
|
255
|
+
if (!value.workspaces || typeof value.workspaces !== 'object' || Array.isArray(value.workspaces)) return invalidWorkspaceConfig('workspaces', 'expected-object');
|
|
247
256
|
const workspaces = {};
|
|
248
|
-
for (const [botId, workspace] of Object.entries(value.workspaces)) {
|
|
249
|
-
if (!/^[A-Za-z0-9_-]{1,128}$/.test(botId)
|
|
250
|
-
|
|
257
|
+
for (const [index, [botId, workspace]] of Object.entries(value.workspaces).entries()) {
|
|
258
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(botId)) return invalidWorkspaceConfig(`workspaces[${index}].key`, 'invalid-identifier');
|
|
259
|
+
if (typeof workspace !== 'string' || !isAbsolute(workspace)) return invalidWorkspaceConfig(`workspaces[${index}].value`, 'invalid-workspace-path');
|
|
251
260
|
workspaces[botId] = resolve(workspace);
|
|
252
261
|
}
|
|
253
262
|
const conversationWorkspaces = normalizeConversationWorkspaces(value.conversationWorkspaces);
|
|
254
263
|
let agentPresets = {};
|
|
255
264
|
if (value.agentPresets !== undefined) {
|
|
256
265
|
if (!value.agentPresets || typeof value.agentPresets !== 'object'
|
|
257
|
-
|| Array.isArray(value.agentPresets)) return
|
|
258
|
-
for (const [botId, agentPreset] of Object.entries(value.agentPresets)) {
|
|
259
|
-
if (!/^[A-Za-z0-9_-]{1,128}$/.test(botId)) return
|
|
266
|
+
|| Array.isArray(value.agentPresets)) return invalidWorkspaceConfig('agentPresets', 'expected-object');
|
|
267
|
+
for (const [index, [botId, agentPreset]] of Object.entries(value.agentPresets).entries()) {
|
|
268
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(botId)) return invalidWorkspaceConfig(`agentPresets[${index}].key`, 'invalid-identifier');
|
|
260
269
|
try {
|
|
261
270
|
const normalized = validateAgentPresetId(agentPreset);
|
|
262
|
-
if (!normalized) return
|
|
271
|
+
if (!normalized) return invalidWorkspaceConfig(`agentPresets[${index}].value`, 'invalid-agent-preset');
|
|
263
272
|
agentPresets[botId] = normalized;
|
|
264
273
|
} catch {
|
|
265
|
-
return
|
|
274
|
+
return invalidWorkspaceConfig(`agentPresets[${index}].value`, 'invalid-agent-preset');
|
|
266
275
|
}
|
|
267
276
|
}
|
|
268
277
|
}
|
|
269
278
|
const models = {};
|
|
270
279
|
if (value.models !== undefined) {
|
|
271
280
|
if (!value.models || typeof value.models !== 'object' || Array.isArray(value.models)) {
|
|
272
|
-
return
|
|
281
|
+
return invalidWorkspaceConfig('models', 'expected-object');
|
|
273
282
|
}
|
|
274
|
-
for (const [botId, model] of Object.entries(value.models)) {
|
|
275
|
-
if (!/^[A-Za-z0-9_-]{1,128}$/.test(botId)) return
|
|
283
|
+
for (const [index, [botId, model]] of Object.entries(value.models).entries()) {
|
|
284
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(botId)) return invalidWorkspaceConfig(`models[${index}].key`, 'invalid-identifier');
|
|
276
285
|
try {
|
|
277
286
|
const normalized = validateModelSelection(model);
|
|
278
|
-
if (!normalized) return
|
|
287
|
+
if (!normalized) return invalidWorkspaceConfig(`models[${index}].value`, 'invalid-model-selection');
|
|
279
288
|
models[botId] = normalized;
|
|
280
289
|
} catch {
|
|
281
|
-
return
|
|
290
|
+
return invalidWorkspaceConfig(`models[${index}].value`, 'invalid-model-selection');
|
|
282
291
|
}
|
|
283
292
|
}
|
|
284
293
|
}
|
|
@@ -292,9 +301,8 @@ function normalizeDocument(value) {
|
|
|
292
301
|
}
|
|
293
302
|
}
|
|
294
303
|
}
|
|
295
|
-
if (value.version === 1 && value.deliveryTargets !== undefined) return
|
|
304
|
+
if (value.version === 1 && value.deliveryTargets !== undefined) return invalidWorkspaceConfig('deliveryTargets', 'unexpected-field');
|
|
296
305
|
const deliveryTargets = normalizeDeliveryTargets(value.deliveryTargets, { version: value.version });
|
|
297
|
-
if (!deliveryTargets) return null;
|
|
298
306
|
const aliases = Object.create(null);
|
|
299
307
|
if (value.aliases && typeof value.aliases === 'object' && !Array.isArray(value.aliases)) {
|
|
300
308
|
for (const [botId, alias] of Object.entries(value.aliases)) {
|
|
@@ -421,7 +429,6 @@ export class BotWorkspaceStore {
|
|
|
421
429
|
async load() {
|
|
422
430
|
try {
|
|
423
431
|
const normalized = normalizeDocument(JSON.parse(await readFile(this.#path, 'utf8')));
|
|
424
|
-
if (!normalized) throw new Error('dsh-im workspace config is invalid');
|
|
425
432
|
this.#version = normalized.version;
|
|
426
433
|
this.#workspaces = normalized.workspaces;
|
|
427
434
|
this.#agentPresets = normalized.agentPresets;
|
|
@@ -432,7 +439,7 @@ export class BotWorkspaceStore {
|
|
|
432
439
|
this.#accessPolicies = normalized.accessPolicies;
|
|
433
440
|
this.#conversationWorkspaces = normalized.conversationWorkspaces;
|
|
434
441
|
} catch (error) {
|
|
435
|
-
if (error?.code !== 'ENOENT') throw error;
|
|
442
|
+
if (error?.code !== 'ENOENT') throw withConfigResource(error, 'workspace-config');
|
|
436
443
|
this.#version = 1;
|
|
437
444
|
this.#workspaces = {};
|
|
438
445
|
this.#agentPresets = {};
|
|
@@ -1371,6 +1378,18 @@ export class BotWorkspaceStore {
|
|
|
1371
1378
|
await this.#persist();
|
|
1372
1379
|
return;
|
|
1373
1380
|
}
|
|
1381
|
+
try {
|
|
1382
|
+
const parent = await stat(dirname(this.#path));
|
|
1383
|
+
if (!parent.isDirectory()) {
|
|
1384
|
+
const error = new Error('workspace config parent is not a directory');
|
|
1385
|
+
error.code = 'ENOTDIR';
|
|
1386
|
+
throw error;
|
|
1387
|
+
}
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
1390
|
+
this.#dirtyRemovals.clear();
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1374
1393
|
try {
|
|
1375
1394
|
await unlink(this.#path);
|
|
1376
1395
|
this.#dirtyRemovals.clear();
|
|
@@ -2277,7 +2296,8 @@ export function createWorkspaceAwareController(controller, {
|
|
|
2277
2296
|
await state.clearSessions();
|
|
2278
2297
|
} catch (error) {
|
|
2279
2298
|
console.warn(
|
|
2280
|
-
|
|
2299
|
+
'[dsh-im] ignored session cleanup failure while deleting bot:',
|
|
2300
|
+
botId,
|
|
2281
2301
|
error?.message ?? error,
|
|
2282
2302
|
);
|
|
2283
2303
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Metadata belongs to the validation/read boundary, never to parser excerpts or
|
|
2
|
+
// arbitrary error properties. Keep the original exception type/message so other
|
|
3
|
+
// channels retain their existing startup classification.
|
|
4
|
+
const details = new WeakMap();
|
|
5
|
+
|
|
6
|
+
export function configValidationError(message, field, issue) {
|
|
7
|
+
const error = new Error(message);
|
|
8
|
+
details.set(error, { reason: 'invalid-config', field, issue });
|
|
9
|
+
return error;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function withConfigResource(error, resource) {
|
|
13
|
+
if (error && typeof error === 'object') {
|
|
14
|
+
details.set(error, {
|
|
15
|
+
...details.get(error), resource,
|
|
16
|
+
...(error instanceof SyntaxError ? { reason: 'invalid-json' } : {}),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function configReadErrorDetails(error) {
|
|
23
|
+
return details.get(error);
|
|
24
|
+
}
|
|
@@ -893,7 +893,7 @@ export class HarnessClient {
|
|
|
893
893
|
stdio: ['ignore', 'inherit', 'inherit'],
|
|
894
894
|
});
|
|
895
895
|
this.#managedProcess.on('error', (error) => {
|
|
896
|
-
console.error(
|
|
896
|
+
console.error('[dsh-im] failed to start Harness:', this.#logPrefix, error.message);
|
|
897
897
|
});
|
|
898
898
|
}
|
|
899
899
|
|
|
@@ -1123,7 +1123,7 @@ export class HarnessClient {
|
|
|
1123
1123
|
});
|
|
1124
1124
|
} catch (error) {
|
|
1125
1125
|
if (signal.aborted) return;
|
|
1126
|
-
console.warn(
|
|
1126
|
+
console.warn('[dsh-im] Harness interaction stream disconnected:', this.#logPrefix, error.message);
|
|
1127
1127
|
}
|
|
1128
1128
|
if (signal.aborted) return;
|
|
1129
1129
|
try {
|
|
@@ -1462,7 +1462,7 @@ export class HarnessClient {
|
|
|
1462
1462
|
deliveredArtifactCount += 1;
|
|
1463
1463
|
} catch (error) {
|
|
1464
1464
|
outboundArtifactRegistry.release(artifact);
|
|
1465
|
-
console.warn(
|
|
1465
|
+
console.warn('[dsh-im] ignored an artifact handoff failure:', this.#logPrefix, error.message);
|
|
1466
1466
|
}
|
|
1467
1467
|
}
|
|
1468
1468
|
return deliveredArtifactCount;
|
|
@@ -1534,7 +1534,8 @@ export class HarnessClient {
|
|
|
1534
1534
|
} catch (stagingError) {
|
|
1535
1535
|
if (signal?.aborted) throw signal.reason ?? stagingError;
|
|
1536
1536
|
console.warn(
|
|
1537
|
-
|
|
1537
|
+
'[dsh-im] unable to restage rejected images as workspace files:',
|
|
1538
|
+
this.#logPrefix,
|
|
1538
1539
|
stagingError?.message ?? String(stagingError),
|
|
1539
1540
|
);
|
|
1540
1541
|
throw error;
|
|
@@ -1586,7 +1587,7 @@ export class HarnessClient {
|
|
|
1586
1587
|
try {
|
|
1587
1588
|
await onUpdate(update);
|
|
1588
1589
|
} catch (error) {
|
|
1589
|
-
console.warn(
|
|
1590
|
+
console.warn('[dsh-im] ignored a progress update failure:', this.#logPrefix, error.message);
|
|
1590
1591
|
}
|
|
1591
1592
|
}
|
|
1592
1593
|
}
|
|
@@ -1643,7 +1644,7 @@ export class HarnessClient {
|
|
|
1643
1644
|
try {
|
|
1644
1645
|
await staged?.cleanup?.();
|
|
1645
1646
|
} catch (error) {
|
|
1646
|
-
console.warn(
|
|
1647
|
+
console.warn('[dsh-im] unable to clean inbound files:', this.#logPrefix, error.message);
|
|
1647
1648
|
}
|
|
1648
1649
|
}
|
|
1649
1650
|
}
|
|
@@ -1677,7 +1678,7 @@ export class HarnessClient {
|
|
|
1677
1678
|
try {
|
|
1678
1679
|
onOpen?.();
|
|
1679
1680
|
} catch (error) {
|
|
1680
|
-
console.warn(
|
|
1681
|
+
console.warn('[dsh-im] ignored an interaction open callback failure:', this.#logPrefix, error.message);
|
|
1681
1682
|
}
|
|
1682
1683
|
if (ownership) {
|
|
1683
1684
|
void this.#refreshInteractionOwnerships(sessionId, signal).then(() => {
|
|
@@ -1769,7 +1770,7 @@ export class HarnessClient {
|
|
|
1769
1770
|
if (!ownershipReady) bufferedEnvelopes.push(envelope);
|
|
1770
1771
|
else processEnvelope(envelope);
|
|
1771
1772
|
} catch (error) {
|
|
1772
|
-
console.warn(
|
|
1773
|
+
console.warn('[dsh-im] ignored a malformed Harness interaction frame:', this.#logPrefix, error.message);
|
|
1773
1774
|
}
|
|
1774
1775
|
};
|
|
1775
1776
|
try {
|
|
@@ -1806,7 +1807,7 @@ export class HarnessClient {
|
|
|
1806
1807
|
try {
|
|
1807
1808
|
onReconnect?.();
|
|
1808
1809
|
} catch (error) {
|
|
1809
|
-
console.warn(
|
|
1810
|
+
console.warn('[dsh-im] mux reconnect hook failed:', this.#logPrefix, error.message);
|
|
1810
1811
|
}
|
|
1811
1812
|
},
|
|
1812
1813
|
onEnvelope: (envelope) => {
|
|
@@ -1822,13 +1823,13 @@ export class HarnessClient {
|
|
|
1822
1823
|
|| typeof payload.event !== 'object') return;
|
|
1823
1824
|
onSessionEvent({ sessionId: payload.sessionId, event: payload.event });
|
|
1824
1825
|
} catch (error) {
|
|
1825
|
-
console.warn(
|
|
1826
|
+
console.warn('[dsh-im] ignored a malformed global mux frame:', this.#logPrefix, error.message);
|
|
1826
1827
|
}
|
|
1827
1828
|
},
|
|
1828
1829
|
});
|
|
1829
1830
|
} catch (error) {
|
|
1830
1831
|
if (signal.aborted) return;
|
|
1831
|
-
console.warn(
|
|
1832
|
+
console.warn('[dsh-im] Harness event mux disconnected:', this.#logPrefix, error.message);
|
|
1832
1833
|
}
|
|
1833
1834
|
if (signal.aborted) return;
|
|
1834
1835
|
try {
|
|
@@ -1848,7 +1849,7 @@ export class HarnessClient {
|
|
|
1848
1849
|
rpcId: `${this.#rpcIdPrefix}-${randomUUID()}`,
|
|
1849
1850
|
...options,
|
|
1850
1851
|
onMalformed: (error) => {
|
|
1851
|
-
console.warn(
|
|
1852
|
+
console.warn('[dsh-im] ignored a malformed Harness mux frame:', this.#logPrefix, error.message);
|
|
1852
1853
|
},
|
|
1853
1854
|
});
|
|
1854
1855
|
}
|
|
@@ -16,7 +16,13 @@ export function validHarnessQuestion(question) {
|
|
|
16
16
|
))));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
/** Render the question prompt. `hasButtons` only swaps the closing hint: the
|
|
20
|
+
* numbered list stays, because a channel whose keyboard fails still answers by text.
|
|
21
|
+
*/
|
|
22
|
+
export function harnessQuestionText(question, index, total, {
|
|
23
|
+
requiresMention = false,
|
|
24
|
+
hasButtons = false,
|
|
25
|
+
} = {}) {
|
|
20
26
|
const lines = [];
|
|
21
27
|
const progress = total > 1 ? `(${index + 1}/${total})` : '';
|
|
22
28
|
lines.push(t('DeepSeek Harness 需要你补充信息{progress}:', { progress }));
|
|
@@ -34,7 +40,9 @@ export function harnessQuestionText(question, index, total, { requiresMention =
|
|
|
34
40
|
});
|
|
35
41
|
lines.push('', question.multiSelect === true
|
|
36
42
|
? t('请回复选项序号或文字;多选用逗号分隔,也可补充其他内容。')
|
|
37
|
-
:
|
|
43
|
+
: hasButtons
|
|
44
|
+
? t('请点击下方按钮选择,也可直接回复文字。')
|
|
45
|
+
: t('请回复一个选项序号或文字,也可直接输入其他答案。'));
|
|
38
46
|
} else {
|
|
39
47
|
lines.push('', t('请直接回复你的答案。'));
|
|
40
48
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// English translations (qq area). Keys are exact Chinese literals passed to t().
|
|
2
2
|
export default {
|
|
3
|
+
'无法读取 QQ 本地状态,请检查数据目录及访问权限。': 'Unable to read local QQ state. Check the data directory and access permissions.',
|
|
4
|
+
'QQ 本地状态已损坏,但无法备份,原文件已保留。': 'Local QQ state is corrupt and could not be backed up. The original file has been preserved.',
|
|
5
|
+
'无法保存 QQ 本地状态,请检查磁盘空间及目录写入权限。': 'Unable to save local QQ state. Check disk space and directory write permissions.',
|
|
3
6
|
'返回主菜单': 'Back to main menu',
|
|
4
7
|
'模式/预设': 'Mode / preset',
|
|
5
8
|
'归档显示': 'Archived sessions',
|
|
@@ -226,4 +226,15 @@ export default {
|
|
|
226
226
|
'Failed to submit the answer. Please resend your answer to the current question.',
|
|
227
227
|
'检测到这个 Session 中遗留的待回答问题,已安全取消并继续处理你刚才的消息。':
|
|
228
228
|
'A pending question left over in this Session was detected. It has been safely cancelled, and your latest message is being processed.',
|
|
229
|
+
// Inline-keyboard question cards (text-harness-bridge.mjs).
|
|
230
|
+
'该问题已处理,无需再次选择。':
|
|
231
|
+
'This question has already been handled; no further choice is needed.',
|
|
232
|
+
'只有发起当前任务的用户可以处理这条问题。':
|
|
233
|
+
'Only the user who started this task can answer this question.',
|
|
234
|
+
'正在提交你的选择,请稍候。': 'Submitting your choice, please wait.',
|
|
235
|
+
'这个选项已失效,请使用最新一条问题。':
|
|
236
|
+
'This option has expired. Please use the most recent question message.',
|
|
237
|
+
'多选问题请直接回复文字。':
|
|
238
|
+
'Please answer a multi-select question by replying with text.',
|
|
239
|
+
'已选择:{label}': 'Selected: {label}',
|
|
229
240
|
};
|
|
@@ -83,6 +83,8 @@ export default {
|
|
|
83
83
|
'Reply with option numbers or text; separate multiple choices with commas, or add anything else.',
|
|
84
84
|
'请回复一个选项序号或文字,也可直接输入其他答案。':
|
|
85
85
|
'Reply with an option number or its text, or type your own answer directly.',
|
|
86
|
+
'请点击下方按钮选择,也可直接回复文字。':
|
|
87
|
+
'Tap a button below to choose, or reply with text directly.',
|
|
86
88
|
'请直接回复你的答案。': 'Please reply with your answer directly.',
|
|
87
89
|
'群聊中请 @机器人 后发送答案。':
|
|
88
90
|
'In group chats, please @ the bot before sending your answer.',
|
|
@@ -371,7 +371,7 @@ export class OutboundArtifactRegistry {
|
|
|
371
371
|
const agent = exec?.agent;
|
|
372
372
|
const sessionId = agent?.session?.header?.id;
|
|
373
373
|
const workspace = agent?.session?.header?.cwd;
|
|
374
|
-
const turn = currentTurn(agent);
|
|
374
|
+
const turn = this.#openTurns.get(sessionId) ?? currentTurn(agent);
|
|
375
375
|
if (typeof sessionId !== 'string' || !sessionId
|
|
376
376
|
|| typeof workspace !== 'string' || !workspace || turn === null) {
|
|
377
377
|
throw artifactError(
|