@hopgoldy/agent-bridge 0.3.0 → 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/README.md CHANGED
@@ -78,6 +78,8 @@ agent-bridge remove <channel-name>
78
78
 
79
79
  Config file: `~/.config/agent-bridge/config.json`
80
80
 
81
+ Architecture overview: [`docs/architecture-design.md`](./docs/architecture-design.md)
82
+
81
83
  Command usage across IM adapters: [`docs/command-system.md`](./docs/command-system.md)
82
84
 
83
85
  ## Development
@@ -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);
@@ -1380,9 +1477,6 @@ var ProgressRenderer = class {
1380
1477
  #upsertToolEntry(id, event) {
1381
1478
  if (!this.#entries.has(id)) {
1382
1479
  this.#order.push(id);
1383
- } else {
1384
- this.#order = this.#order.filter((entryId) => entryId !== id);
1385
- this.#order.push(id);
1386
1480
  }
1387
1481
  this.#entries.set(id, {
1388
1482
  kind: "tool",
@@ -1402,9 +1496,9 @@ var ProgressRenderer = class {
1402
1496
  const subject = this.#formatToolSubject(entry.toolName, entry.toolLabel);
1403
1497
  switch (entry.status) {
1404
1498
  case "running":
1405
- return `- Running ${subject}`;
1499
+ return `- ${this.#t("progress.running", { subject })}`;
1406
1500
  case "done":
1407
- return `- Finished ${subject}`;
1501
+ return `- ${this.#t("progress.finished", { subject })}`;
1408
1502
  case "error":
1409
1503
  return this.#formatToolErrorLine(subject, entry.text);
1410
1504
  }
@@ -1412,12 +1506,12 @@ var ProgressRenderer = class {
1412
1506
  #formatProgressLine(event) {
1413
1507
  switch (event.type) {
1414
1508
  case "session.compacting":
1415
- return `- Compacting session${event.text ? `: ${event.text}` : ""}`;
1509
+ return `- ${event.text ? this.#t("progress.compactingWithDetail", { detail: event.text }) : this.#t("progress.compacting")}`;
1416
1510
  case "assistant.tool.running":
1417
1511
  case "assistant.tool.update":
1418
- return `- Running ${this.#formatToolSubject(event.toolName, event.toolLabel)}`;
1512
+ return `- ${this.#t("progress.running", { subject: this.#formatToolSubject(event.toolName, event.toolLabel) })}`;
1419
1513
  case "assistant.tool.done":
1420
- return `- Finished ${this.#formatToolSubject(event.toolName, event.toolLabel)}`;
1514
+ return `- ${this.#t("progress.finished", { subject: this.#formatToolSubject(event.toolName, event.toolLabel) })}`;
1421
1515
  case "assistant.tool.error":
1422
1516
  return this.#formatToolErrorLine(this.#formatToolSubject(event.toolName, event.toolLabel), event.text);
1423
1517
  }
@@ -1439,13 +1533,13 @@ var ProgressRenderer = class {
1439
1533
  }
1440
1534
  const lowerText = normalizedText.toLowerCase();
1441
1535
  const lowerToolName = toolName.toLowerCase();
1442
- 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()) {
1443
1537
  return `- ${this.#humanizeToolError(toolName)}`;
1444
1538
  }
1445
- return `- ${this.#humanizeToolError(toolName)}: ${normalizedText}`;
1539
+ return `- ${this.#t("progress.failedWithDetail", { subject: toolName, detail: normalizedText })}`;
1446
1540
  }
1447
1541
  #humanizeToolError(toolName) {
1448
- return `Failed ${toolName}`;
1542
+ return this.#t("progress.failed", { subject: toolName });
1449
1543
  }
1450
1544
  #progressStatus(event) {
1451
1545
  switch (event.type) {
@@ -1460,6 +1554,18 @@ var ProgressRenderer = class {
1460
1554
  };
1461
1555
 
1462
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
+ }
1463
1569
  function parseSlashCommand(text2, clientSessionId) {
1464
1570
  switch (text2.toLowerCase()) {
1465
1571
  case "/new":
@@ -1469,6 +1575,7 @@ function parseSlashCommand(text2, clientSessionId) {
1469
1575
  case "/c":
1470
1576
  return { type: "command.session.compact", clientSessionId };
1471
1577
  case "/stop":
1578
+ case "/s":
1472
1579
  return { type: "command.session.stop", clientSessionId };
1473
1580
  default:
1474
1581
  return null;
@@ -1821,6 +1928,7 @@ function chunkText(text2, maxLen) {
1821
1928
  var FeishuIMAdapter = class _FeishuIMAdapter {
1822
1929
  #config;
1823
1930
  #logger;
1931
+ #t;
1824
1932
  #onOutput = null;
1825
1933
  #client = null;
1826
1934
  #egressQueue = [];
@@ -1845,18 +1953,17 @@ var FeishuIMAdapter = class _FeishuIMAdapter {
1845
1953
  return;
1846
1954
  }
1847
1955
  const message = error instanceof Error ? error.message : String(error);
1848
- const text2 = `[agent-bridge error] Message delivery failed
1849
-
1850
- ${message}`;
1956
+ const text2 = formatSendFailureNotice(this.#t, message);
1851
1957
  try {
1852
1958
  await this.#client.sendText(chatId, text2);
1853
1959
  } catch (notifyError) {
1854
1960
  this.#logger.error("failed to notify send failure:", notifyError);
1855
1961
  }
1856
1962
  }
1857
- constructor(config, logger3 = createLogger("feishu")) {
1963
+ constructor(config, logger3 = createLogger("feishu"), common) {
1858
1964
  this.#config = config;
1859
1965
  this.#logger = logger3;
1966
+ this.#t = getTranslatorForCommon(common);
1860
1967
  }
1861
1968
  async start(onOutput) {
1862
1969
  this.#onOutput = onOutput;
@@ -1873,16 +1980,22 @@ ${message}`;
1873
1980
  );
1874
1981
  return;
1875
1982
  }
1876
- this.#lastInboundMessageIdBySession.set(clientSessionId, messageId);
1877
- this.#resetProgressState(clientSessionId);
1878
- await this.#client?.startTyping(chatId, messageId);
1879
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
+ }
1880
1990
  const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
1881
1991
  if (commandEvent) {
1882
1992
  this.#logger.info(`received command ${normalizedText} (session=${clientSessionId})`);
1883
1993
  await this.#onOutput(commandEvent);
1884
1994
  return;
1885
1995
  }
1996
+ this.#lastInboundMessageIdBySession.set(clientSessionId, messageId);
1997
+ this.#resetProgressState(clientSessionId);
1998
+ await this.#client?.startTyping(chatId, messageId);
1886
1999
  this.#logger.info(`received user message (session=${clientSessionId}): ${normalizedText}`);
1887
2000
  await this.#onOutput({
1888
2001
  type: "user.message",
@@ -1969,7 +2082,7 @@ ${message}`;
1969
2082
  return;
1970
2083
  }
1971
2084
  const state = this.#progressStateBySession.get(event.clientSessionId) ?? {
1972
- renderer: new ProgressRenderer(),
2085
+ renderer: new ProgressRenderer({ t: this.#t }),
1973
2086
  messageId: null,
1974
2087
  creating: false
1975
2088
  };
@@ -1999,7 +2112,7 @@ ${message}`;
1999
2112
  }
2000
2113
  #resetProgressState(clientSessionId) {
2001
2114
  this.#progressStateBySession.set(clientSessionId, {
2002
- renderer: new ProgressRenderer(),
2115
+ renderer: new ProgressRenderer({ t: this.#t }),
2003
2116
  messageId: null,
2004
2117
  creating: false
2005
2118
  });
@@ -2051,8 +2164,8 @@ function createFeishuConfigCollector() {
2051
2164
  var feishuClientModule = {
2052
2165
  type: "feishu",
2053
2166
  createConfigCollector: createFeishuConfigCollector,
2054
- createClientAdapter(config) {
2055
- return new FeishuIMAdapter(config);
2167
+ createClientAdapter({ config, common }) {
2168
+ return new FeishuIMAdapter(config, void 0, common);
2056
2169
  }
2057
2170
  };
2058
2171
 
@@ -2522,7 +2635,6 @@ function parseWecomSessionId(clientSessionId) {
2522
2635
 
2523
2636
  // src/modules/client/wecom/adapter/wecom-im-adapter.ts
2524
2637
  var MAX_TEXT_CHUNK2 = 4e3;
2525
- var STARTING_MESSAGE = "Processing...";
2526
2638
  function chunkText2(text2, maxLen) {
2527
2639
  if (text2.length <= maxLen) return [text2];
2528
2640
  const chunks = [];
@@ -2549,6 +2661,7 @@ function chunkText2(text2, maxLen) {
2549
2661
  var WecomIMAdapter = class {
2550
2662
  #config;
2551
2663
  #logger;
2664
+ #t;
2552
2665
  #onOutput = null;
2553
2666
  #client = null;
2554
2667
  #egressQueue = [];
@@ -2564,18 +2677,17 @@ var WecomIMAdapter = class {
2564
2677
  return;
2565
2678
  }
2566
2679
  const message = error instanceof Error ? error.message : String(error);
2567
- const text2 = `[agent-bridge error] Message delivery failed
2568
-
2569
- ${message}`;
2680
+ const text2 = formatSendFailureNotice(this.#t, message);
2570
2681
  try {
2571
2682
  await this.#client.sendText(chatId, text2);
2572
2683
  } catch (notifyError) {
2573
2684
  this.#logger.error("failed to notify send failure:", notifyError);
2574
2685
  }
2575
2686
  }
2576
- constructor(config, logger3 = createLogger("wecom")) {
2687
+ constructor(config, logger3 = createLogger("wecom"), common) {
2577
2688
  this.#config = config;
2578
2689
  this.#logger = logger3;
2690
+ this.#t = getTranslatorForCommon(common);
2579
2691
  }
2580
2692
  async start(onOutput) {
2581
2693
  this.#onOutput = onOutput;
@@ -2601,6 +2713,11 @@ ${message}`;
2601
2713
  return;
2602
2714
  }
2603
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
+ }
2604
2721
  const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
2605
2722
  if (commandEvent) {
2606
2723
  await this.#onOutput(commandEvent);
@@ -2691,7 +2808,7 @@ ${message}`;
2691
2808
  if (!state || state.announced || !this.#client) {
2692
2809
  return;
2693
2810
  }
2694
- await this.#client.sendStreamText(chatId, STARTING_MESSAGE, {
2811
+ await this.#client.sendStreamText(chatId, this.#t("client.processing"), {
2695
2812
  replyToMessageId: messageId,
2696
2813
  finish: false
2697
2814
  });
@@ -2702,7 +2819,7 @@ ${message}`;
2702
2819
  return;
2703
2820
  }
2704
2821
  const state = this.#progressStateBySession.get(event.clientSessionId) ?? {
2705
- renderer: new ProgressRenderer(),
2822
+ renderer: new ProgressRenderer({ t: this.#t }),
2706
2823
  announced: false
2707
2824
  };
2708
2825
  this.#progressStateBySession.set(event.clientSessionId, state);
@@ -2725,7 +2842,7 @@ ${message}`;
2725
2842
  }
2726
2843
  this.#progressStateBySession.delete(clientSessionId);
2727
2844
  const progress = state.renderer.getCurrentProgress();
2728
- const body = progress.markdown === NO_PROGRESS_MARKDOWN ? STARTING_MESSAGE : progress.markdown;
2845
+ const body = progress.isEmpty ? this.#t("client.processing") : progress.markdown;
2729
2846
  try {
2730
2847
  await this.#client.sendStreamText(chatId, body, { replyToMessageId, finish: true });
2731
2848
  } catch (error) {
@@ -2734,7 +2851,7 @@ ${message}`;
2734
2851
  }
2735
2852
  #resetProgressState(clientSessionId) {
2736
2853
  this.#progressStateBySession.set(clientSessionId, {
2737
- renderer: new ProgressRenderer(),
2854
+ renderer: new ProgressRenderer({ t: this.#t }),
2738
2855
  announced: false
2739
2856
  });
2740
2857
  }
@@ -2781,8 +2898,8 @@ function createWecomConfigCollector() {
2781
2898
  var wecomClientModule = {
2782
2899
  type: "wecom",
2783
2900
  createConfigCollector: createWecomConfigCollector,
2784
- createClientAdapter(config) {
2785
- return new WecomIMAdapter(config);
2901
+ createClientAdapter({ config, common }) {
2902
+ return new WecomIMAdapter(config, void 0, common);
2786
2903
  }
2787
2904
  };
2788
2905
 
@@ -3090,6 +3207,7 @@ function chunkText3(text2, maxLen) {
3090
3207
  var WeixinIMAdapter = class {
3091
3208
  #config;
3092
3209
  #logger;
3210
+ #t;
3093
3211
  #onOutput = null;
3094
3212
  #client = null;
3095
3213
  #egressQueue = [];
@@ -3100,9 +3218,10 @@ var WeixinIMAdapter = class {
3100
3218
  #recentInboundContentKeys = /* @__PURE__ */ new Map();
3101
3219
  #rateLimitEvents = [];
3102
3220
  #rateLimitCircuitUntil = 0;
3103
- constructor(config, logger3 = createLogger("weixin")) {
3221
+ constructor(config, logger3 = createLogger("weixin"), common) {
3104
3222
  this.#config = config;
3105
3223
  this.#logger = logger3;
3224
+ this.#t = getTranslatorForCommon(common);
3106
3225
  }
3107
3226
  async start(onOutput) {
3108
3227
  this.#onOutput = onOutput;
@@ -3123,8 +3242,13 @@ var WeixinIMAdapter = class {
3123
3242
  this.#logger.debug(`ignoring unsupported Weixin group message (session=${clientSessionId})`);
3124
3243
  return;
3125
3244
  }
3126
- this.#resetProgressState(clientSessionId);
3127
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);
3128
3252
  const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
3129
3253
  if (commandEvent) {
3130
3254
  await this.#onOutput(commandEvent);
@@ -3244,9 +3368,7 @@ var WeixinIMAdapter = class {
3244
3368
  return;
3245
3369
  }
3246
3370
  const message = error instanceof Error ? error.message : String(error);
3247
- const text2 = `[agent-bridge error] Message delivery failed
3248
-
3249
- ${message}`;
3371
+ const text2 = formatSendFailureNotice(this.#t, message);
3250
3372
  try {
3251
3373
  await this.#client.sendText(chatId, text2);
3252
3374
  } catch (notifyError) {
@@ -3259,7 +3381,7 @@ ${message}`;
3259
3381
  }
3260
3382
  const now = Date.now();
3261
3383
  if (this.#rateLimitCircuitUntil > now) {
3262
- throw new Error("Weixin send is cooling down after rate limiting. Please try again shortly.");
3384
+ throw new Error(this.#t("client.weixinCooldown"));
3263
3385
  }
3264
3386
  if (this.#rateLimitCircuitUntil !== 0 && this.#rateLimitCircuitUntil <= now) {
3265
3387
  this.#rateLimitCircuitUntil = 0;
@@ -3275,7 +3397,7 @@ ${message}`;
3275
3397
  if (this.#isRateLimitError(error)) {
3276
3398
  this.#recordRateLimitEvent(now);
3277
3399
  if (this.#rateLimitCircuitUntil > now) {
3278
- throw new Error("Weixin send is cooling down after rate limiting. Please try again shortly.");
3400
+ throw new Error(this.#t("client.weixinCooldown"));
3279
3401
  }
3280
3402
  }
3281
3403
  throw error;
@@ -3304,7 +3426,7 @@ ${message}`;
3304
3426
  }
3305
3427
  #createProgressState() {
3306
3428
  return {
3307
- renderer: new ProgressRenderer(),
3429
+ renderer: new ProgressRenderer({ t: this.#t }),
3308
3430
  dirty: false,
3309
3431
  interval: null
3310
3432
  };
@@ -3489,8 +3611,8 @@ function createWeixinConfigCollector() {
3489
3611
  var weixinClientModule = {
3490
3612
  type: "weixin",
3491
3613
  createConfigCollector: createWeixinConfigCollector,
3492
- createClientAdapter(config) {
3493
- return new WeixinIMAdapter(config);
3614
+ createClientAdapter({ config, common }) {
3615
+ return new WeixinIMAdapter(config, void 0, common);
3494
3616
  }
3495
3617
  };
3496
3618
 
@@ -3519,14 +3641,19 @@ var logger2 = createLogger("runner");
3519
3641
  async function runChannel({ channelName, channelConfig, defaults }) {
3520
3642
  const clientModule = getTypedClientModule(channelConfig.client);
3521
3643
  const agentModule = getTypedAgentModule(channelConfig.agent);
3522
- 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 });
3523
3649
  const bindingStore = createFileSessionBindingStore(getSessionBindingStorePath(channelName));
3524
3650
  const core = new GatewayCore({
3525
3651
  imAdapter,
3526
3652
  agentModule,
3527
3653
  agentConfig: channelConfig.agent.config,
3528
3654
  agentIdleTimeoutMs: defaults.agentIdleTimeoutMs,
3529
- bindingStore
3655
+ bindingStore,
3656
+ common
3530
3657
  });
3531
3658
  await core.start();
3532
3659
  logger2.info(`channel ${channelName} started`);
@@ -3540,6 +3667,8 @@ async function runChannel({ channelName, channelConfig, defaults }) {
3540
3667
  }
3541
3668
 
3542
3669
  // src/cli.ts
3670
+ var require2 = createRequire(import.meta.url);
3671
+ var { version } = require2("../package.json");
3543
3672
  async function selectModuleType(label, modules, ctx) {
3544
3673
  if (modules.length === 0) {
3545
3674
  throw new Error(`No modules available for ${label}`);
@@ -3561,6 +3690,15 @@ async function collectModuleConfig(module, ctx) {
3561
3690
  await collector.validate(config);
3562
3691
  return config;
3563
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
+ }
3564
3702
  async function addChannel(config) {
3565
3703
  const ctx = createPromptContext();
3566
3704
  try {
@@ -3572,6 +3710,7 @@ async function addChannel(config) {
3572
3710
  return null;
3573
3711
  }
3574
3712
  });
3713
+ const commonConfig = await collectCommonChannelConfig(ctx);
3575
3714
  const clientType = await selectModuleType("Select client module", listClientModules(), ctx);
3576
3715
  const clientModule = getClientModule(clientType);
3577
3716
  if (!clientModule) {
@@ -3585,6 +3724,7 @@ async function addChannel(config) {
3585
3724
  }
3586
3725
  const agentConfig = await collectModuleConfig(agentModule, ctx);
3587
3726
  config.channels[name] = {
3727
+ common: commonConfig,
3588
3728
  client: {
3589
3729
  type: clientType,
3590
3730
  config: clientConfig
@@ -3663,7 +3803,7 @@ async function startChannel(channelName) {
3663
3803
  }
3664
3804
  async function runCli(argv = process2.argv) {
3665
3805
  const program = new Command();
3666
- 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);
3667
3807
  program.command("add").description("Interactively add a channel").action(async () => {
3668
3808
  const config = await loadConfig();
3669
3809
  await addChannel(config);