@xmanrui/dsh-im 1.4.0 → 2.0.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.
Files changed (82) hide show
  1. package/README.en.md +18 -6
  2. package/README.md +18 -6
  3. package/lib/index.js +206 -176
  4. package/package.json +1 -1
  5. package/plugin-src/host/index.mjs +2 -0
  6. package/src/channels/dingtalk/config-store.mjs +5 -3
  7. package/src/channels/dingtalk/dingtalk-api.mjs +105 -82
  8. package/src/channels/dingtalk/dingtalk-bridge.mjs +79 -110
  9. package/src/channels/dingtalk/dingtalk-card-stream.mjs +3 -1
  10. package/src/channels/dingtalk/dingtalk-controller.mjs +24 -23
  11. package/src/channels/dingtalk/dingtalk-runtime.mjs +4 -3
  12. package/src/channels/dingtalk/state-store.mjs +3 -1
  13. package/src/channels/discord/config-store.mjs +2 -1
  14. package/src/channels/discord/discord-api.mjs +25 -1
  15. package/src/channels/discord/discord-runtime.mjs +237 -11
  16. package/src/channels/feishu/bridge.mjs +169 -180
  17. package/src/channels/feishu/feishu-cards.mjs +62 -54
  18. package/src/channels/feishu/feishu-channel.mjs +57 -21
  19. package/src/channels/feishu/feishu-runtime.mjs +10 -9
  20. package/src/channels/feishu/message-utils.mjs +4 -3
  21. package/src/channels/office/office-job-executor.mjs +15 -14
  22. package/src/channels/office/office-runtime.mjs +6 -5
  23. package/src/channels/qq/config-store.mjs +3 -1
  24. package/src/channels/qq/markdown-reply.mjs +4 -2
  25. package/src/channels/qq/qq-bridge.mjs +149 -130
  26. package/src/channels/qq/qq-controller.mjs +17 -16
  27. package/src/channels/qq/qq-runtime.mjs +3 -2
  28. package/src/channels/shared/agent-preset.mjs +3 -1
  29. package/src/channels/shared/compact-command.mjs +15 -14
  30. package/src/channels/shared/connection-test.mjs +6 -4
  31. package/src/channels/shared/control-command.mjs +13 -11
  32. package/src/channels/shared/editable-message-stream.mjs +4 -2
  33. package/src/channels/shared/harness-approval.mjs +23 -21
  34. package/src/channels/shared/harness-client.mjs +3 -2
  35. package/src/channels/shared/harness-question.mjs +8 -6
  36. package/src/channels/shared/i18n-en/dingtalk.mjs +52 -0
  37. package/src/channels/shared/i18n-en/discord.mjs +8 -0
  38. package/src/channels/shared/i18n-en/feishu.mjs +217 -0
  39. package/src/channels/shared/i18n-en/office.mjs +23 -0
  40. package/src/channels/shared/i18n-en/qq.mjs +54 -0
  41. package/src/channels/shared/i18n-en/shared-a.mjs +91 -0
  42. package/src/channels/shared/i18n-en/shared-b.mjs +215 -0
  43. package/src/channels/shared/i18n-en/shared-c.mjs +99 -0
  44. package/src/channels/shared/i18n-en/slack.mjs +27 -0
  45. package/src/channels/shared/i18n-en/telegram.mjs +21 -0
  46. package/src/channels/shared/i18n-en/wecom.mjs +38 -0
  47. package/src/channels/shared/i18n-en/weixin.mjs +80 -0
  48. package/src/channels/shared/i18n-en/whatsapp.mjs +17 -0
  49. package/src/channels/shared/i18n-en.mjs +35 -0
  50. package/src/channels/shared/i18n.mjs +35 -0
  51. package/src/channels/shared/image-prompt.mjs +17 -15
  52. package/src/channels/shared/inbound-file.mjs +5 -3
  53. package/src/channels/shared/model-command.mjs +35 -31
  54. package/src/channels/shared/preset-command.mjs +41 -38
  55. package/src/channels/shared/semantic/artifact-delivery.mjs +170 -0
  56. package/src/channels/shared/semantic/artifact.mjs +2 -2
  57. package/src/channels/shared/semantic/delivery.mjs +40 -0
  58. package/src/channels/shared/text-harness-bridge.mjs +154 -136
  59. package/src/channels/shared/token-bot-controller.mjs +32 -11
  60. package/src/channels/shared/workspace-command.mjs +69 -58
  61. package/src/channels/slack/config-store.mjs +3 -1
  62. package/src/channels/slack/slack-api.mjs +6 -5
  63. package/src/channels/slack/slack-controller.mjs +10 -9
  64. package/src/channels/slack/slack-runtime.mjs +16 -4
  65. package/src/channels/telegram/config-store.mjs +2 -1
  66. package/src/channels/telegram/telegram-api.mjs +111 -19
  67. package/src/channels/telegram/telegram-rich-message.mjs +147 -0
  68. package/src/channels/telegram/telegram-runtime.mjs +383 -5
  69. package/src/channels/wecom/config-store.mjs +3 -1
  70. package/src/channels/wecom/wecom-bridge.mjs +154 -153
  71. package/src/channels/wecom/wecom-controller.mjs +20 -19
  72. package/src/channels/wecom/wecom-runtime.mjs +2 -1
  73. package/src/channels/weixin/config-store.mjs +2 -1
  74. package/src/channels/weixin/weixin-api.mjs +140 -107
  75. package/src/channels/weixin/weixin-bridge.mjs +86 -123
  76. package/src/channels/weixin/weixin-controller.mjs +22 -21
  77. package/src/channels/weixin/weixin-runtime.mjs +3 -2
  78. package/src/channels/whatsapp/config-store.mjs +3 -1
  79. package/src/channels/whatsapp/whatsapp-bridge.mjs +3 -1
  80. package/src/channels/whatsapp/whatsapp-controller.mjs +15 -14
  81. package/src/channels/whatsapp/whatsapp-runtime.mjs +47 -13
  82. package/src/channels/whatsapp/whatsapp-web-session.mjs +3 -1
@@ -1,3 +1,4 @@
1
+ import { t } from './i18n.mjs';
1
2
  import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
2
3
 
3
4
  const COMPACT_COMMAND = /^\/compact(?=$|\s)([\s\S]*)$/i;
@@ -41,27 +42,27 @@ function compactResultText(result) {
41
42
  const text = result.text?.trim() ?? '';
42
43
  const compacted = /^Compacted (\d+) history items \(~(\d+) tokens\)\.$/u.exec(text);
43
44
  if (compacted) {
44
- return `已压缩 ${compacted[1]} 条历史记录(约 ${compacted[2]} 个 token)。`;
45
+ return t('已压缩 {count} 条历史记录(约 {tokens} 个 token)。', { count: compacted[1], tokens: compacted[2] });
45
46
  }
46
- if (COMPACT_RESULT_TEXT.has(text)) return COMPACT_RESULT_TEXT.get(text);
47
+ if (COMPACT_RESULT_TEXT.has(text)) return t(COMPACT_RESULT_TEXT.get(text));
47
48
  if (text) return text;
48
- return result.kind === 'success' ? '上下文压缩完成。' : '上下文压缩失败。';
49
+ return result.kind === 'success' ? t('上下文压缩完成。') : t('上下文压缩失败。');
49
50
  }
50
51
 
51
52
  function compactErrorMessage(error) {
52
53
  const code = error?.code ?? error?.failure?.code;
53
54
  if (code === 'session-not-found') {
54
- return '当前聊天绑定的会话已不存在,请发送新消息开启会话。';
55
+ return t('当前聊天绑定的会话已不存在,请发送新消息开启会话。');
55
56
  }
56
- if (code === 'agent-busy') return '当前会话正在生成回复,请稍后重试。';
57
- if (code === 'cancelled' || error?.name === 'AbortError') return '上下文压缩已取消。';
57
+ if (code === 'agent-busy') return t('当前会话正在生成回复,请稍后重试。');
58
+ if (code === 'cancelled' || error?.name === 'AbortError') return t('上下文压缩已取消。');
58
59
  if (code === WORKSPACE_SESSION_STALE || code === 'workspace-bot-not-found') {
59
- return '工作区或机器人状态已发生变化,请重试。';
60
+ return t('工作区或机器人状态已发生变化,请重试。');
60
61
  }
61
62
  if (code === 'commands-unavailable') {
62
- return '当前 Harness 暂不支持从机器人执行上下文压缩。';
63
+ return t('当前 Harness 暂不支持从机器人执行上下文压缩。');
63
64
  }
64
- return '上下文压缩失败,请稍后重试。';
65
+ return t('上下文压缩失败,请稍后重试。');
65
66
  }
66
67
 
67
68
  /**
@@ -72,21 +73,21 @@ export async function runCompactCommand(text, harness, state, conversationKey, o
72
73
  if (typeof text !== 'string') return null;
73
74
  const match = COMPACT_COMMAND.exec(text.trim());
74
75
  if (!match) return null;
75
- if (match[1].trim()) return commandResult(COMPACT_USAGE);
76
+ if (match[1].trim()) return commandResult(t(COMPACT_USAGE));
76
77
  if (typeof state?.sessionFor !== 'function') {
77
- return commandResult('当前机器人没有可用的会话状态。');
78
+ return commandResult(t('当前机器人没有可用的会话状态。'));
78
79
  }
79
80
  const sessionId = state.sessionFor(conversationKey);
80
81
  if (typeof sessionId !== 'string' || !sessionId) {
81
- return commandResult('当前聊天还没有可压缩的会话,请先发送一条消息。');
82
+ return commandResult(t('当前聊天还没有可压缩的会话,请先发送一条消息。'));
82
83
  }
83
84
  if (typeof harness?.executeCommand !== 'function') {
84
- return commandResult('当前机器人暂不支持上下文压缩。');
85
+ return commandResult(t('当前机器人暂不支持上下文压缩。'));
85
86
  }
86
87
  try {
87
88
  const execution = await harness.executeCommand(sessionId, '/compact', options);
88
89
  if (execution === undefined) {
89
- return commandResult('当前 Harness 未注册 /compact 命令,请确认上下文压缩组件已启用。');
90
+ return commandResult(t('当前 Harness 未注册 /compact 命令,请确认上下文压缩组件已启用。'));
90
91
  }
91
92
  return commandResult(compactResultText(execution?.result));
92
93
  } catch (error) {
@@ -1,3 +1,5 @@
1
+ import { t } from './i18n.mjs';
2
+
1
3
  const targets = new WeakMap();
2
4
 
3
5
  export const CONNECTION_TEST_STATE_IDENTITY = Symbol('dsh-im.connection-test-state-identity');
@@ -25,13 +27,13 @@ export function connectionTestTarget(state) {
25
27
  return target ? structuredClone(target) : null;
26
28
  }
27
29
 
28
- export function connectionTestMessage(botName, channelLabel = '机器人') {
30
+ export function connectionTestMessage(botName, channelLabel = t('机器人')) {
29
31
  const name = cleanText(botName) ?? channelLabel;
30
- return `✅ DeepSeek Harness 连接测试成功\n这条消息由插件页面中的“${name}”机器人卡片发出。`;
32
+ return `${t('✅ DeepSeek Harness 连接测试成功')}\n${t('这条消息由插件页面中的“{name}”机器人卡片发出。', { name })}`;
31
33
  }
32
34
 
33
- export function connectionTestTargetUnavailable(channelLabel = '机器人') {
34
- const error = new Error(`${channelLabel}尚未收到可用于测试的私聊消息。`);
35
+ export function connectionTestTargetUnavailable(channelLabel = t('机器人')) {
36
+ const error = new Error(t('{channelLabel}尚未收到可用于测试的私聊消息。', { channelLabel }));
35
37
  error.code = 'test-target-unavailable';
36
38
  return error;
37
39
  }
@@ -1,3 +1,5 @@
1
+ import { t } from './i18n.mjs';
2
+
1
3
  const CONTROL_COMMAND = /^\/(?:stop|steer)(?=$|\s)/iu;
2
4
  const STOP_COMMAND = /^\/stop(?=$|\s)/iu;
3
5
  const STOP_USAGE = '用法:/stop(不带参数)';
@@ -40,35 +42,35 @@ export async function runControlCommand(text, harness, state, key, {
40
42
  const command = text.trim();
41
43
  const stop = STOP_COMMAND.test(command);
42
44
 
43
- if (hasImages) return commandResult(TEXT_ONLY);
45
+ if (hasImages) return commandResult(t(TEXT_ONLY));
44
46
 
45
47
  if (stop) {
46
- if (!/^\/stop$/iu.test(command)) return commandResult(STOP_USAGE);
48
+ if (!/^\/stop$/iu.test(command)) return commandResult(t(STOP_USAGE));
47
49
  const session = boundSession(harness, state, key);
48
- if (!session) return commandResult('当前聊天没有正在运行的任务。');
50
+ if (!session) return commandResult(t('当前聊天没有正在运行的任务。'));
49
51
  if (typeof session.stopActiveTurn !== 'function') {
50
52
  throw new TypeError('Harness session does not support stopping active turns');
51
53
  }
52
54
  const stopped = await session.stopActiveTurn(control, requestOptions(signal));
53
55
  return stopped
54
- ? commandResult('已请求停止当前任务。', { stopped: true })
55
- : commandResult('当前聊天没有正在运行的任务。');
56
+ ? commandResult(t('已请求停止当前任务。'), { stopped: true })
57
+ : commandResult(t('当前聊天没有正在运行的任务。'));
56
58
  }
57
59
 
58
60
  const match = /^\/steer(?:\s+([\s\S]*))?$/iu.exec(command);
59
61
  const instruction = match?.[1]?.trim() ?? '';
60
- if (!instruction) return commandResult(STEER_USAGE);
62
+ if (!instruction) return commandResult(t(STEER_USAGE));
61
63
  if (pendingInteraction) {
62
64
  return commandResult([
63
- '当前任务正在等待你的回答或审批。',
65
+ t('当前任务正在等待你的回答或审批。'),
64
66
  '',
65
- '请先处理当前请求,或者发送 /stop 停止任务。',
67
+ t('请先处理当前请求,或者发送 /stop 停止任务。'),
66
68
  ].join('\n'));
67
69
  }
68
70
 
69
71
  const session = boundSession(harness, state, key);
70
72
  if (!session) {
71
- return commandResult('当前聊天没有正在运行的任务,请直接发送普通消息。');
73
+ return commandResult(t('当前聊天没有正在运行的任务,请直接发送普通消息。'));
72
74
  }
73
75
  if (typeof session.steerActiveTurn !== 'function') {
74
76
  throw new TypeError('Harness session does not support steering active turns');
@@ -79,6 +81,6 @@ export async function runControlCommand(text, harness, state, key, {
79
81
  requestOptions(signal),
80
82
  );
81
83
  return steered
82
- ? commandResult('已提交补充指令,Agent 会在下一步读取。')
83
- : commandResult('当前聊天没有正在运行的任务,请直接发送普通消息。');
84
+ ? commandResult(t('已提交补充指令,Agent 会在下一步读取。'))
85
+ : commandResult(t('当前聊天没有正在运行的任务,请直接发送普通消息。'));
84
86
  }
@@ -1,3 +1,5 @@
1
+ import { t } from './i18n.mjs';
2
+
1
3
  export function splitMessageText(value, limit) {
2
4
  const text = typeof value === 'string' ? value.trim() : '';
3
5
  if (!text) return [];
@@ -15,7 +17,7 @@ export function splitMessageText(value, limit) {
15
17
  }
16
18
 
17
19
  export function createEditableMessageStream({
18
- initialText = '正在处理…',
20
+ initialText = t('正在处理…'),
19
21
  limit,
20
22
  updateIntervalMs = 800,
21
23
  create,
@@ -77,7 +79,7 @@ export function createEditableMessageStream({
77
79
  pending = null;
78
80
  await inFlight?.catch(() => undefined);
79
81
  const chunks = splitMessageText(text, limit);
80
- const first = chunks[0] ?? '处理完成。';
82
+ const first = chunks[0] ?? t('处理完成。');
81
83
  if (first !== lastSent) await edit(messageId, first);
82
84
  lastSent = first;
83
85
  for (const chunk of chunks.slice(1)) {
@@ -1,3 +1,5 @@
1
+ import { t } from './i18n.mjs';
2
+
1
3
  const APPROVAL_REPLIES = new Map([
2
4
  ['批准', 'allowed-once'],
3
5
  ['同意', 'allowed-once'],
@@ -68,16 +70,16 @@ export function harnessApprovalText(payload, {
68
70
  if (!operation || operation.length > maxArgumentsLength) return null;
69
71
 
70
72
  const lines = [
71
- 'DeepSeek Harness 需要你的审批:',
73
+ t('DeepSeek Harness 需要你的审批:'),
72
74
  '',
73
- `工具:${printableText(payload.toolName)}`,
74
- '操作参数:',
75
+ t('工具:{tool}', { tool: printableText(payload.toolName) }),
76
+ t('操作参数:'),
75
77
  operation,
76
78
  ];
77
79
  const reason = printableText(payload.reason);
78
- if (reason) lines.push(`原因:${reason}`);
79
- lines.push('', APPROVAL_PROMPT);
80
- if (requiresMention) lines.push('', '群聊中请 @机器人 后发送审批决定。');
80
+ if (reason) lines.push(t('原因:{reason}', { reason }));
81
+ lines.push('', t(APPROVAL_PROMPT));
82
+ if (requiresMention) lines.push('', t('群聊中请 @机器人 后发送审批决定。'));
81
83
  return lines.join('\n');
82
84
  }
83
85
 
@@ -93,9 +95,9 @@ function approvalResult(pending, outcome) {
93
95
  }
94
96
 
95
97
  function approvalOutcomeText(outcome) {
96
- if (outcome === 'allowed-once') return '已批准,仅对本次操作有效。';
97
- if (outcome === 'rejected') return '已拒绝此次操作。';
98
- return APPROVAL_RESOLVED_TEXT;
98
+ if (outcome === 'allowed-once') return t('已批准,仅对本次操作有效。');
99
+ if (outcome === 'rejected') return t('已拒绝此次操作。');
100
+ return t(APPROVAL_RESOLVED_TEXT);
99
101
  }
100
102
 
101
103
  export class HarnessApprovalQueue {
@@ -149,11 +151,11 @@ export class HarnessApprovalQueue {
149
151
  return null;
150
152
  }
151
153
  if (!decision) return null;
152
- return notice(APPROVAL_RESOLVED_TEXT, true);
154
+ return notice(t(APPROVAL_RESOLVED_TEXT), true);
153
155
  }
154
156
  if (pending.actor !== actor || (pending.requiresMention && addressed !== true)) {
155
157
  if (!decision) return null;
156
- return notice('只有发起当前任务的用户可以处理这条审批。');
158
+ return notice(t('只有发起当前任务的用户可以处理这条审批。'));
157
159
  }
158
160
 
159
161
  return {
@@ -168,14 +170,14 @@ export class HarnessApprovalQueue {
168
170
  await questionCompletion.catch(() => undefined);
169
171
  if (pending.inactive || pending.resolving) return;
170
172
  if (typeof isQuestionPending === 'function' && isQuestionPending()) {
171
- await send(APPROVAL_AFTER_QUESTION_PROMPT);
173
+ await send(t(APPROVAL_AFTER_QUESTION_PROMPT));
172
174
  return;
173
175
  }
174
176
  }
175
177
  await pending.activationTask?.catch(() => undefined);
176
178
  await pending.presentationTask?.catch(() => undefined);
177
179
  if (pending.inactive || pending.resolving) {
178
- await send(APPROVAL_RESOLVED_TEXT);
180
+ await send(t(APPROVAL_RESOLVED_TEXT));
179
181
  return;
180
182
  }
181
183
  pending.send = send;
@@ -185,15 +187,15 @@ export class HarnessApprovalQueue {
185
187
  if (!presentedWhenClaimed || !pending.presented) {
186
188
  if (!pending.presented) await this.#present(pending);
187
189
  if (pending.inactive || pending.resolving) return;
188
- await send(APPROVAL_PROMPT);
190
+ await send(t(APPROVAL_PROMPT));
189
191
  return;
190
192
  }
191
193
  if (pending.submitting) {
192
- await send('审批决定正在提交,请稍候。');
194
+ await send(t('审批决定正在提交,请稍候。'));
193
195
  return;
194
196
  }
195
197
  if (!decision) {
196
- await send(APPROVAL_PROMPT);
198
+ await send(t(APPROVAL_PROMPT));
197
199
  return;
198
200
  }
199
201
  await this.#submit(pending, decision);
@@ -251,8 +253,8 @@ export class HarnessApprovalQueue {
251
253
  if (!text) {
252
254
  const rejected = await this.#rejectInteraction(interaction, payload);
253
255
  await send(rejected
254
- ? '无法完整展示这次操作,已安全拒绝此次审批。'
255
- : APPROVAL_RESOLVED_TEXT);
256
+ ? t('无法完整展示这次操作,已安全拒绝此次审批。')
257
+ : t(APPROVAL_RESOLVED_TEXT));
256
258
  return true;
257
259
  }
258
260
 
@@ -335,7 +337,7 @@ export class HarnessApprovalQueue {
335
337
  pending.closedOutcome = 'resolved';
336
338
  if ((pending.presented || pending.deliveryCompleted) && !pending.resolutionNotified) {
337
339
  pending.resolutionNotified = true;
338
- await pending.send(APPROVAL_RESOLVED_TEXT).catch(() => undefined);
340
+ await pending.send(t(APPROVAL_RESOLVED_TEXT)).catch(() => undefined);
339
341
  }
340
342
  } else {
341
343
  this.#logger.warn?.(`[dsh-im:${this.#label}] failed to reject a closing approval:`, error);
@@ -377,7 +379,7 @@ export class HarnessApprovalQueue {
377
379
  const next = this.#remove(pending);
378
380
  await this.#transition(next, async () => {
379
381
  if (!pending.resolutionNotified) {
380
- await send(APPROVAL_RESOLVED_TEXT).catch(() => undefined);
382
+ await send(t(APPROVAL_RESOLVED_TEXT)).catch(() => undefined);
381
383
  }
382
384
  });
383
385
  return;
@@ -385,7 +387,7 @@ export class HarnessApprovalQueue {
385
387
  if (pending.inactive) return;
386
388
  pending.submitting = false;
387
389
  this.#logger.error?.(`[dsh-im:${this.#label}] failed to submit an approval:`, error);
388
- await pending.send('审批提交失败,请重新回复「批准」或「拒绝」。').catch(() => undefined);
390
+ await pending.send(t('审批提交失败,请重新回复「批准」或「拒绝」。')).catch(() => undefined);
389
391
  return;
390
392
  }
391
393
 
@@ -8,6 +8,7 @@ import {
8
8
  InboundFileError,
9
9
  } from './inbound-file.mjs';
10
10
  import { outboundArtifactRegistry } from './semantic/artifact.mjs';
11
+ import { t } from './i18n.mjs';
11
12
 
12
13
  // Every channel plugin runs in the same Host process. Sharing ownership by
13
14
  // Harness origin prevents two channel-specific clients bound to one Session
@@ -438,7 +439,7 @@ export class HarnessReplyTracker {
438
439
  }
439
440
 
440
441
  if (event.type === 'tool/call') {
441
- const name = nonEmptyText(event.data?.name) ?? '工具';
442
+ const name = nonEmptyText(event.data?.name) ?? t('工具');
442
443
  const callId = nonEmptyText(event.data?.callId)
443
444
  ?? nonEmptyText(event.data?.subCallId);
444
445
  if (callId) this.#toolNames.set(callId, name);
@@ -453,7 +454,7 @@ export class HarnessReplyTracker {
453
454
  const error = toolResultErrorText(event.data?.error);
454
455
  pushUpdate({
455
456
  type: 'status',
456
- text: '正在整理结果…',
457
+ text: t('正在整理结果…'),
457
458
  ...(toolName ? { toolName } : {}),
458
459
  ...(error ? { error } : {}),
459
460
  });
@@ -1,3 +1,5 @@
1
+ import { t } from './i18n.mjs';
2
+
1
3
  function nonEmptyString(value) {
2
4
  return typeof value === 'string' && value.trim() ? value.trim() : null;
3
5
  }
@@ -17,9 +19,9 @@ export function validHarnessQuestion(question) {
17
19
  export function harnessQuestionText(question, index, total, { requiresMention = false } = {}) {
18
20
  const lines = [];
19
21
  const progress = total > 1 ? `(${index + 1}/${total})` : '';
20
- lines.push(`DeepSeek Harness 需要你补充信息${progress}:`);
22
+ lines.push(t('DeepSeek Harness 需要你补充信息{progress}:', { progress }));
21
23
  if (nonEmptyString(question.header)) lines.push('', question.header.trim());
22
- lines.push('', nonEmptyString(question.question) ?? '请输入你的回答。');
24
+ lines.push('', nonEmptyString(question.question) ?? t('请输入你的回答。'));
23
25
  if (nonEmptyString(question.detail)) lines.push('', question.detail.trim());
24
26
 
25
27
  const options = Array.isArray(question.options) ? question.options : [];
@@ -31,12 +33,12 @@ export function harnessQuestionText(question, index, total, { requiresMention =
31
33
  lines.push(`${optionIndex + 1}. ${label}${description ? ` — ${description}` : ''}`);
32
34
  });
33
35
  lines.push('', question.multiSelect === true
34
- ? '请回复选项序号或文字;多选用逗号分隔,也可补充其他内容。'
35
- : '请回复一个选项序号或文字,也可直接输入其他答案。');
36
+ ? t('请回复选项序号或文字;多选用逗号分隔,也可补充其他内容。')
37
+ : t('请回复一个选项序号或文字,也可直接输入其他答案。'));
36
38
  } else {
37
- lines.push('', '请直接回复你的答案。');
39
+ lines.push('', t('请直接回复你的答案。'));
38
40
  }
39
- if (requiresMention) lines.push('', '群聊中请 @机器人 后发送答案。');
41
+ if (requiresMention) lines.push('', t('群聊中请 @机器人 后发送答案。'));
40
42
  return lines.join('\n');
41
43
  }
42
44
 
@@ -0,0 +1,52 @@
1
+ // English translations (dingtalk area). Keys are exact Chinese literals passed to t().
2
+ export default {
3
+ // Bot chat replies and card text (dingtalk-bridge.mjs, dingtalk-card-stream.mjs,
4
+ // dingtalk-api.mjs)
5
+ '已连接 DeepSeek Harness,正在思考…': 'Connected to DeepSeek Harness, thinking…',
6
+
7
+ '钉钉机器人已连接 DeepSeek Harness。': 'The DingTalk bot is connected to DeepSeek Harness.',
8
+
9
+
10
+
11
+
12
+ '钉钉未能换取图片下载地址,请重新发送;若持续失败,请检查机器人的“企业内机器人发送消息权限”。': 'DingTalk could not provide the image download address. Please resend; if it keeps failing, check the bot\'s "Send messages as an internal robot" permission.',
13
+ '钉钉没有返回图片下载地址,请重新发送。': 'DingTalk did not return an image download address. Please resend.',
14
+ '钉钉返回的图片临时地址无法读取,请重新发送。': 'The temporary image address returned by DingTalk could not be read. Please resend.',
15
+ '结果文件「{name}」发送结果未能确认,请先检查聊天内是否已收到,不要立即重试。': 'Delivery of the result file "{name}" could not be confirmed. Please check whether it already arrived in the chat before retrying.',
16
+ '结果文件「{name}」已生成,但钉钉应用或机器人缺少文件消息权限。请开通应用 qyapi_base 权限,并确认机器人具备文件消息发送能力。': 'Result file "{name}" was generated, but the DingTalk app or bot lacks file message permission. Enable the app\'s qyapi_base permission and make sure the bot can send file messages.',
17
+ '结果文件「{name}」超过当前钉钉机器人可发送的文件大小,未发送。': 'Result file "{name}" exceeds the file size limit of the current DingTalk bot and was not sent.',
18
+ '结果文件「{name}」暂时被钉钉限流,未能发送,请稍后重试。': 'Result file "{name}" was rate-limited by DingTalk and could not be sent. Please try again later.',
19
+ '结果文件「{name}」已生成,但钉钉拒绝了该文件消息,请检查文件类型和机器人文件消息配置。': 'Result file "{name}" was generated, but DingTalk rejected the file message. Check the file type and the bot\'s file message configuration.',
20
+
21
+ '结果文件「{name}」已生成,但暂时未能通过钉钉发送,请稍后重试。': 'Result file "{name}" was generated but could not be sent via DingTalk right now. Please try again later.',
22
+ '结果文件已生成。': 'The result file has been generated.',
23
+ '钉钉机器人与 DeepSeek Harness 连接正常。': 'The DingTalk bot is connected to DeepSeek Harness normally.',
24
+
25
+
26
+
27
+ // Host-side status and error text (dingtalk-bridge.mjs, dingtalk-runtime.mjs,
28
+ // dingtalk-controller.mjs, config-store.mjs, state-store.mjs)
29
+ '钉钉命令处理失败。': 'Failed to process the DingTalk command.',
30
+ '钉钉消息没有安全的回复地址。': 'The DingTalk message has no safe reply address.',
31
+ '钉钉审批处理失败。': 'Failed to process the DingTalk approval.',
32
+ '钉钉消息处理失败。': 'Failed to process the DingTalk message.',
33
+ '钉钉消息格式无效。': 'Invalid DingTalk message format.',
34
+ '钉钉交互问题发送失败。': 'Failed to send the DingTalk interaction question.',
35
+ '钉钉用户': 'DingTalk user',
36
+ '钉钉机器人': 'DingTalk bot',
37
+ '身份已隐藏': 'Identity hidden',
38
+ '钉钉机器人凭据缺失,请移除后重新扫码。': 'The DingTalk bot credentials are missing. Remove the bot and scan the QR code again.',
39
+ '钉钉连接未就绪,请稍后重试。': 'The DingTalk connection is not ready. Please try again later.',
40
+ '钉钉连接仍未就绪,请稍后重试。': 'The DingTalk connection is still not ready. Please try again later.',
41
+ '扫码接入已取消。': 'QR code setup has been cancelled.',
42
+ '无法生成钉钉二维码,请稍后重试。': 'Could not generate the DingTalk QR code. Please try again later.',
43
+ '二维码已过期,请重新生成。': 'The QR code has expired. Generate a new one.',
44
+ '钉钉已接入,但消息连接暂未就绪,请稍后重试。': 'DingTalk is connected, but the message connection is not ready yet. Please try again later.',
45
+ '钉钉未完成机器人授权,请重新扫码。': 'DingTalk did not complete the bot authorization. Please scan the QR code again.',
46
+ '钉钉授权状态暂时不可用,正在重试。': 'The DingTalk authorization status is temporarily unavailable. Retrying.',
47
+ '钉钉已授权,但无法安全保存接入配置。': 'DingTalk authorization succeeded, but the configuration could not be saved safely.',
48
+ '钉钉授权查询暂时失败,正在重试。': 'The DingTalk authorization query failed temporarily. Retrying.',
49
+ '钉钉机器人({clientId})': 'DingTalk bot ({clientId})',
50
+ '钉钉 Stream 消息连接运行正常': 'The DingTalk Stream message connection is running normally',
51
+ '钉钉消息连接当前离线': 'The DingTalk message connection is currently offline',
52
+ };
@@ -0,0 +1,8 @@
1
+ // English translations (discord area). Keys are exact Chinese literals passed to t().
2
+ export default {
3
+ 'Discord Bot Token 无效,请重新填写。': 'The Discord Bot Token is invalid. Please enter it again.',
4
+ 'Discord Gateway Intents 配置不正确,请检查 Developer Portal 的 Bot 设置。':
5
+ 'The Discord Gateway Intents are misconfigured. Please check the Bot settings in the Developer Portal.',
6
+ 'Discord机器人': 'Discord Bot',
7
+ ' Gateway 长连接': ' Gateway long-lived connection',
8
+ };