@hopgoldy/agent-bridge 0.3.1 → 0.3.4

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/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import process2 from "process";
5
+ import { createRequire } from "module";
5
6
  import { Command } from "commander";
6
7
 
7
8
  // src/config/prompt.ts
@@ -106,6 +107,85 @@ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from
106
107
  import os2 from "os";
107
108
  import path2 from "path";
108
109
 
110
+ // src/i18n/index.ts
111
+ import i18next from "i18next";
112
+ var DEFAULT_LOCALE = "en-US";
113
+ var resources = {
114
+ "en-US": {
115
+ translation: {
116
+ progress: {
117
+ noProgress: "No progress yet.",
118
+ collapsed: "Collapsed {{count}} earlier updates.",
119
+ running: "Running {{subject}}",
120
+ finished: "Finished {{subject}}",
121
+ failed: "Failed {{subject}}",
122
+ failedWithDetail: "Failed {{subject}}: {{detail}}",
123
+ compacting: "Compacting session",
124
+ compactingWithDetail: "Compacting session: {{detail}}"
125
+ },
126
+ client: {
127
+ processing: "Processing...",
128
+ helpMessage: "Available commands:\n\n- `/new` (`/n`) - Start a new agent session\n- `/compact` (`/c`) - Compact the current session context\n- `/stop` (`/s`) - Stop the active agent run\n- `/help` (`/h`) - Show this help message",
129
+ messageDeliveryFailedTitle: "[agent-bridge error] Message delivery failed",
130
+ weixinCooldown: "Weixin send is cooling down after rate limiting. Please try again shortly."
131
+ },
132
+ gateway: {
133
+ noActiveSessionToCompact: "No active agent session to compact.",
134
+ noActiveSessionToStop: "No active agent session to stop.",
135
+ sessionCannotBeStopped: "This agent session cannot be stopped right now.",
136
+ noActiveRunToStop: "No active agent run to stop.",
137
+ startedNewSession: "Started a new session."
138
+ }
139
+ }
140
+ },
141
+ "zh-CN": {
142
+ translation: {
143
+ progress: {
144
+ noProgress: "\u6682\u65E0\u8FDB\u5EA6\u3002",
145
+ collapsed: "\u5DF2\u6298\u53E0 {{count}} \u6761\u8F83\u65E9\u66F4\u65B0\u3002",
146
+ running: "\u6B63\u5728\u6267\u884C {{subject}}",
147
+ finished: "\u5DF2\u5B8C\u6210 {{subject}}",
148
+ failed: "{{subject}} \u6267\u884C\u5931\u8D25",
149
+ failedWithDetail: "{{subject}} \u6267\u884C\u5931\u8D25\uFF1A{{detail}}",
150
+ compacting: "\u6B63\u5728\u538B\u7F29\u4F1A\u8BDD",
151
+ compactingWithDetail: "\u6B63\u5728\u538B\u7F29\u4F1A\u8BDD: {{detail}}"
152
+ },
153
+ client: {
154
+ processing: "\u6B63\u5728\u5904\u7406\u4E2D...",
155
+ helpMessage: "\u53EF\u7528\u547D\u4EE4\uFF1A\n\n- `/new` (`/n`) - \u5F00\u59CB\u4E00\u4E2A\u65B0\u4F1A\u8BDD\n- `/compact` (`/c`) - \u538B\u7F29\u5F53\u524D\u4F1A\u8BDD\u4E0A\u4E0B\u6587\n- `/stop` (`/s`) - \u505C\u6B62\u5F53\u524D\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\n- `/help` (`/h`) - \u67E5\u770B\u8FD9\u6761\u5E2E\u52A9\u4FE1\u606F",
156
+ messageDeliveryFailedTitle: "[agent-bridge \u9519\u8BEF] \u6D88\u606F\u53D1\u9001\u5931\u8D25",
157
+ weixinCooldown: "\u5FAE\u4FE1\u53D1\u9001\u56E0\u9650\u6D41\u5DF2\u8FDB\u5165\u51B7\u5374\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002"
158
+ },
159
+ gateway: {
160
+ noActiveSessionToCompact: "\u5F53\u524D\u6CA1\u6709\u53EF\u538B\u7F29\u7684\u667A\u80FD\u4F53\u4F1A\u8BDD\u3002",
161
+ noActiveSessionToStop: "\u5F53\u524D\u6CA1\u6709\u53EF\u505C\u6B62\u7684\u667A\u80FD\u4F53\u4F1A\u8BDD\u3002",
162
+ sessionCannotBeStopped: "\u5F53\u524D\u65E0\u6CD5\u505C\u6B62\u8FD9\u4E2A\u667A\u80FD\u4F53\u4F1A\u8BDD\u3002",
163
+ noActiveRunToStop: "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u667A\u80FD\u4F53\u4EFB\u52A1\u53EF\u505C\u6B62\u3002",
164
+ startedNewSession: "\u5DF2\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002"
165
+ }
166
+ }
167
+ }
168
+ };
169
+ var instance = i18next.createInstance();
170
+ void instance.init({
171
+ lng: DEFAULT_LOCALE,
172
+ fallbackLng: DEFAULT_LOCALE,
173
+ resources,
174
+ interpolation: { escapeValue: false },
175
+ initImmediate: false
176
+ });
177
+ function getTranslator(locale) {
178
+ return instance.getFixedT(locale);
179
+ }
180
+ function getTranslatorForCommon(common) {
181
+ return getTranslator(common?.language ?? DEFAULT_LOCALE);
182
+ }
183
+ function formatSendFailureNotice(t, detail) {
184
+ return `${t("client.messageDeliveryFailedTitle")}
185
+
186
+ ${detail}`;
187
+ }
188
+
109
189
  // src/config/defaults.ts
110
190
  var DEFAULTS = {
111
191
  agentIdleTimeoutMs: 10 * 60 * 1e3
@@ -114,11 +194,20 @@ var DEFAULTS = {
114
194
  // src/config/store.ts
115
195
  var CONFIG_DIR = path2.join(os2.homedir(), ".config", "agent-bridge");
116
196
  var CONFIG_PATH = path2.join(CONFIG_DIR, "config.json");
197
+ function normalizeLanguage(language) {
198
+ return language === "en-US" || language === "zh-CN" ? language : DEFAULT_LOCALE;
199
+ }
117
200
  function normalizeChannelConfig(channel) {
118
201
  if (!channel.client || !channel.agent) {
119
202
  throw new Error("Invalid channel config shape");
120
203
  }
121
- return channel;
204
+ return {
205
+ common: {
206
+ language: normalizeLanguage(channel.common?.language)
207
+ },
208
+ client: channel.client,
209
+ agent: channel.agent
210
+ };
122
211
  }
123
212
  function mergeDefaults(config = {}) {
124
213
  const channels = Object.fromEntries(
@@ -193,16 +282,20 @@ var GatewayCore = class {
193
282
  #agentConfig;
194
283
  #agentIdleTimeoutMs;
195
284
  #bindingStore;
285
+ #common;
286
+ #t;
196
287
  #logger = createLogger("core");
197
288
  #clientToAgentSession = /* @__PURE__ */ new Map();
198
289
  #agentRuntimes = /* @__PURE__ */ new Map();
199
290
  #started = false;
200
- constructor({ imAdapter, agentModule, agentConfig, agentIdleTimeoutMs, bindingStore }) {
291
+ constructor({ imAdapter, agentModule, agentConfig, agentIdleTimeoutMs, bindingStore, common }) {
201
292
  this.#imAdapter = imAdapter;
202
293
  this.#agentModule = agentModule;
203
294
  this.#agentConfig = agentConfig;
204
295
  this.#agentIdleTimeoutMs = agentIdleTimeoutMs;
205
296
  this.#bindingStore = bindingStore;
297
+ this.#common = common;
298
+ this.#t = getTranslatorForCommon(common);
206
299
  }
207
300
  async start() {
208
301
  if (this.#started) return;
@@ -258,7 +351,7 @@ var GatewayCore = class {
258
351
  await this.#deliverClientInput({
259
352
  type: "assistant.message",
260
353
  clientSessionId,
261
- text: "No active agent session to compact."
354
+ text: this.#t("gateway.noActiveSessionToCompact")
262
355
  });
263
356
  return;
264
357
  }
@@ -273,7 +366,7 @@ var GatewayCore = class {
273
366
  await this.#deliverClientInput({
274
367
  type: "assistant.message",
275
368
  clientSessionId,
276
- text: "No active agent session to stop."
369
+ text: this.#t("gateway.noActiveSessionToStop")
277
370
  });
278
371
  return;
279
372
  }
@@ -282,7 +375,7 @@ var GatewayCore = class {
282
375
  await this.#deliverClientInput({
283
376
  type: "assistant.message",
284
377
  clientSessionId,
285
- text: "This agent session cannot be stopped right now."
378
+ text: this.#t("gateway.sessionCannotBeStopped")
286
379
  });
287
380
  return;
288
381
  }
@@ -290,7 +383,7 @@ var GatewayCore = class {
290
383
  await this.#deliverClientInput({
291
384
  type: "assistant.message",
292
385
  clientSessionId,
293
- text: "No active agent run to stop."
386
+ text: this.#t("gateway.noActiveRunToStop")
294
387
  });
295
388
  return;
296
389
  }
@@ -309,7 +402,7 @@ var GatewayCore = class {
309
402
  await this.#deliverClientInput({
310
403
  type: "assistant.message",
311
404
  clientSessionId,
312
- text: "Started a new session."
405
+ text: this.#t("gateway.startedNewSession")
313
406
  });
314
407
  }
315
408
  async #deliverClientInput(event) {
@@ -344,6 +437,7 @@ var GatewayCore = class {
344
437
  if (this.#agentModule.resumeAgentSession) {
345
438
  const agentAdapter = await this.#agentModule.resumeAgentSession({
346
439
  config: this.#agentConfig,
440
+ common: this.#common ?? { channelName: "", language: "en-US" },
347
441
  agentSessionId
348
442
  });
349
443
  return this.#startRuntime(clientSessionId, agentSessionId, agentAdapter);
@@ -354,7 +448,8 @@ var GatewayCore = class {
354
448
  }
355
449
  async #createRuntimeForClient(clientSessionId) {
356
450
  const { agentSessionId, agentAdapter } = await this.#agentModule.createAgentSession({
357
- config: this.#agentConfig
451
+ config: this.#agentConfig,
452
+ common: this.#common ?? { channelName: "", language: "en-US" }
358
453
  });
359
454
  return this.#startRuntime(clientSessionId, agentSessionId, agentAdapter);
360
455
  }
@@ -1127,7 +1222,7 @@ var PiCodingAgentAdapter = class {
1127
1222
  toolInput,
1128
1223
  toolLabel,
1129
1224
  result: "result" in rpcEvent ? rpcEvent.result : void 0,
1130
- text: isError ? `Failed ${toolName}` : `Finished ${toolName}`
1225
+ text: isError ? void 0 : `Finished ${toolName}`
1131
1226
  });
1132
1227
  if (toolCallId) {
1133
1228
  this.#toolLabelByCallId.delete(toolCallId);
@@ -1292,16 +1387,16 @@ function createPiCodingAgentConfigCollector() {
1292
1387
  var piCodingAgentModule = {
1293
1388
  type: "pi-coding-agent",
1294
1389
  createConfigCollector: createPiCodingAgentConfigCollector,
1295
- async createAgentSession({ config }) {
1390
+ async createAgentSession({ config, common }) {
1296
1391
  const agentSessionId = `pi-coding-agent:${randomUUID()}`;
1297
- logger.info(`creating agent session ${agentSessionId}`);
1392
+ logger.info(`creating agent session ${agentSessionId} for channel ${common.channelName}`);
1298
1393
  return {
1299
1394
  agentSessionId,
1300
1395
  agentAdapter: buildAdapter(config, agentSessionId)
1301
1396
  };
1302
1397
  },
1303
- async resumeAgentSession({ config, agentSessionId }) {
1304
- logger.info(`resuming agent session ${agentSessionId}`);
1398
+ async resumeAgentSession({ config, common, agentSessionId }) {
1399
+ logger.info(`resuming agent session ${agentSessionId} for channel ${common.channelName}`);
1305
1400
  return buildAdapter(config, agentSessionId);
1306
1401
  }
1307
1402
  };
@@ -1325,14 +1420,15 @@ function getTypedAgentModule(config) {
1325
1420
  // src/modules/client/utils/progress-renderer.ts
1326
1421
  var DEFAULT_COLLAPSE_THRESHOLD = 10;
1327
1422
  var MAX_TOOL_LABEL_DISPLAY_LENGTH = 15;
1328
- var NO_PROGRESS_MARKDOWN = "No progress yet.";
1329
1423
  var ProgressRenderer = class {
1330
1424
  #collapseThreshold;
1425
+ #t;
1331
1426
  #entries = /* @__PURE__ */ new Map();
1332
1427
  #order = [];
1333
1428
  #status = "running";
1334
1429
  constructor(options = {}) {
1335
1430
  this.#collapseThreshold = options.collapseThreshold ?? DEFAULT_COLLAPSE_THRESHOLD;
1431
+ this.#t = options.t ?? getTranslator("en-US");
1336
1432
  }
1337
1433
  /** Whether `event` should be recorded as progress (excludes assistant messages and thinking). */
1338
1434
  isProgressEvent(event) {
@@ -1354,20 +1450,21 @@ var ProgressRenderer = class {
1354
1450
  return {
1355
1451
  markdown: this.#renderMarkdown(collapsedCount),
1356
1452
  status: this.#status,
1357
- collapsedCount
1453
+ collapsedCount,
1454
+ isEmpty: this.#order.length === 0
1358
1455
  };
1359
1456
  }
1360
1457
  #renderMarkdown(collapsedCount) {
1361
1458
  const contentLines = [];
1362
1459
  if (collapsedCount > 0) {
1363
- contentLines.push(`- Collapsed ${collapsedCount} earlier updates.`);
1460
+ contentLines.push(`- ${this.#t("progress.collapsed", { count: collapsedCount })}`);
1364
1461
  }
1365
1462
  for (const id of this.#visibleOrder()) {
1366
1463
  const entry = this.#entries.get(id);
1367
1464
  if (!entry) continue;
1368
1465
  contentLines.push(entry.kind === "line" ? entry.line : this.#formatToolEntry(entry));
1369
1466
  }
1370
- return contentLines.length > 0 ? contentLines.join("\n") : NO_PROGRESS_MARKDOWN;
1467
+ return contentLines.length > 0 ? contentLines.join("\n") : this.#t("progress.noProgress");
1371
1468
  }
1372
1469
  #visibleOrder() {
1373
1470
  return this.#order.slice(-this.#collapseThreshold);
@@ -1399,9 +1496,9 @@ var ProgressRenderer = class {
1399
1496
  const subject = this.#formatToolSubject(entry.toolName, entry.toolLabel);
1400
1497
  switch (entry.status) {
1401
1498
  case "running":
1402
- return `- Running ${subject}`;
1499
+ return `- ${this.#t("progress.running", { subject })}`;
1403
1500
  case "done":
1404
- return `- Finished ${subject}`;
1501
+ return `- ${this.#t("progress.finished", { subject })}`;
1405
1502
  case "error":
1406
1503
  return this.#formatToolErrorLine(subject, entry.text);
1407
1504
  }
@@ -1409,12 +1506,12 @@ var ProgressRenderer = class {
1409
1506
  #formatProgressLine(event) {
1410
1507
  switch (event.type) {
1411
1508
  case "session.compacting":
1412
- return `- Compacting session${event.text ? `: ${event.text}` : ""}`;
1509
+ return `- ${event.text ? this.#t("progress.compactingWithDetail", { detail: event.text }) : this.#t("progress.compacting")}`;
1413
1510
  case "assistant.tool.running":
1414
1511
  case "assistant.tool.update":
1415
- return `- Running ${this.#formatToolSubject(event.toolName, event.toolLabel)}`;
1512
+ return `- ${this.#t("progress.running", { subject: this.#formatToolSubject(event.toolName, event.toolLabel) })}`;
1416
1513
  case "assistant.tool.done":
1417
- return `- Finished ${this.#formatToolSubject(event.toolName, event.toolLabel)}`;
1514
+ return `- ${this.#t("progress.finished", { subject: this.#formatToolSubject(event.toolName, event.toolLabel) })}`;
1418
1515
  case "assistant.tool.error":
1419
1516
  return this.#formatToolErrorLine(this.#formatToolSubject(event.toolName, event.toolLabel), event.text);
1420
1517
  }
@@ -1436,13 +1533,13 @@ var ProgressRenderer = class {
1436
1533
  }
1437
1534
  const lowerText = normalizedText.toLowerCase();
1438
1535
  const lowerToolName = toolName.toLowerCase();
1439
- if (lowerText === lowerToolName || lowerText === `failed ${lowerToolName}` || lowerText === `running ${lowerToolName}` || lowerText === `finished ${lowerToolName}`) {
1536
+ if (lowerText === lowerToolName || lowerText === this.#t("progress.failed", { subject: toolName }).toLowerCase() || lowerText === this.#t("progress.running", { subject: toolName }).toLowerCase() || lowerText === this.#t("progress.finished", { subject: toolName }).toLowerCase()) {
1440
1537
  return `- ${this.#humanizeToolError(toolName)}`;
1441
1538
  }
1442
- return `- ${this.#humanizeToolError(toolName)}: ${normalizedText}`;
1539
+ return `- ${this.#t("progress.failedWithDetail", { subject: toolName, detail: normalizedText })}`;
1443
1540
  }
1444
1541
  #humanizeToolError(toolName) {
1445
- return `Failed ${toolName}`;
1542
+ return this.#t("progress.failed", { subject: toolName });
1446
1543
  }
1447
1544
  #progressStatus(event) {
1448
1545
  switch (event.type) {
@@ -1457,6 +1554,18 @@ var ProgressRenderer = class {
1457
1554
  };
1458
1555
 
1459
1556
  // src/modules/client/utils/slash-commands.ts
1557
+ function isHelpCommand(text2) {
1558
+ switch (text2.toLowerCase()) {
1559
+ case "/help":
1560
+ case "/h":
1561
+ return true;
1562
+ default:
1563
+ return false;
1564
+ }
1565
+ }
1566
+ function resolveHelpMarkdown(text2, t) {
1567
+ return isHelpCommand(text2) ? t("client.helpMessage") : null;
1568
+ }
1460
1569
  function parseSlashCommand(text2, clientSessionId) {
1461
1570
  switch (text2.toLowerCase()) {
1462
1571
  case "/new":
@@ -1466,6 +1575,7 @@ function parseSlashCommand(text2, clientSessionId) {
1466
1575
  case "/c":
1467
1576
  return { type: "command.session.compact", clientSessionId };
1468
1577
  case "/stop":
1578
+ case "/s":
1469
1579
  return { type: "command.session.stop", clientSessionId };
1470
1580
  default:
1471
1581
  return null;
@@ -1818,6 +1928,7 @@ function chunkText(text2, maxLen) {
1818
1928
  var FeishuIMAdapter = class _FeishuIMAdapter {
1819
1929
  #config;
1820
1930
  #logger;
1931
+ #t;
1821
1932
  #onOutput = null;
1822
1933
  #client = null;
1823
1934
  #egressQueue = [];
@@ -1842,18 +1953,17 @@ var FeishuIMAdapter = class _FeishuIMAdapter {
1842
1953
  return;
1843
1954
  }
1844
1955
  const message = error instanceof Error ? error.message : String(error);
1845
- const text2 = `[agent-bridge error] Message delivery failed
1846
-
1847
- ${message}`;
1956
+ const text2 = formatSendFailureNotice(this.#t, message);
1848
1957
  try {
1849
1958
  await this.#client.sendText(chatId, text2);
1850
1959
  } catch (notifyError) {
1851
1960
  this.#logger.error("failed to notify send failure:", notifyError);
1852
1961
  }
1853
1962
  }
1854
- constructor(config, logger3 = createLogger("feishu")) {
1963
+ constructor(config, logger3 = createLogger("feishu"), common) {
1855
1964
  this.#config = config;
1856
1965
  this.#logger = logger3;
1966
+ this.#t = getTranslatorForCommon(common);
1857
1967
  }
1858
1968
  async start(onOutput) {
1859
1969
  this.#onOutput = onOutput;
@@ -1870,16 +1980,22 @@ ${message}`;
1870
1980
  );
1871
1981
  return;
1872
1982
  }
1873
- this.#lastInboundMessageIdBySession.set(clientSessionId, messageId);
1874
- this.#resetProgressState(clientSessionId);
1875
- await this.#client?.startTyping(chatId, messageId);
1876
1983
  const normalizedText = text2.trim();
1984
+ const helpMarkdown = resolveHelpMarkdown(normalizedText, this.#t);
1985
+ if (helpMarkdown) {
1986
+ this.#logger.info(`received local help command ${normalizedText} (session=${clientSessionId})`);
1987
+ await this.#client?.sendText(chatId, helpMarkdown, messageId);
1988
+ return;
1989
+ }
1877
1990
  const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
1878
1991
  if (commandEvent) {
1879
1992
  this.#logger.info(`received command ${normalizedText} (session=${clientSessionId})`);
1880
1993
  await this.#onOutput(commandEvent);
1881
1994
  return;
1882
1995
  }
1996
+ this.#lastInboundMessageIdBySession.set(clientSessionId, messageId);
1997
+ this.#resetProgressState(clientSessionId);
1998
+ await this.#client?.startTyping(chatId, messageId);
1883
1999
  this.#logger.info(`received user message (session=${clientSessionId}): ${normalizedText}`);
1884
2000
  await this.#onOutput({
1885
2001
  type: "user.message",
@@ -1966,7 +2082,7 @@ ${message}`;
1966
2082
  return;
1967
2083
  }
1968
2084
  const state = this.#progressStateBySession.get(event.clientSessionId) ?? {
1969
- renderer: new ProgressRenderer(),
2085
+ renderer: new ProgressRenderer({ t: this.#t }),
1970
2086
  messageId: null,
1971
2087
  creating: false
1972
2088
  };
@@ -1996,7 +2112,7 @@ ${message}`;
1996
2112
  }
1997
2113
  #resetProgressState(clientSessionId) {
1998
2114
  this.#progressStateBySession.set(clientSessionId, {
1999
- renderer: new ProgressRenderer(),
2115
+ renderer: new ProgressRenderer({ t: this.#t }),
2000
2116
  messageId: null,
2001
2117
  creating: false
2002
2118
  });
@@ -2048,8 +2164,8 @@ function createFeishuConfigCollector() {
2048
2164
  var feishuClientModule = {
2049
2165
  type: "feishu",
2050
2166
  createConfigCollector: createFeishuConfigCollector,
2051
- createClientAdapter(config) {
2052
- return new FeishuIMAdapter(config);
2167
+ createClientAdapter({ config, common }) {
2168
+ return new FeishuIMAdapter(config, void 0, common);
2053
2169
  }
2054
2170
  };
2055
2171
 
@@ -2519,7 +2635,6 @@ function parseWecomSessionId(clientSessionId) {
2519
2635
 
2520
2636
  // src/modules/client/wecom/adapter/wecom-im-adapter.ts
2521
2637
  var MAX_TEXT_CHUNK2 = 4e3;
2522
- var STARTING_MESSAGE = "Processing...";
2523
2638
  function chunkText2(text2, maxLen) {
2524
2639
  if (text2.length <= maxLen) return [text2];
2525
2640
  const chunks = [];
@@ -2546,6 +2661,7 @@ function chunkText2(text2, maxLen) {
2546
2661
  var WecomIMAdapter = class {
2547
2662
  #config;
2548
2663
  #logger;
2664
+ #t;
2549
2665
  #onOutput = null;
2550
2666
  #client = null;
2551
2667
  #egressQueue = [];
@@ -2561,18 +2677,17 @@ var WecomIMAdapter = class {
2561
2677
  return;
2562
2678
  }
2563
2679
  const message = error instanceof Error ? error.message : String(error);
2564
- const text2 = `[agent-bridge error] Message delivery failed
2565
-
2566
- ${message}`;
2680
+ const text2 = formatSendFailureNotice(this.#t, message);
2567
2681
  try {
2568
2682
  await this.#client.sendText(chatId, text2);
2569
2683
  } catch (notifyError) {
2570
2684
  this.#logger.error("failed to notify send failure:", notifyError);
2571
2685
  }
2572
2686
  }
2573
- constructor(config, logger3 = createLogger("wecom")) {
2687
+ constructor(config, logger3 = createLogger("wecom"), common) {
2574
2688
  this.#config = config;
2575
2689
  this.#logger = logger3;
2690
+ this.#t = getTranslatorForCommon(common);
2576
2691
  }
2577
2692
  async start(onOutput) {
2578
2693
  this.#onOutput = onOutput;
@@ -2598,6 +2713,11 @@ ${message}`;
2598
2713
  return;
2599
2714
  }
2600
2715
  const normalizedText = text2.trim();
2716
+ const helpMarkdown = resolveHelpMarkdown(normalizedText, this.#t);
2717
+ if (helpMarkdown) {
2718
+ await this.#client?.sendText(chatId, helpMarkdown, messageId);
2719
+ return;
2720
+ }
2601
2721
  const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
2602
2722
  if (commandEvent) {
2603
2723
  await this.#onOutput(commandEvent);
@@ -2688,7 +2808,7 @@ ${message}`;
2688
2808
  if (!state || state.announced || !this.#client) {
2689
2809
  return;
2690
2810
  }
2691
- await this.#client.sendStreamText(chatId, STARTING_MESSAGE, {
2811
+ await this.#client.sendStreamText(chatId, this.#t("client.processing"), {
2692
2812
  replyToMessageId: messageId,
2693
2813
  finish: false
2694
2814
  });
@@ -2699,7 +2819,7 @@ ${message}`;
2699
2819
  return;
2700
2820
  }
2701
2821
  const state = this.#progressStateBySession.get(event.clientSessionId) ?? {
2702
- renderer: new ProgressRenderer(),
2822
+ renderer: new ProgressRenderer({ t: this.#t }),
2703
2823
  announced: false
2704
2824
  };
2705
2825
  this.#progressStateBySession.set(event.clientSessionId, state);
@@ -2722,7 +2842,7 @@ ${message}`;
2722
2842
  }
2723
2843
  this.#progressStateBySession.delete(clientSessionId);
2724
2844
  const progress = state.renderer.getCurrentProgress();
2725
- const body = progress.markdown === NO_PROGRESS_MARKDOWN ? STARTING_MESSAGE : progress.markdown;
2845
+ const body = progress.isEmpty ? this.#t("client.processing") : progress.markdown;
2726
2846
  try {
2727
2847
  await this.#client.sendStreamText(chatId, body, { replyToMessageId, finish: true });
2728
2848
  } catch (error) {
@@ -2731,7 +2851,7 @@ ${message}`;
2731
2851
  }
2732
2852
  #resetProgressState(clientSessionId) {
2733
2853
  this.#progressStateBySession.set(clientSessionId, {
2734
- renderer: new ProgressRenderer(),
2854
+ renderer: new ProgressRenderer({ t: this.#t }),
2735
2855
  announced: false
2736
2856
  });
2737
2857
  }
@@ -2778,8 +2898,8 @@ function createWecomConfigCollector() {
2778
2898
  var wecomClientModule = {
2779
2899
  type: "wecom",
2780
2900
  createConfigCollector: createWecomConfigCollector,
2781
- createClientAdapter(config) {
2782
- return new WecomIMAdapter(config);
2901
+ createClientAdapter({ config, common }) {
2902
+ return new WecomIMAdapter(config, void 0, common);
2783
2903
  }
2784
2904
  };
2785
2905
 
@@ -3087,6 +3207,7 @@ function chunkText3(text2, maxLen) {
3087
3207
  var WeixinIMAdapter = class {
3088
3208
  #config;
3089
3209
  #logger;
3210
+ #t;
3090
3211
  #onOutput = null;
3091
3212
  #client = null;
3092
3213
  #egressQueue = [];
@@ -3097,9 +3218,10 @@ var WeixinIMAdapter = class {
3097
3218
  #recentInboundContentKeys = /* @__PURE__ */ new Map();
3098
3219
  #rateLimitEvents = [];
3099
3220
  #rateLimitCircuitUntil = 0;
3100
- constructor(config, logger3 = createLogger("weixin")) {
3221
+ constructor(config, logger3 = createLogger("weixin"), common) {
3101
3222
  this.#config = config;
3102
3223
  this.#logger = logger3;
3224
+ this.#t = getTranslatorForCommon(common);
3103
3225
  }
3104
3226
  async start(onOutput) {
3105
3227
  this.#onOutput = onOutput;
@@ -3120,8 +3242,13 @@ var WeixinIMAdapter = class {
3120
3242
  this.#logger.debug(`ignoring unsupported Weixin group message (session=${clientSessionId})`);
3121
3243
  return;
3122
3244
  }
3123
- this.#resetProgressState(clientSessionId);
3124
3245
  const normalizedText = text2.trim();
3246
+ const helpMarkdown = resolveHelpMarkdown(normalizedText, this.#t);
3247
+ if (helpMarkdown) {
3248
+ await this.#client?.sendText(chatId, helpMarkdown);
3249
+ return;
3250
+ }
3251
+ this.#resetProgressState(clientSessionId);
3125
3252
  const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
3126
3253
  if (commandEvent) {
3127
3254
  await this.#onOutput(commandEvent);
@@ -3241,9 +3368,7 @@ var WeixinIMAdapter = class {
3241
3368
  return;
3242
3369
  }
3243
3370
  const message = error instanceof Error ? error.message : String(error);
3244
- const text2 = `[agent-bridge error] Message delivery failed
3245
-
3246
- ${message}`;
3371
+ const text2 = formatSendFailureNotice(this.#t, message);
3247
3372
  try {
3248
3373
  await this.#client.sendText(chatId, text2);
3249
3374
  } catch (notifyError) {
@@ -3256,7 +3381,7 @@ ${message}`;
3256
3381
  }
3257
3382
  const now = Date.now();
3258
3383
  if (this.#rateLimitCircuitUntil > now) {
3259
- throw new Error("Weixin send is cooling down after rate limiting. Please try again shortly.");
3384
+ throw new Error(this.#t("client.weixinCooldown"));
3260
3385
  }
3261
3386
  if (this.#rateLimitCircuitUntil !== 0 && this.#rateLimitCircuitUntil <= now) {
3262
3387
  this.#rateLimitCircuitUntil = 0;
@@ -3272,7 +3397,7 @@ ${message}`;
3272
3397
  if (this.#isRateLimitError(error)) {
3273
3398
  this.#recordRateLimitEvent(now);
3274
3399
  if (this.#rateLimitCircuitUntil > now) {
3275
- throw new Error("Weixin send is cooling down after rate limiting. Please try again shortly.");
3400
+ throw new Error(this.#t("client.weixinCooldown"));
3276
3401
  }
3277
3402
  }
3278
3403
  throw error;
@@ -3301,7 +3426,7 @@ ${message}`;
3301
3426
  }
3302
3427
  #createProgressState() {
3303
3428
  return {
3304
- renderer: new ProgressRenderer(),
3429
+ renderer: new ProgressRenderer({ t: this.#t }),
3305
3430
  dirty: false,
3306
3431
  interval: null
3307
3432
  };
@@ -3486,8 +3611,8 @@ function createWeixinConfigCollector() {
3486
3611
  var weixinClientModule = {
3487
3612
  type: "weixin",
3488
3613
  createConfigCollector: createWeixinConfigCollector,
3489
- createClientAdapter(config) {
3490
- return new WeixinIMAdapter(config);
3614
+ createClientAdapter({ config, common }) {
3615
+ return new WeixinIMAdapter(config, void 0, common);
3491
3616
  }
3492
3617
  };
3493
3618
 
@@ -3516,14 +3641,19 @@ var logger2 = createLogger("runner");
3516
3641
  async function runChannel({ channelName, channelConfig, defaults }) {
3517
3642
  const clientModule = getTypedClientModule(channelConfig.client);
3518
3643
  const agentModule = getTypedAgentModule(channelConfig.agent);
3519
- const imAdapter = clientModule.createClientAdapter(channelConfig.client.config);
3644
+ const common = {
3645
+ channelName,
3646
+ language: channelConfig.common.language
3647
+ };
3648
+ const imAdapter = clientModule.createClientAdapter({ config: channelConfig.client.config, common });
3520
3649
  const bindingStore = createFileSessionBindingStore(getSessionBindingStorePath(channelName));
3521
3650
  const core = new GatewayCore({
3522
3651
  imAdapter,
3523
3652
  agentModule,
3524
3653
  agentConfig: channelConfig.agent.config,
3525
3654
  agentIdleTimeoutMs: defaults.agentIdleTimeoutMs,
3526
- bindingStore
3655
+ bindingStore,
3656
+ common
3527
3657
  });
3528
3658
  await core.start();
3529
3659
  logger2.info(`channel ${channelName} started`);
@@ -3537,6 +3667,8 @@ async function runChannel({ channelName, channelConfig, defaults }) {
3537
3667
  }
3538
3668
 
3539
3669
  // src/cli.ts
3670
+ var require2 = createRequire(import.meta.url);
3671
+ var { version } = require2("../package.json");
3540
3672
  async function selectModuleType(label, modules, ctx) {
3541
3673
  if (modules.length === 0) {
3542
3674
  throw new Error(`No modules available for ${label}`);
@@ -3558,6 +3690,15 @@ async function collectModuleConfig(module, ctx) {
3558
3690
  await collector.validate(config);
3559
3691
  return config;
3560
3692
  }
3693
+ async function collectCommonChannelConfig(ctx) {
3694
+ const language = await ctx.select("Channel language", [
3695
+ { label: "English (en-US)", value: "en-US" },
3696
+ { label: "\u4E2D\u6587 (zh-CN)", value: "zh-CN" }
3697
+ ]);
3698
+ return {
3699
+ language
3700
+ };
3701
+ }
3561
3702
  async function addChannel(config) {
3562
3703
  const ctx = createPromptContext();
3563
3704
  try {
@@ -3569,6 +3710,7 @@ async function addChannel(config) {
3569
3710
  return null;
3570
3711
  }
3571
3712
  });
3713
+ const commonConfig = await collectCommonChannelConfig(ctx);
3572
3714
  const clientType = await selectModuleType("Select client module", listClientModules(), ctx);
3573
3715
  const clientModule = getClientModule(clientType);
3574
3716
  if (!clientModule) {
@@ -3582,6 +3724,7 @@ async function addChannel(config) {
3582
3724
  }
3583
3725
  const agentConfig = await collectModuleConfig(agentModule, ctx);
3584
3726
  config.channels[name] = {
3727
+ common: commonConfig,
3585
3728
  client: {
3586
3729
  type: clientType,
3587
3730
  config: clientConfig
@@ -3660,7 +3803,7 @@ async function startChannel(channelName) {
3660
3803
  }
3661
3804
  async function runCli(argv = process2.argv) {
3662
3805
  const program = new Command();
3663
- program.name("agent-bridge").description("IM to Agent bridge CLI").version("0.1.0");
3806
+ program.name("agent-bridge").description("IM to Agent bridge CLI").version(version);
3664
3807
  program.command("add").description("Interactively add a channel").action(async () => {
3665
3808
  const config = await loadConfig();
3666
3809
  await addChannel(config);