@hopgoldy/agent-bridge 0.3.1 → 0.4.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.
@@ -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,115 @@ 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: "\u23F3 {{subject}}",
120
+ finished: "\u2705 {{subject}}",
121
+ failed: "\u274C {{subject}}",
122
+ failedWithDetail: "\u274C {{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- `/status` (`/st`) - Show the current agent session status\n- `/model` (`/m`) - List available models, or switch with `/model provider/modelId`\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
+ statusTitle: "Current session status",
132
+ statusSessionId: "Session ID",
133
+ statusModel: "Model",
134
+ statusThinkingLevel: "Thinking level",
135
+ statusContext: "Context",
136
+ statusUnavailable: "Current session status is unavailable.",
137
+ statusUnavailableValue: "Unavailable",
138
+ modelListTitle: "Available models",
139
+ modelListCurrent: "current",
140
+ modelListUsage: "Use `/model provider/modelId` to switch.",
141
+ modelListUnavailable: "Available models are unavailable for the current session.",
142
+ modelSetUnavailable: "Current session model switching is unavailable.",
143
+ modelInvalid: "The requested model is invalid or unavailable.",
144
+ modelBusy: "Current session is busy, so the model cannot be switched. Please use `/stop` first.",
145
+ modelUpdated: "Switched current model to `{{model}}`."
146
+ },
147
+ gateway: {
148
+ noActiveSessionToCompact: "No active agent session to compact.",
149
+ noActiveSessionToStop: "No active agent session to stop.",
150
+ sessionCannotBeStopped: "This agent session cannot be stopped right now.",
151
+ noActiveRunToStop: "No active agent run to stop.",
152
+ startedNewSession: "Started a new session."
153
+ }
154
+ }
155
+ },
156
+ "zh-CN": {
157
+ translation: {
158
+ progress: {
159
+ noProgress: "\u6682\u65E0\u8FDB\u5EA6\u3002",
160
+ collapsed: "\u5DF2\u6298\u53E0 {{count}} \u6761\u8F83\u65E9\u66F4\u65B0\u3002",
161
+ running: "\u23F3 {{subject}}",
162
+ finished: "\u2705 {{subject}}",
163
+ failed: "\u274C {{subject}}",
164
+ failedWithDetail: "\u274C {{subject}}: {{detail}}",
165
+ compacting: "\u6B63\u5728\u538B\u7F29\u4F1A\u8BDD",
166
+ compactingWithDetail: "\u6B63\u5728\u538B\u7F29\u4F1A\u8BDD: {{detail}}"
167
+ },
168
+ client: {
169
+ processing: "\u6B63\u5728\u5904\u7406\u4E2D...",
170
+ 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- `/status` (`/st`) - \u67E5\u770B\u5F53\u524D\u667A\u80FD\u4F53\u4F1A\u8BDD\u72B6\u6001\n- `/model` (`/m`) - \u67E5\u770B\u53EF\u7528\u6A21\u578B\uFF0C\u6216\u4F7F\u7528 `/model provider/modelId` \u5207\u6362\u6A21\u578B\n- `/help` (`/h`) - \u67E5\u770B\u8FD9\u6761\u5E2E\u52A9\u4FE1\u606F",
171
+ messageDeliveryFailedTitle: "[agent-bridge \u9519\u8BEF] \u6D88\u606F\u53D1\u9001\u5931\u8D25",
172
+ weixinCooldown: "\u5FAE\u4FE1\u53D1\u9001\u56E0\u9650\u6D41\u5DF2\u8FDB\u5165\u51B7\u5374\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
173
+ statusTitle: "\u5F53\u524D\u4F1A\u8BDD\u72B6\u6001",
174
+ statusSessionId: "Session ID",
175
+ statusModel: "\u6A21\u578B",
176
+ statusThinkingLevel: "\u601D\u8003\u7B49\u7EA7",
177
+ statusContext: "\u4E0A\u4E0B\u6587",
178
+ statusUnavailable: "\u5F53\u524D\u65E0\u6CD5\u83B7\u53D6\u4F1A\u8BDD\u72B6\u6001\u3002",
179
+ statusUnavailableValue: "\u6682\u4E0D\u53EF\u7528",
180
+ modelListTitle: "\u53EF\u7528\u6A21\u578B",
181
+ modelListCurrent: "\u5F53\u524D",
182
+ modelListUsage: "\u4F7F\u7528 `/model provider/modelId` \u5207\u6362\u6A21\u578B\u3002",
183
+ modelListUnavailable: "\u5F53\u524D\u4F1A\u8BDD\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\u53EF\u7528\u6A21\u578B\u5217\u8868\u3002",
184
+ modelSetUnavailable: "\u5F53\u524D\u4F1A\u8BDD\u6682\u65F6\u65E0\u6CD5\u5207\u6362\u6A21\u578B\u3002",
185
+ modelInvalid: "\u8BF7\u6C42\u7684\u6A21\u578B\u65E0\u6548\u6216\u5F53\u524D\u4E0D\u53EF\u7528\u3002",
186
+ modelBusy: "\u5F53\u524D\u6B63\u5728\u8FD0\u884C\uFF0C\u65E0\u6CD5\u5207\u6362\u6A21\u578B\u3002\u8BF7\u5148\u4F7F\u7528 `/stop`\u3002",
187
+ modelUpdated: "\u5F53\u524D\u6A21\u578B\u5DF2\u5207\u6362\u81F3 `{{model}}`\u3002"
188
+ },
189
+ gateway: {
190
+ noActiveSessionToCompact: "\u5F53\u524D\u6CA1\u6709\u53EF\u538B\u7F29\u7684\u667A\u80FD\u4F53\u4F1A\u8BDD\u3002",
191
+ noActiveSessionToStop: "\u5F53\u524D\u6CA1\u6709\u53EF\u505C\u6B62\u7684\u667A\u80FD\u4F53\u4F1A\u8BDD\u3002",
192
+ sessionCannotBeStopped: "\u5F53\u524D\u65E0\u6CD5\u505C\u6B62\u8FD9\u4E2A\u667A\u80FD\u4F53\u4F1A\u8BDD\u3002",
193
+ noActiveRunToStop: "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u667A\u80FD\u4F53\u4EFB\u52A1\u53EF\u505C\u6B62\u3002",
194
+ startedNewSession: "\u5DF2\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002"
195
+ }
196
+ }
197
+ }
198
+ };
199
+ var instance = i18next.createInstance();
200
+ void instance.init({
201
+ lng: DEFAULT_LOCALE,
202
+ fallbackLng: DEFAULT_LOCALE,
203
+ resources,
204
+ interpolation: { escapeValue: false },
205
+ initImmediate: false
206
+ });
207
+ function getTranslator(locale) {
208
+ return instance.getFixedT(locale);
209
+ }
210
+ function getTranslatorForCommon(common) {
211
+ return getTranslator(common?.language ?? DEFAULT_LOCALE);
212
+ }
213
+ function formatSendFailureNotice(t, detail) {
214
+ return `${t("client.messageDeliveryFailedTitle")}
215
+
216
+ ${detail}`;
217
+ }
218
+
109
219
  // src/config/defaults.ts
110
220
  var DEFAULTS = {
111
221
  agentIdleTimeoutMs: 10 * 60 * 1e3
@@ -114,11 +224,20 @@ var DEFAULTS = {
114
224
  // src/config/store.ts
115
225
  var CONFIG_DIR = path2.join(os2.homedir(), ".config", "agent-bridge");
116
226
  var CONFIG_PATH = path2.join(CONFIG_DIR, "config.json");
227
+ function normalizeLanguage(language) {
228
+ return language === "en-US" || language === "zh-CN" ? language : DEFAULT_LOCALE;
229
+ }
117
230
  function normalizeChannelConfig(channel) {
118
231
  if (!channel.client || !channel.agent) {
119
232
  throw new Error("Invalid channel config shape");
120
233
  }
121
- return channel;
234
+ return {
235
+ common: {
236
+ language: normalizeLanguage(channel.common?.language)
237
+ },
238
+ client: channel.client,
239
+ agent: channel.agent
240
+ };
122
241
  }
123
242
  function mergeDefaults(config = {}) {
124
243
  const channels = Object.fromEntries(
@@ -193,16 +312,20 @@ var GatewayCore = class {
193
312
  #agentConfig;
194
313
  #agentIdleTimeoutMs;
195
314
  #bindingStore;
315
+ #common;
316
+ #t;
196
317
  #logger = createLogger("core");
197
318
  #clientToAgentSession = /* @__PURE__ */ new Map();
198
319
  #agentRuntimes = /* @__PURE__ */ new Map();
199
320
  #started = false;
200
- constructor({ imAdapter, agentModule, agentConfig, agentIdleTimeoutMs, bindingStore }) {
321
+ constructor({ imAdapter, agentModule, agentConfig, agentIdleTimeoutMs, bindingStore, common }) {
201
322
  this.#imAdapter = imAdapter;
202
323
  this.#agentModule = agentModule;
203
324
  this.#agentConfig = agentConfig;
204
325
  this.#agentIdleTimeoutMs = agentIdleTimeoutMs;
205
326
  this.#bindingStore = bindingStore;
327
+ this.#common = common;
328
+ this.#t = getTranslatorForCommon(common);
206
329
  }
207
330
  async start() {
208
331
  if (this.#started) return;
@@ -242,6 +365,18 @@ var GatewayCore = class {
242
365
  await this.#handleSessionStop(event.clientSessionId);
243
366
  return;
244
367
  }
368
+ if (event.type === "command.session.status") {
369
+ await this.#handleSessionStatus(event.clientSessionId);
370
+ return;
371
+ }
372
+ if (event.type === "command.session.model.list") {
373
+ await this.#handleSessionModelList(event.clientSessionId);
374
+ return;
375
+ }
376
+ if (event.type === "command.session.model.set") {
377
+ await this.#handleSessionModelSet(event.clientSessionId, event.target);
378
+ return;
379
+ }
245
380
  await this.#handleUserMessage(event.clientSessionId, event.text);
246
381
  }
247
382
  async #handleUserMessage(clientSessionId, text2) {
@@ -258,7 +393,7 @@ var GatewayCore = class {
258
393
  await this.#deliverClientInput({
259
394
  type: "assistant.message",
260
395
  clientSessionId,
261
- text: "No active agent session to compact."
396
+ text: this.#t("gateway.noActiveSessionToCompact")
262
397
  });
263
398
  return;
264
399
  }
@@ -273,7 +408,7 @@ var GatewayCore = class {
273
408
  await this.#deliverClientInput({
274
409
  type: "assistant.message",
275
410
  clientSessionId,
276
- text: "No active agent session to stop."
411
+ text: this.#t("gateway.noActiveSessionToStop")
277
412
  });
278
413
  return;
279
414
  }
@@ -282,7 +417,7 @@ var GatewayCore = class {
282
417
  await this.#deliverClientInput({
283
418
  type: "assistant.message",
284
419
  clientSessionId,
285
- text: "This agent session cannot be stopped right now."
420
+ text: this.#t("gateway.sessionCannotBeStopped")
286
421
  });
287
422
  return;
288
423
  }
@@ -290,12 +425,129 @@ var GatewayCore = class {
290
425
  await this.#deliverClientInput({
291
426
  type: "assistant.message",
292
427
  clientSessionId,
293
- text: "No active agent run to stop."
428
+ text: this.#t("gateway.noActiveRunToStop")
294
429
  });
295
430
  return;
296
431
  }
297
432
  await runtime.agentAdapter.abort();
298
433
  }
434
+ async #handleSessionStatus(clientSessionId) {
435
+ const runtime = await this.#getActiveRuntime(clientSessionId);
436
+ if (!runtime) {
437
+ await this.#deliverClientInput({
438
+ type: "error",
439
+ clientSessionId,
440
+ kind: "agent.status.unavailable"
441
+ });
442
+ return;
443
+ }
444
+ this.#touchRuntime(runtime);
445
+ if (!runtime.agentAdapter.getStatus) {
446
+ await this.#deliverClientInput({
447
+ type: "error",
448
+ clientSessionId,
449
+ kind: "agent.status.unavailable"
450
+ });
451
+ return;
452
+ }
453
+ try {
454
+ const status = await runtime.agentAdapter.getStatus();
455
+ await this.#deliverClientInput({
456
+ type: "agent.status.info",
457
+ clientSessionId,
458
+ status
459
+ });
460
+ } catch (error) {
461
+ const detail = error instanceof Error ? error.message : String(error);
462
+ await this.#deliverClientInput({
463
+ type: "error",
464
+ clientSessionId,
465
+ kind: "agent.status.unavailable",
466
+ detail
467
+ });
468
+ }
469
+ }
470
+ async #handleSessionModelList(clientSessionId) {
471
+ const runtime = await this.#getActiveRuntime(clientSessionId);
472
+ if (!runtime) {
473
+ await this.#deliverClientInput({
474
+ type: "error",
475
+ clientSessionId,
476
+ kind: "agent.model.list.unavailable"
477
+ });
478
+ return;
479
+ }
480
+ this.#touchRuntime(runtime);
481
+ if (!runtime.agentAdapter.getAvailableModels) {
482
+ await this.#deliverClientInput({
483
+ type: "error",
484
+ clientSessionId,
485
+ kind: "agent.model.list.unavailable"
486
+ });
487
+ return;
488
+ }
489
+ try {
490
+ const models = await runtime.agentAdapter.getAvailableModels();
491
+ await this.#deliverClientInput({
492
+ type: "agent.model.list",
493
+ clientSessionId,
494
+ models
495
+ });
496
+ } catch (error) {
497
+ const detail = error instanceof Error ? error.message : String(error);
498
+ await this.#deliverClientInput({
499
+ type: "error",
500
+ clientSessionId,
501
+ kind: "agent.model.list.unavailable",
502
+ detail
503
+ });
504
+ }
505
+ }
506
+ async #handleSessionModelSet(clientSessionId, target) {
507
+ const runtime = await this.#getActiveRuntime(clientSessionId);
508
+ if (!runtime) {
509
+ await this.#deliverClientInput({
510
+ type: "error",
511
+ clientSessionId,
512
+ kind: "agent.model.set.unavailable"
513
+ });
514
+ return;
515
+ }
516
+ this.#touchRuntime(runtime);
517
+ if (!runtime.agentAdapter.setModel) {
518
+ await this.#deliverClientInput({
519
+ type: "error",
520
+ clientSessionId,
521
+ kind: "agent.model.set.unavailable"
522
+ });
523
+ return;
524
+ }
525
+ if (await runtime.agentAdapter.isBusy()) {
526
+ await this.#deliverClientInput({
527
+ type: "error",
528
+ clientSessionId,
529
+ kind: "agent.model.busy"
530
+ });
531
+ return;
532
+ }
533
+ try {
534
+ const result = await runtime.agentAdapter.setModel(target);
535
+ await this.#deliverClientInput({
536
+ type: "agent.model.updated",
537
+ clientSessionId,
538
+ provider: result.provider,
539
+ modelId: result.modelId
540
+ });
541
+ } catch (error) {
542
+ const { kind, detail } = this.#resolveModelCommandError(error);
543
+ await this.#deliverClientInput({
544
+ type: "error",
545
+ clientSessionId,
546
+ kind,
547
+ ...detail ? { detail } : {}
548
+ });
549
+ }
550
+ }
299
551
  async #handleSessionNew(clientSessionId) {
300
552
  const previousAgentSessionId = this.#clientToAgentSession.get(clientSessionId);
301
553
  if (previousAgentSessionId) {
@@ -309,7 +561,7 @@ var GatewayCore = class {
309
561
  await this.#deliverClientInput({
310
562
  type: "assistant.message",
311
563
  clientSessionId,
312
- text: "Started a new session."
564
+ text: this.#t("gateway.startedNewSession")
313
565
  });
314
566
  }
315
567
  async #deliverClientInput(event) {
@@ -344,6 +596,7 @@ var GatewayCore = class {
344
596
  if (this.#agentModule.resumeAgentSession) {
345
597
  const agentAdapter = await this.#agentModule.resumeAgentSession({
346
598
  config: this.#agentConfig,
599
+ common: this.#common ?? { channelName: "", language: "en-US" },
347
600
  agentSessionId
348
601
  });
349
602
  return this.#startRuntime(clientSessionId, agentSessionId, agentAdapter);
@@ -354,7 +607,8 @@ var GatewayCore = class {
354
607
  }
355
608
  async #createRuntimeForClient(clientSessionId) {
356
609
  const { agentSessionId, agentAdapter } = await this.#agentModule.createAgentSession({
357
- config: this.#agentConfig
610
+ config: this.#agentConfig,
611
+ common: this.#common ?? { channelName: "", language: "en-US" }
358
612
  });
359
613
  return this.#startRuntime(clientSessionId, agentSessionId, agentAdapter);
360
614
  }
@@ -387,6 +641,18 @@ var GatewayCore = class {
387
641
  this.#logger.error("failed to persist session bindings:", error);
388
642
  }
389
643
  }
644
+ #resolveModelCommandError(error) {
645
+ const detail = error instanceof Error ? error.message : String(error);
646
+ if (typeof error === "object" && error && "kind" in error && typeof error.kind === "string") {
647
+ switch (error.kind) {
648
+ case "agent.model.invalid":
649
+ case "agent.model.busy":
650
+ case "agent.model.set.unavailable":
651
+ return { kind: error.kind, ...detail ? { detail } : {} };
652
+ }
653
+ }
654
+ return { kind: "agent.model.set.unavailable", ...detail ? { detail } : {} };
655
+ }
390
656
  async #handleAgentOutput(event) {
391
657
  const agentSessionId = event.agentSessionId;
392
658
  const runtime = this.#agentRuntimes.get(agentSessionId);
@@ -789,6 +1055,19 @@ var PiRpcClient = class {
789
1055
  const response = await this.#send({ type: "get_state" });
790
1056
  return response.data ?? {};
791
1057
  }
1058
+ async getSessionStats() {
1059
+ const response = await this.#send({ type: "get_session_stats" });
1060
+ return response.data ?? {};
1061
+ }
1062
+ async getAvailableModels() {
1063
+ const response = await this.#send({ type: "get_available_models" });
1064
+ const data = response.data;
1065
+ return data?.models ?? [];
1066
+ }
1067
+ async setModel(provider, modelId) {
1068
+ const response = await this.#send({ type: "set_model", provider, modelId });
1069
+ return response.data ?? {};
1070
+ }
792
1071
  async setSessionName(name) {
793
1072
  await this.#send({ type: "set_session_name", name });
794
1073
  }
@@ -895,6 +1174,13 @@ function toPiSessionId(agentSessionId) {
895
1174
  }
896
1175
 
897
1176
  // src/modules/agent/pi-coding-agent/adapter/pi-coding-agent-adapter.ts
1177
+ var PiModelCommandError = class extends Error {
1178
+ kind;
1179
+ constructor(kind, message) {
1180
+ super(message);
1181
+ this.kind = kind;
1182
+ }
1183
+ };
898
1184
  var PiCodingAgentAdapter = class {
899
1185
  #agentSessionId;
900
1186
  #piSessionId;
@@ -977,6 +1263,71 @@ var PiCodingAgentAdapter = class {
977
1263
  async isBusy() {
978
1264
  return this.#processing || this.#inputQueue.length > 0;
979
1265
  }
1266
+ async getStatus() {
1267
+ if (!this.#client) {
1268
+ throw new Error("PiCodingAgentAdapter is not started");
1269
+ }
1270
+ const [state, stats] = await Promise.all([this.#client.getState(), this.#client.getSessionStats()]);
1271
+ const contextUsage = stats.contextUsage;
1272
+ return {
1273
+ sessionId: state.sessionId ?? this.#agentSessionId,
1274
+ provider: state.model?.provider,
1275
+ modelId: state.model?.id,
1276
+ thinkingLevel: state.thinkingLevel,
1277
+ context: contextUsage ? {
1278
+ tokens: contextUsage.tokens ?? null,
1279
+ contextWindow: contextUsage.contextWindow ?? null,
1280
+ percent: contextUsage.percent ?? null
1281
+ } : void 0
1282
+ };
1283
+ }
1284
+ async getAvailableModels() {
1285
+ if (!this.#client) {
1286
+ throw new Error("PiCodingAgentAdapter is not started");
1287
+ }
1288
+ const [state, models] = await Promise.all([this.#client.getState(), this.#client.getAvailableModels()]);
1289
+ return models.filter((model) => typeof model.provider === "string" && typeof model.id === "string").map((model) => ({
1290
+ provider: model.provider,
1291
+ modelId: model.id,
1292
+ isCurrent: state.model?.provider === model.provider && state.model?.id === model.id
1293
+ }));
1294
+ }
1295
+ async setModel(target) {
1296
+ if (!this.#client) {
1297
+ throw new PiModelCommandError("agent.model.set.unavailable", "PiCodingAgentAdapter is not started");
1298
+ }
1299
+ const parsed = this.#parseModelTarget(target);
1300
+ const state = await this.#client.getState();
1301
+ if (state.isStreaming || state.isCompacting) {
1302
+ throw new PiModelCommandError("agent.model.busy", "Current session is busy, so the model cannot be switched. Please use /stop first.");
1303
+ }
1304
+ try {
1305
+ const result = await this.#client.setModel(parsed.provider, parsed.modelId);
1306
+ return {
1307
+ provider: result.provider ?? parsed.provider,
1308
+ modelId: result.id ?? parsed.modelId
1309
+ };
1310
+ } catch (error) {
1311
+ const message = error instanceof Error ? error.message : String(error);
1312
+ if (/Model not found:/i.test(message)) {
1313
+ throw new PiModelCommandError("agent.model.invalid", message);
1314
+ }
1315
+ throw new PiModelCommandError("agent.model.set.unavailable", message);
1316
+ }
1317
+ }
1318
+ #parseModelTarget(target) {
1319
+ const trimmed = target.trim();
1320
+ const slashIndex = trimmed.indexOf("/");
1321
+ if (slashIndex <= 0 || slashIndex === trimmed.length - 1) {
1322
+ throw new PiModelCommandError("agent.model.invalid", `Invalid model target: ${target}`);
1323
+ }
1324
+ const provider = trimmed.slice(0, slashIndex).trim();
1325
+ const modelId = trimmed.slice(slashIndex + 1).trim();
1326
+ if (!provider || !modelId) {
1327
+ throw new PiModelCommandError("agent.model.invalid", `Invalid model target: ${target}`);
1328
+ }
1329
+ return { provider, modelId };
1330
+ }
980
1331
  async #drainInputQueue() {
981
1332
  if (this.#processing) {
982
1333
  return;
@@ -1088,7 +1439,7 @@ var PiCodingAgentAdapter = class {
1088
1439
  toolCallId,
1089
1440
  toolInput,
1090
1441
  toolLabel,
1091
- text: `Running ${toolName}`
1442
+ text: void 0
1092
1443
  });
1093
1444
  return;
1094
1445
  }
@@ -1109,7 +1460,7 @@ var PiCodingAgentAdapter = class {
1109
1460
  toolInput,
1110
1461
  toolLabel,
1111
1462
  partialResult: "partialResult" in rpcEvent ? rpcEvent.partialResult : void 0,
1112
- text: `Running ${toolName}`
1463
+ text: void 0
1113
1464
  });
1114
1465
  return;
1115
1466
  }
@@ -1127,7 +1478,7 @@ var PiCodingAgentAdapter = class {
1127
1478
  toolInput,
1128
1479
  toolLabel,
1129
1480
  result: "result" in rpcEvent ? rpcEvent.result : void 0,
1130
- text: isError ? `Failed ${toolName}` : `Finished ${toolName}`
1481
+ text: void 0
1131
1482
  });
1132
1483
  if (toolCallId) {
1133
1484
  this.#toolLabelByCallId.delete(toolCallId);
@@ -1292,16 +1643,16 @@ function createPiCodingAgentConfigCollector() {
1292
1643
  var piCodingAgentModule = {
1293
1644
  type: "pi-coding-agent",
1294
1645
  createConfigCollector: createPiCodingAgentConfigCollector,
1295
- async createAgentSession({ config }) {
1646
+ async createAgentSession({ config, common }) {
1296
1647
  const agentSessionId = `pi-coding-agent:${randomUUID()}`;
1297
- logger.info(`creating agent session ${agentSessionId}`);
1648
+ logger.info(`creating agent session ${agentSessionId} for channel ${common.channelName}`);
1298
1649
  return {
1299
1650
  agentSessionId,
1300
1651
  agentAdapter: buildAdapter(config, agentSessionId)
1301
1652
  };
1302
1653
  },
1303
- async resumeAgentSession({ config, agentSessionId }) {
1304
- logger.info(`resuming agent session ${agentSessionId}`);
1654
+ async resumeAgentSession({ config, common, agentSessionId }) {
1655
+ logger.info(`resuming agent session ${agentSessionId} for channel ${common.channelName}`);
1305
1656
  return buildAdapter(config, agentSessionId);
1306
1657
  }
1307
1658
  };
@@ -1324,15 +1675,16 @@ function getTypedAgentModule(config) {
1324
1675
 
1325
1676
  // src/modules/client/utils/progress-renderer.ts
1326
1677
  var DEFAULT_COLLAPSE_THRESHOLD = 10;
1327
- var MAX_TOOL_LABEL_DISPLAY_LENGTH = 15;
1328
- var NO_PROGRESS_MARKDOWN = "No progress yet.";
1678
+ var MAX_TOOL_LABEL_DISPLAY_LENGTH = 24;
1329
1679
  var ProgressRenderer = class {
1330
1680
  #collapseThreshold;
1681
+ #t;
1331
1682
  #entries = /* @__PURE__ */ new Map();
1332
1683
  #order = [];
1333
1684
  #status = "running";
1334
1685
  constructor(options = {}) {
1335
1686
  this.#collapseThreshold = options.collapseThreshold ?? DEFAULT_COLLAPSE_THRESHOLD;
1687
+ this.#t = options.t ?? getTranslator("en-US");
1336
1688
  }
1337
1689
  /** Whether `event` should be recorded as progress (excludes assistant messages and thinking). */
1338
1690
  isProgressEvent(event) {
@@ -1354,20 +1706,21 @@ var ProgressRenderer = class {
1354
1706
  return {
1355
1707
  markdown: this.#renderMarkdown(collapsedCount),
1356
1708
  status: this.#status,
1357
- collapsedCount
1709
+ collapsedCount,
1710
+ isEmpty: this.#order.length === 0
1358
1711
  };
1359
1712
  }
1360
1713
  #renderMarkdown(collapsedCount) {
1361
1714
  const contentLines = [];
1362
1715
  if (collapsedCount > 0) {
1363
- contentLines.push(`- Collapsed ${collapsedCount} earlier updates.`);
1716
+ contentLines.push(`- ${this.#t("progress.collapsed", { count: collapsedCount })}`);
1364
1717
  }
1365
1718
  for (const id of this.#visibleOrder()) {
1366
1719
  const entry = this.#entries.get(id);
1367
1720
  if (!entry) continue;
1368
1721
  contentLines.push(entry.kind === "line" ? entry.line : this.#formatToolEntry(entry));
1369
1722
  }
1370
- return contentLines.length > 0 ? contentLines.join("\n") : NO_PROGRESS_MARKDOWN;
1723
+ return contentLines.length > 0 ? contentLines.join("\n") : this.#t("progress.noProgress");
1371
1724
  }
1372
1725
  #visibleOrder() {
1373
1726
  return this.#order.slice(-this.#collapseThreshold);
@@ -1399,9 +1752,9 @@ var ProgressRenderer = class {
1399
1752
  const subject = this.#formatToolSubject(entry.toolName, entry.toolLabel);
1400
1753
  switch (entry.status) {
1401
1754
  case "running":
1402
- return `- Running ${subject}`;
1755
+ return `- ${this.#t("progress.running", { subject })}`;
1403
1756
  case "done":
1404
- return `- Finished ${subject}`;
1757
+ return `- ${this.#t("progress.finished", { subject })}`;
1405
1758
  case "error":
1406
1759
  return this.#formatToolErrorLine(subject, entry.text);
1407
1760
  }
@@ -1409,12 +1762,12 @@ var ProgressRenderer = class {
1409
1762
  #formatProgressLine(event) {
1410
1763
  switch (event.type) {
1411
1764
  case "session.compacting":
1412
- return `- Compacting session${event.text ? `: ${event.text}` : ""}`;
1765
+ return `- ${event.text ? this.#t("progress.compactingWithDetail", { detail: event.text }) : this.#t("progress.compacting")}`;
1413
1766
  case "assistant.tool.running":
1414
1767
  case "assistant.tool.update":
1415
- return `- Running ${this.#formatToolSubject(event.toolName, event.toolLabel)}`;
1768
+ return `- ${this.#t("progress.running", { subject: this.#formatToolSubject(event.toolName, event.toolLabel) })}`;
1416
1769
  case "assistant.tool.done":
1417
- return `- Finished ${this.#formatToolSubject(event.toolName, event.toolLabel)}`;
1770
+ return `- ${this.#t("progress.finished", { subject: this.#formatToolSubject(event.toolName, event.toolLabel) })}`;
1418
1771
  case "assistant.tool.error":
1419
1772
  return this.#formatToolErrorLine(this.#formatToolSubject(event.toolName, event.toolLabel), event.text);
1420
1773
  }
@@ -1434,15 +1787,10 @@ var ProgressRenderer = class {
1434
1787
  if (!normalizedText) {
1435
1788
  return `- ${this.#humanizeToolError(toolName)}`;
1436
1789
  }
1437
- const lowerText = normalizedText.toLowerCase();
1438
- const lowerToolName = toolName.toLowerCase();
1439
- if (lowerText === lowerToolName || lowerText === `failed ${lowerToolName}` || lowerText === `running ${lowerToolName}` || lowerText === `finished ${lowerToolName}`) {
1440
- return `- ${this.#humanizeToolError(toolName)}`;
1441
- }
1442
- return `- ${this.#humanizeToolError(toolName)}: ${normalizedText}`;
1790
+ return `- ${this.#t("progress.failedWithDetail", { subject: toolName, detail: normalizedText })}`;
1443
1791
  }
1444
1792
  #humanizeToolError(toolName) {
1445
- return `Failed ${toolName}`;
1793
+ return this.#t("progress.failed", { subject: toolName });
1446
1794
  }
1447
1795
  #progressStatus(event) {
1448
1796
  switch (event.type) {
@@ -1457,7 +1805,38 @@ var ProgressRenderer = class {
1457
1805
  };
1458
1806
 
1459
1807
  // src/modules/client/utils/slash-commands.ts
1808
+ function isHelpCommand(text2) {
1809
+ switch (text2.toLowerCase()) {
1810
+ case "/help":
1811
+ case "/h":
1812
+ return true;
1813
+ default:
1814
+ return false;
1815
+ }
1816
+ }
1817
+ function resolveHelpMarkdown(text2, t) {
1818
+ return isHelpCommand(text2) ? t("client.helpMessage") : null;
1819
+ }
1820
+ function parseModelCommand(text2, clientSessionId) {
1821
+ const match = text2.match(/^\/(model|m)(?:\s+(.*))?$/i);
1822
+ if (!match) {
1823
+ return null;
1824
+ }
1825
+ const target = match[2]?.trim();
1826
+ if (!target) {
1827
+ return { type: "command.session.model.list", clientSessionId };
1828
+ }
1829
+ return {
1830
+ type: "command.session.model.set",
1831
+ clientSessionId,
1832
+ target
1833
+ };
1834
+ }
1460
1835
  function parseSlashCommand(text2, clientSessionId) {
1836
+ const modelCommand = parseModelCommand(text2, clientSessionId);
1837
+ if (modelCommand) {
1838
+ return modelCommand;
1839
+ }
1461
1840
  switch (text2.toLowerCase()) {
1462
1841
  case "/new":
1463
1842
  case "/n":
@@ -1466,12 +1845,89 @@ function parseSlashCommand(text2, clientSessionId) {
1466
1845
  case "/c":
1467
1846
  return { type: "command.session.compact", clientSessionId };
1468
1847
  case "/stop":
1848
+ case "/s":
1469
1849
  return { type: "command.session.stop", clientSessionId };
1850
+ case "/status":
1851
+ case "/st":
1852
+ return { type: "command.session.status", clientSessionId };
1470
1853
  default:
1471
1854
  return null;
1472
1855
  }
1473
1856
  }
1474
1857
 
1858
+ // src/modules/client/utils/status-markdown.ts
1859
+ function formatModel(status, t) {
1860
+ if (status.provider && status.modelId) {
1861
+ return `\`${status.provider}/${status.modelId}\``;
1862
+ }
1863
+ if (status.modelId) {
1864
+ return `\`${status.modelId}\``;
1865
+ }
1866
+ if (status.provider) {
1867
+ return `\`${status.provider}\``;
1868
+ }
1869
+ return t("client.statusUnavailableValue");
1870
+ }
1871
+ function formatContext(status, t) {
1872
+ const context = status.context;
1873
+ if (!context) {
1874
+ return t("client.statusUnavailableValue");
1875
+ }
1876
+ const { tokens, contextWindow, percent } = context;
1877
+ if (tokens == null || contextWindow == null || percent == null) {
1878
+ return t("client.statusUnavailableValue");
1879
+ }
1880
+ return `\`${tokens.toLocaleString()} / ${contextWindow.toLocaleString()} (${percent}%)\``;
1881
+ }
1882
+ function formatAvailableModel(model, t) {
1883
+ const label = `\`${model.provider}/${model.modelId}\``;
1884
+ return model.isCurrent ? `- ${label} \u2705 ${t("client.modelListCurrent")}` : `- ${label}`;
1885
+ }
1886
+ function formatErrorMarkdown(title, detail) {
1887
+ return [`**${title}**`, ...detail ? ["", detail] : []].join("\n");
1888
+ }
1889
+ function renderStatusMarkdown(event, t) {
1890
+ if (event.type === "agent.model.list") {
1891
+ return [
1892
+ `**${t("client.modelListTitle")}**`,
1893
+ "",
1894
+ ...event.models.length > 0 ? event.models.map((model) => formatAvailableModel(model, t)) : [`- ${t("client.statusUnavailableValue")}`],
1895
+ "",
1896
+ t("client.modelListUsage")
1897
+ ].join("\n");
1898
+ }
1899
+ if (event.type === "agent.model.updated") {
1900
+ return t("client.modelUpdated", { model: `${event.provider}/${event.modelId}` });
1901
+ }
1902
+ if (event.type === "agent.status.info") {
1903
+ return [
1904
+ `**${t("client.statusTitle")}**`,
1905
+ "",
1906
+ `- ${t("client.statusSessionId")}: \`${event.status.sessionId}\``,
1907
+ `- ${t("client.statusModel")}: ${formatModel(event.status, t)}`,
1908
+ `- ${t("client.statusThinkingLevel")}: ${event.status.thinkingLevel ? `\`${event.status.thinkingLevel}\`` : t("client.statusUnavailableValue")}`,
1909
+ `- ${t("client.statusContext")}: ${formatContext(event.status, t)}`
1910
+ ].join("\n");
1911
+ }
1912
+ if (event.type === "error") {
1913
+ switch (event.kind) {
1914
+ case "agent.status.unavailable":
1915
+ return formatErrorMarkdown(t("client.statusUnavailable"), event.detail);
1916
+ case "agent.model.list.unavailable":
1917
+ return formatErrorMarkdown(t("client.modelListUnavailable"), event.detail);
1918
+ case "agent.model.set.unavailable":
1919
+ return formatErrorMarkdown(t("client.modelSetUnavailable"), event.detail);
1920
+ case "agent.model.invalid":
1921
+ return formatErrorMarkdown(t("client.modelInvalid"), event.detail);
1922
+ case "agent.model.busy":
1923
+ return event.detail ?? t("client.modelBusy");
1924
+ default:
1925
+ return null;
1926
+ }
1927
+ }
1928
+ return null;
1929
+ }
1930
+
1475
1931
  // src/modules/client/feishu/adapter/feishu-client.ts
1476
1932
  import * as Lark from "@larksuiteoapi/node-sdk";
1477
1933
  import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
@@ -1818,6 +2274,7 @@ function chunkText(text2, maxLen) {
1818
2274
  var FeishuIMAdapter = class _FeishuIMAdapter {
1819
2275
  #config;
1820
2276
  #logger;
2277
+ #t;
1821
2278
  #onOutput = null;
1822
2279
  #client = null;
1823
2280
  #egressQueue = [];
@@ -1842,18 +2299,17 @@ var FeishuIMAdapter = class _FeishuIMAdapter {
1842
2299
  return;
1843
2300
  }
1844
2301
  const message = error instanceof Error ? error.message : String(error);
1845
- const text2 = `[agent-bridge error] Message delivery failed
1846
-
1847
- ${message}`;
2302
+ const text2 = formatSendFailureNotice(this.#t, message);
1848
2303
  try {
1849
2304
  await this.#client.sendText(chatId, text2);
1850
2305
  } catch (notifyError) {
1851
2306
  this.#logger.error("failed to notify send failure:", notifyError);
1852
2307
  }
1853
2308
  }
1854
- constructor(config, logger3 = createLogger("feishu")) {
2309
+ constructor(config, logger3 = createLogger("feishu"), common) {
1855
2310
  this.#config = config;
1856
2311
  this.#logger = logger3;
2312
+ this.#t = getTranslatorForCommon(common);
1857
2313
  }
1858
2314
  async start(onOutput) {
1859
2315
  this.#onOutput = onOutput;
@@ -1870,10 +2326,17 @@ ${message}`;
1870
2326
  );
1871
2327
  return;
1872
2328
  }
2329
+ const normalizedText = text2.trim();
1873
2330
  this.#lastInboundMessageIdBySession.set(clientSessionId, messageId);
1874
2331
  this.#resetProgressState(clientSessionId);
1875
2332
  await this.#client?.startTyping(chatId, messageId);
1876
- const normalizedText = text2.trim();
2333
+ const helpMarkdown = resolveHelpMarkdown(normalizedText, this.#t);
2334
+ if (helpMarkdown) {
2335
+ this.#logger.info(`received local help command ${normalizedText} (session=${clientSessionId})`);
2336
+ await this.#client?.sendText(chatId, helpMarkdown, messageId);
2337
+ await this.#client?.stopTyping(chatId);
2338
+ return;
2339
+ }
1877
2340
  const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
1878
2341
  if (commandEvent) {
1879
2342
  this.#logger.info(`received command ${normalizedText} (session=${clientSessionId})`);
@@ -1925,6 +2388,13 @@ ${message}`;
1925
2388
  try {
1926
2389
  const target = parseFeishuSessionId(event.clientSessionId);
1927
2390
  if (event.type !== "assistant.message") {
2391
+ const statusMarkdown = renderStatusMarkdown(event, this.#t);
2392
+ if (statusMarkdown) {
2393
+ const replyToMessageId2 = this.#lastInboundMessageIdBySession.get(event.clientSessionId);
2394
+ await this.#client.sendText(target.chatId, statusMarkdown, replyToMessageId2);
2395
+ await this.#client.stopTyping(target.chatId);
2396
+ continue;
2397
+ }
1928
2398
  await this.#handleProgressEvent(target.chatId, event);
1929
2399
  continue;
1930
2400
  }
@@ -1966,7 +2436,7 @@ ${message}`;
1966
2436
  return;
1967
2437
  }
1968
2438
  const state = this.#progressStateBySession.get(event.clientSessionId) ?? {
1969
- renderer: new ProgressRenderer(),
2439
+ renderer: new ProgressRenderer({ t: this.#t }),
1970
2440
  messageId: null,
1971
2441
  creating: false
1972
2442
  };
@@ -1996,7 +2466,7 @@ ${message}`;
1996
2466
  }
1997
2467
  #resetProgressState(clientSessionId) {
1998
2468
  this.#progressStateBySession.set(clientSessionId, {
1999
- renderer: new ProgressRenderer(),
2469
+ renderer: new ProgressRenderer({ t: this.#t }),
2000
2470
  messageId: null,
2001
2471
  creating: false
2002
2472
  });
@@ -2048,8 +2518,8 @@ function createFeishuConfigCollector() {
2048
2518
  var feishuClientModule = {
2049
2519
  type: "feishu",
2050
2520
  createConfigCollector: createFeishuConfigCollector,
2051
- createClientAdapter(config) {
2052
- return new FeishuIMAdapter(config);
2521
+ createClientAdapter({ config, common }) {
2522
+ return new FeishuIMAdapter(config, void 0, common);
2053
2523
  }
2054
2524
  };
2055
2525
 
@@ -2519,7 +2989,6 @@ function parseWecomSessionId(clientSessionId) {
2519
2989
 
2520
2990
  // src/modules/client/wecom/adapter/wecom-im-adapter.ts
2521
2991
  var MAX_TEXT_CHUNK2 = 4e3;
2522
- var STARTING_MESSAGE = "Processing...";
2523
2992
  function chunkText2(text2, maxLen) {
2524
2993
  if (text2.length <= maxLen) return [text2];
2525
2994
  const chunks = [];
@@ -2546,6 +3015,7 @@ function chunkText2(text2, maxLen) {
2546
3015
  var WecomIMAdapter = class {
2547
3016
  #config;
2548
3017
  #logger;
3018
+ #t;
2549
3019
  #onOutput = null;
2550
3020
  #client = null;
2551
3021
  #egressQueue = [];
@@ -2561,18 +3031,17 @@ var WecomIMAdapter = class {
2561
3031
  return;
2562
3032
  }
2563
3033
  const message = error instanceof Error ? error.message : String(error);
2564
- const text2 = `[agent-bridge error] Message delivery failed
2565
-
2566
- ${message}`;
3034
+ const text2 = formatSendFailureNotice(this.#t, message);
2567
3035
  try {
2568
3036
  await this.#client.sendText(chatId, text2);
2569
3037
  } catch (notifyError) {
2570
3038
  this.#logger.error("failed to notify send failure:", notifyError);
2571
3039
  }
2572
3040
  }
2573
- constructor(config, logger3 = createLogger("wecom")) {
3041
+ constructor(config, logger3 = createLogger("wecom"), common) {
2574
3042
  this.#config = config;
2575
3043
  this.#logger = logger3;
3044
+ this.#t = getTranslatorForCommon(common);
2576
3045
  }
2577
3046
  async start(onOutput) {
2578
3047
  this.#onOutput = onOutput;
@@ -2598,14 +3067,19 @@ ${message}`;
2598
3067
  return;
2599
3068
  }
2600
3069
  const normalizedText = text2.trim();
2601
- const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
2602
- if (commandEvent) {
2603
- await this.#onOutput(commandEvent);
3070
+ const helpMarkdown = resolveHelpMarkdown(normalizedText, this.#t);
3071
+ if (helpMarkdown) {
3072
+ await this.#client?.sendText(chatId, helpMarkdown, messageId);
2604
3073
  return;
2605
3074
  }
2606
3075
  this.#lastInboundMessageIdBySession.set(clientSessionId, messageId);
2607
3076
  this.#resetProgressState(clientSessionId);
2608
3077
  await this.#announceStart(chatId, messageId, clientSessionId);
3078
+ const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
3079
+ if (commandEvent) {
3080
+ await this.#onOutput(commandEvent);
3081
+ return;
3082
+ }
2609
3083
  await this.#onOutput({
2610
3084
  type: "user.message",
2611
3085
  clientSessionId,
@@ -2650,6 +3124,13 @@ ${message}`;
2650
3124
  try {
2651
3125
  const target = parseWecomSessionId(event.clientSessionId);
2652
3126
  if (event.type !== "assistant.message") {
3127
+ const statusMarkdown = renderStatusMarkdown(event, this.#t);
3128
+ if (statusMarkdown) {
3129
+ const replyToMessageId2 = this.#lastInboundMessageIdBySession.get(event.clientSessionId);
3130
+ await this.#finishProgressMessage(target.chatId, event.clientSessionId, replyToMessageId2);
3131
+ await this.#client.sendText(target.chatId, statusMarkdown, replyToMessageId2);
3132
+ continue;
3133
+ }
2653
3134
  await this.#handleProgressEvent(target.chatId, event);
2654
3135
  continue;
2655
3136
  }
@@ -2688,7 +3169,7 @@ ${message}`;
2688
3169
  if (!state || state.announced || !this.#client) {
2689
3170
  return;
2690
3171
  }
2691
- await this.#client.sendStreamText(chatId, STARTING_MESSAGE, {
3172
+ await this.#client.sendStreamText(chatId, this.#t("client.processing"), {
2692
3173
  replyToMessageId: messageId,
2693
3174
  finish: false
2694
3175
  });
@@ -2699,7 +3180,7 @@ ${message}`;
2699
3180
  return;
2700
3181
  }
2701
3182
  const state = this.#progressStateBySession.get(event.clientSessionId) ?? {
2702
- renderer: new ProgressRenderer(),
3183
+ renderer: new ProgressRenderer({ t: this.#t }),
2703
3184
  announced: false
2704
3185
  };
2705
3186
  this.#progressStateBySession.set(event.clientSessionId, state);
@@ -2722,7 +3203,7 @@ ${message}`;
2722
3203
  }
2723
3204
  this.#progressStateBySession.delete(clientSessionId);
2724
3205
  const progress = state.renderer.getCurrentProgress();
2725
- const body = progress.markdown === NO_PROGRESS_MARKDOWN ? STARTING_MESSAGE : progress.markdown;
3206
+ const body = progress.isEmpty ? this.#t("client.processing") : progress.markdown;
2726
3207
  try {
2727
3208
  await this.#client.sendStreamText(chatId, body, { replyToMessageId, finish: true });
2728
3209
  } catch (error) {
@@ -2731,7 +3212,7 @@ ${message}`;
2731
3212
  }
2732
3213
  #resetProgressState(clientSessionId) {
2733
3214
  this.#progressStateBySession.set(clientSessionId, {
2734
- renderer: new ProgressRenderer(),
3215
+ renderer: new ProgressRenderer({ t: this.#t }),
2735
3216
  announced: false
2736
3217
  });
2737
3218
  }
@@ -2778,8 +3259,8 @@ function createWecomConfigCollector() {
2778
3259
  var wecomClientModule = {
2779
3260
  type: "wecom",
2780
3261
  createConfigCollector: createWecomConfigCollector,
2781
- createClientAdapter(config) {
2782
- return new WecomIMAdapter(config);
3262
+ createClientAdapter({ config, common }) {
3263
+ return new WecomIMAdapter(config, void 0, common);
2783
3264
  }
2784
3265
  };
2785
3266
 
@@ -3087,6 +3568,7 @@ function chunkText3(text2, maxLen) {
3087
3568
  var WeixinIMAdapter = class {
3088
3569
  #config;
3089
3570
  #logger;
3571
+ #t;
3090
3572
  #onOutput = null;
3091
3573
  #client = null;
3092
3574
  #egressQueue = [];
@@ -3097,9 +3579,10 @@ var WeixinIMAdapter = class {
3097
3579
  #recentInboundContentKeys = /* @__PURE__ */ new Map();
3098
3580
  #rateLimitEvents = [];
3099
3581
  #rateLimitCircuitUntil = 0;
3100
- constructor(config, logger3 = createLogger("weixin")) {
3582
+ constructor(config, logger3 = createLogger("weixin"), common) {
3101
3583
  this.#config = config;
3102
3584
  this.#logger = logger3;
3585
+ this.#t = getTranslatorForCommon(common);
3103
3586
  }
3104
3587
  async start(onOutput) {
3105
3588
  this.#onOutput = onOutput;
@@ -3120,15 +3603,23 @@ var WeixinIMAdapter = class {
3120
3603
  this.#logger.debug(`ignoring unsupported Weixin group message (session=${clientSessionId})`);
3121
3604
  return;
3122
3605
  }
3123
- this.#resetProgressState(clientSessionId);
3124
3606
  const normalizedText = text2.trim();
3607
+ this.#resetProgressState(clientSessionId);
3608
+ await this.#client?.sendTyping(chatId);
3609
+ this.#startTypingHeartbeat(clientSessionId, chatId);
3610
+ const helpMarkdown = resolveHelpMarkdown(normalizedText, this.#t);
3611
+ if (helpMarkdown) {
3612
+ await this.#client?.sendText(chatId, helpMarkdown);
3613
+ this.#stopProgressTimer(clientSessionId);
3614
+ this.#stopTypingHeartbeat(clientSessionId);
3615
+ await this.#client?.stopTyping(chatId);
3616
+ return;
3617
+ }
3125
3618
  const commandEvent = parseSlashCommand(normalizedText, clientSessionId);
3126
3619
  if (commandEvent) {
3127
3620
  await this.#onOutput(commandEvent);
3128
3621
  return;
3129
3622
  }
3130
- await this.#client?.sendTyping(chatId);
3131
- this.#startTypingHeartbeat(clientSessionId, chatId);
3132
3623
  await this.#onOutput({
3133
3624
  type: "user.message",
3134
3625
  clientSessionId,
@@ -3184,6 +3675,14 @@ var WeixinIMAdapter = class {
3184
3675
  continue;
3185
3676
  }
3186
3677
  if (event.type !== "assistant.message") {
3678
+ const statusMarkdown = renderStatusMarkdown(event, this.#t);
3679
+ if (statusMarkdown) {
3680
+ await this.#sendTextWithProtection(target.chatId, statusMarkdown);
3681
+ this.#stopProgressTimer(event.clientSessionId);
3682
+ this.#stopTypingHeartbeat(event.clientSessionId);
3683
+ await this.#client.stopTyping(target.chatId);
3684
+ continue;
3685
+ }
3187
3686
  this.#recordProgressEvent(event);
3188
3687
  continue;
3189
3688
  }
@@ -3241,9 +3740,7 @@ var WeixinIMAdapter = class {
3241
3740
  return;
3242
3741
  }
3243
3742
  const message = error instanceof Error ? error.message : String(error);
3244
- const text2 = `[agent-bridge error] Message delivery failed
3245
-
3246
- ${message}`;
3743
+ const text2 = formatSendFailureNotice(this.#t, message);
3247
3744
  try {
3248
3745
  await this.#client.sendText(chatId, text2);
3249
3746
  } catch (notifyError) {
@@ -3256,7 +3753,7 @@ ${message}`;
3256
3753
  }
3257
3754
  const now = Date.now();
3258
3755
  if (this.#rateLimitCircuitUntil > now) {
3259
- throw new Error("Weixin send is cooling down after rate limiting. Please try again shortly.");
3756
+ throw new Error(this.#t("client.weixinCooldown"));
3260
3757
  }
3261
3758
  if (this.#rateLimitCircuitUntil !== 0 && this.#rateLimitCircuitUntil <= now) {
3262
3759
  this.#rateLimitCircuitUntil = 0;
@@ -3272,7 +3769,7 @@ ${message}`;
3272
3769
  if (this.#isRateLimitError(error)) {
3273
3770
  this.#recordRateLimitEvent(now);
3274
3771
  if (this.#rateLimitCircuitUntil > now) {
3275
- throw new Error("Weixin send is cooling down after rate limiting. Please try again shortly.");
3772
+ throw new Error(this.#t("client.weixinCooldown"));
3276
3773
  }
3277
3774
  }
3278
3775
  throw error;
@@ -3301,7 +3798,7 @@ ${message}`;
3301
3798
  }
3302
3799
  #createProgressState() {
3303
3800
  return {
3304
- renderer: new ProgressRenderer(),
3801
+ renderer: new ProgressRenderer({ t: this.#t }),
3305
3802
  dirty: false,
3306
3803
  interval: null
3307
3804
  };
@@ -3486,8 +3983,8 @@ function createWeixinConfigCollector() {
3486
3983
  var weixinClientModule = {
3487
3984
  type: "weixin",
3488
3985
  createConfigCollector: createWeixinConfigCollector,
3489
- createClientAdapter(config) {
3490
- return new WeixinIMAdapter(config);
3986
+ createClientAdapter({ config, common }) {
3987
+ return new WeixinIMAdapter(config, void 0, common);
3491
3988
  }
3492
3989
  };
3493
3990
 
@@ -3516,14 +4013,19 @@ var logger2 = createLogger("runner");
3516
4013
  async function runChannel({ channelName, channelConfig, defaults }) {
3517
4014
  const clientModule = getTypedClientModule(channelConfig.client);
3518
4015
  const agentModule = getTypedAgentModule(channelConfig.agent);
3519
- const imAdapter = clientModule.createClientAdapter(channelConfig.client.config);
4016
+ const common = {
4017
+ channelName,
4018
+ language: channelConfig.common.language
4019
+ };
4020
+ const imAdapter = clientModule.createClientAdapter({ config: channelConfig.client.config, common });
3520
4021
  const bindingStore = createFileSessionBindingStore(getSessionBindingStorePath(channelName));
3521
4022
  const core = new GatewayCore({
3522
4023
  imAdapter,
3523
4024
  agentModule,
3524
4025
  agentConfig: channelConfig.agent.config,
3525
4026
  agentIdleTimeoutMs: defaults.agentIdleTimeoutMs,
3526
- bindingStore
4027
+ bindingStore,
4028
+ common
3527
4029
  });
3528
4030
  await core.start();
3529
4031
  logger2.info(`channel ${channelName} started`);
@@ -3537,6 +4039,8 @@ async function runChannel({ channelName, channelConfig, defaults }) {
3537
4039
  }
3538
4040
 
3539
4041
  // src/cli.ts
4042
+ var require2 = createRequire(import.meta.url);
4043
+ var { version } = require2("../package.json");
3540
4044
  async function selectModuleType(label, modules, ctx) {
3541
4045
  if (modules.length === 0) {
3542
4046
  throw new Error(`No modules available for ${label}`);
@@ -3558,6 +4062,15 @@ async function collectModuleConfig(module, ctx) {
3558
4062
  await collector.validate(config);
3559
4063
  return config;
3560
4064
  }
4065
+ async function collectCommonChannelConfig(ctx) {
4066
+ const language = await ctx.select("Channel language", [
4067
+ { label: "English (en-US)", value: "en-US" },
4068
+ { label: "\u4E2D\u6587 (zh-CN)", value: "zh-CN" }
4069
+ ]);
4070
+ return {
4071
+ language
4072
+ };
4073
+ }
3561
4074
  async function addChannel(config) {
3562
4075
  const ctx = createPromptContext();
3563
4076
  try {
@@ -3569,6 +4082,7 @@ async function addChannel(config) {
3569
4082
  return null;
3570
4083
  }
3571
4084
  });
4085
+ const commonConfig = await collectCommonChannelConfig(ctx);
3572
4086
  const clientType = await selectModuleType("Select client module", listClientModules(), ctx);
3573
4087
  const clientModule = getClientModule(clientType);
3574
4088
  if (!clientModule) {
@@ -3582,6 +4096,7 @@ async function addChannel(config) {
3582
4096
  }
3583
4097
  const agentConfig = await collectModuleConfig(agentModule, ctx);
3584
4098
  config.channels[name] = {
4099
+ common: commonConfig,
3585
4100
  client: {
3586
4101
  type: clientType,
3587
4102
  config: clientConfig
@@ -3660,7 +4175,7 @@ async function startChannel(channelName) {
3660
4175
  }
3661
4176
  async function runCli(argv = process2.argv) {
3662
4177
  const program = new Command();
3663
- program.name("agent-bridge").description("IM to Agent bridge CLI").version("0.1.0");
4178
+ program.name("agent-bridge").description("IM to Agent bridge CLI").version(version);
3664
4179
  program.command("add").description("Interactively add a channel").action(async () => {
3665
4180
  const config = await loadConfig();
3666
4181
  await addChannel(config);