weacpx 0.4.0-beta.2 → 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.
package/dist/cli.js CHANGED
@@ -9898,6 +9898,22 @@ var init_registry = __esm(() => {
9898
9898
  cliProviders = new Map;
9899
9899
  });
9900
9900
 
9901
+ // src/weixin/storage/ensure-dir.ts
9902
+ import fs2 from "node:fs";
9903
+ function ensureDirSync(dir) {
9904
+ try {
9905
+ fs2.mkdirSync(dir, { recursive: true });
9906
+ return;
9907
+ } catch (err) {
9908
+ try {
9909
+ if (fs2.statSync(dir).isDirectory())
9910
+ return;
9911
+ } catch {}
9912
+ throw err;
9913
+ }
9914
+ }
9915
+ var init_ensure_dir = () => {};
9916
+
9901
9917
  // src/weixin/storage/state-dir.ts
9902
9918
  import os from "node:os";
9903
9919
  import path3 from "node:path";
@@ -9907,7 +9923,7 @@ function resolveStateDir() {
9907
9923
  var init_state_dir = () => {};
9908
9924
 
9909
9925
  // src/weixin/auth/accounts.ts
9910
- import fs2 from "node:fs";
9926
+ import fs3 from "node:fs";
9911
9927
  import path4 from "node:path";
9912
9928
  function normalizeAccountId(raw) {
9913
9929
  return raw.trim().toLowerCase().replace(/[@.]/g, "-");
@@ -9930,9 +9946,9 @@ function resolveAccountIndexPath() {
9930
9946
  function listIndexedWeixinAccountIds() {
9931
9947
  const filePath = resolveAccountIndexPath();
9932
9948
  try {
9933
- if (!fs2.existsSync(filePath))
9949
+ if (!fs3.existsSync(filePath))
9934
9950
  return [];
9935
- const raw = fs2.readFileSync(filePath, "utf-8");
9951
+ const raw = fs3.readFileSync(filePath, "utf-8");
9936
9952
  const parsed = JSON.parse(raw);
9937
9953
  if (!Array.isArray(parsed))
9938
9954
  return [];
@@ -9943,8 +9959,8 @@ function listIndexedWeixinAccountIds() {
9943
9959
  }
9944
9960
  function registerWeixinAccountId(accountId) {
9945
9961
  const dir = resolveWeixinStateDir();
9946
- fs2.mkdirSync(dir, { recursive: true });
9947
- fs2.writeFileSync(resolveAccountIndexPath(), JSON.stringify([accountId], null, 2), "utf-8");
9962
+ ensureDirSync(dir);
9963
+ fs3.writeFileSync(resolveAccountIndexPath(), JSON.stringify([accountId], null, 2), "utf-8");
9948
9964
  }
9949
9965
  function resolveAccountsDir() {
9950
9966
  return path4.join(resolveWeixinStateDir(), "accounts");
@@ -9955,9 +9971,9 @@ function resolveAccountPath(accountId) {
9955
9971
  function loadLegacyToken() {
9956
9972
  const legacyPath = path4.join(resolveStateDir(), "credentials", "openclaw-weixin", "credentials.json");
9957
9973
  try {
9958
- if (!fs2.existsSync(legacyPath))
9974
+ if (!fs3.existsSync(legacyPath))
9959
9975
  return;
9960
- const raw = fs2.readFileSync(legacyPath, "utf-8");
9976
+ const raw = fs3.readFileSync(legacyPath, "utf-8");
9961
9977
  const parsed = JSON.parse(raw);
9962
9978
  return typeof parsed.token === "string" ? parsed.token : undefined;
9963
9979
  } catch {
@@ -9966,8 +9982,8 @@ function loadLegacyToken() {
9966
9982
  }
9967
9983
  function readAccountFile(filePath) {
9968
9984
  try {
9969
- if (fs2.existsSync(filePath)) {
9970
- return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
9985
+ if (fs3.existsSync(filePath)) {
9986
+ return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
9971
9987
  }
9972
9988
  } catch {}
9973
9989
  return null;
@@ -9989,7 +10005,7 @@ function loadWeixinAccount(accountId) {
9989
10005
  }
9990
10006
  function saveWeixinAccount(accountId, update) {
9991
10007
  const dir = resolveAccountsDir();
9992
- fs2.mkdirSync(dir, { recursive: true });
10008
+ ensureDirSync(dir);
9993
10009
  const existing = loadWeixinAccount(accountId) ?? {};
9994
10010
  const token = update.token?.trim() || existing.token;
9995
10011
  const baseUrl = update.baseUrl?.trim() || existing.baseUrl;
@@ -10000,14 +10016,14 @@ function saveWeixinAccount(accountId, update) {
10000
10016
  ...userId ? { userId } : {}
10001
10017
  };
10002
10018
  const filePath = resolveAccountPath(accountId);
10003
- fs2.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
10019
+ fs3.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
10004
10020
  try {
10005
- fs2.chmodSync(filePath, 384);
10021
+ fs3.chmodSync(filePath, 384);
10006
10022
  } catch {}
10007
10023
  }
10008
10024
  function clearWeixinAccount(accountId) {
10009
10025
  try {
10010
- fs2.unlinkSync(resolveAccountPath(accountId));
10026
+ fs3.unlinkSync(resolveAccountPath(accountId));
10011
10027
  } catch {}
10012
10028
  }
10013
10029
  function clearAllWeixinAccounts() {
@@ -10016,7 +10032,7 @@ function clearAllWeixinAccounts() {
10016
10032
  clearWeixinAccount(id);
10017
10033
  }
10018
10034
  try {
10019
- fs2.writeFileSync(resolveAccountIndexPath(), "[]", "utf-8");
10035
+ fs3.writeFileSync(resolveAccountIndexPath(), "[]", "utf-8");
10020
10036
  } catch {}
10021
10037
  }
10022
10038
  function resolveConfigPath() {
@@ -10028,9 +10044,9 @@ function resolveConfigPath() {
10028
10044
  function loadConfigRouteTag(accountId) {
10029
10045
  try {
10030
10046
  const configPath = resolveConfigPath();
10031
- if (!fs2.existsSync(configPath))
10047
+ if (!fs3.existsSync(configPath))
10032
10048
  return;
10033
- const raw = fs2.readFileSync(configPath, "utf-8");
10049
+ const raw = fs3.readFileSync(configPath, "utf-8");
10034
10050
  const cfg = JSON.parse(raw);
10035
10051
  const channels = cfg.channels;
10036
10052
  const section = channels?.["openclaw-weixin"];
@@ -10074,11 +10090,12 @@ function resolveWeixinAccount(accountId) {
10074
10090
  }
10075
10091
  var DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com", CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
10076
10092
  var init_accounts = __esm(() => {
10093
+ init_ensure_dir();
10077
10094
  init_state_dir();
10078
10095
  });
10079
10096
 
10080
10097
  // src/weixin/util/logger.ts
10081
- import fs3 from "node:fs";
10098
+ import fs4 from "node:fs";
10082
10099
  import os2 from "node:os";
10083
10100
  import path5 from "node:path";
10084
10101
  function resolveMinLevel() {
@@ -10128,10 +10145,10 @@ function writeLog(level, message, accountId) {
10128
10145
  });
10129
10146
  try {
10130
10147
  if (!logDirEnsured) {
10131
- fs3.mkdirSync(MAIN_LOG_DIR, { recursive: true });
10148
+ fs4.mkdirSync(MAIN_LOG_DIR, { recursive: true });
10132
10149
  logDirEnsured = true;
10133
10150
  }
10134
- fs3.appendFileSync(resolveMainLogPath(), `${entry}
10151
+ fs4.appendFileSync(resolveMainLogPath(), `${entry}
10135
10152
  `, "utf-8");
10136
10153
  } catch {}
10137
10154
  }
@@ -12103,7 +12120,7 @@ var init_media_store = __esm(() => {
12103
12120
  });
12104
12121
 
12105
12122
  // src/channels/outbound-media-safety.ts
12106
- import fs4 from "node:fs/promises";
12123
+ import fs5 from "node:fs/promises";
12107
12124
  import path7 from "node:path";
12108
12125
  async function resolveSafeOutboundMediaPath(mediaPath, allowedRoots) {
12109
12126
  if (mediaPath.startsWith("http://") || mediaPath.startsWith("https://")) {
@@ -12114,7 +12131,7 @@ async function resolveSafeOutboundMediaPath(mediaPath, allowedRoots) {
12114
12131
  if (!realCandidate) {
12115
12132
  return null;
12116
12133
  }
12117
- const stat2 = await fs4.stat(realCandidate).catch(() => null);
12134
+ const stat2 = await fs5.stat(realCandidate).catch(() => null);
12118
12135
  if (!stat2?.isFile()) {
12119
12136
  return null;
12120
12137
  }
@@ -12128,7 +12145,7 @@ async function resolveSafeOutboundMediaPath(mediaPath, allowedRoots) {
12128
12145
  }
12129
12146
  async function realpathOrNull(filePath) {
12130
12147
  try {
12131
- return await fs4.realpath(filePath);
12148
+ return await fs5.realpath(filePath);
12132
12149
  } catch {
12133
12150
  return null;
12134
12151
  }
@@ -12824,10 +12841,10 @@ var init_cdn_upload = __esm(() => {
12824
12841
 
12825
12842
  // src/weixin/cdn/upload.ts
12826
12843
  import crypto3 from "node:crypto";
12827
- import fs5 from "node:fs/promises";
12844
+ import fs6 from "node:fs/promises";
12828
12845
  async function uploadMediaToCdn(params) {
12829
12846
  const { filePath, toUserId, opts, cdnBaseUrl, mediaType, label } = params;
12830
- const plaintext = await fs5.readFile(filePath);
12847
+ const plaintext = await fs6.readFile(filePath);
12831
12848
  const rawsize = plaintext.length;
12832
12849
  const rawfilemd5 = crypto3.createHash("md5").update(plaintext).digest("hex");
12833
12850
  const filesize = aesEcbPaddedSize(rawsize);
@@ -12947,14 +12964,14 @@ var init_send_media = __esm(() => {
12947
12964
  });
12948
12965
 
12949
12966
  // src/weixin/messaging/debug-mode.ts
12950
- import fs6 from "node:fs";
12967
+ import fs7 from "node:fs";
12951
12968
  import path10 from "node:path";
12952
12969
  function resolveDebugModePath() {
12953
12970
  return path10.join(resolveStateDir(), "openclaw-weixin", "debug-mode.json");
12954
12971
  }
12955
12972
  function loadState() {
12956
12973
  try {
12957
- const raw = fs6.readFileSync(resolveDebugModePath(), "utf-8");
12974
+ const raw = fs7.readFileSync(resolveDebugModePath(), "utf-8");
12958
12975
  const parsed = JSON.parse(raw);
12959
12976
  if (parsed && typeof parsed.accounts === "object")
12960
12977
  return parsed;
@@ -12963,8 +12980,8 @@ function loadState() {
12963
12980
  }
12964
12981
  function saveState(state) {
12965
12982
  const filePath = resolveDebugModePath();
12966
- fs6.mkdirSync(path10.dirname(filePath), { recursive: true });
12967
- fs6.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
12983
+ ensureDirSync(path10.dirname(filePath));
12984
+ fs7.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
12968
12985
  }
12969
12986
  function toggleDebugMode(accountId) {
12970
12987
  const state = loadState();
@@ -12978,6 +12995,7 @@ function toggleDebugMode(accountId) {
12978
12995
  return next;
12979
12996
  }
12980
12997
  var init_debug_mode = __esm(() => {
12998
+ init_ensure_dir();
12981
12999
  init_state_dir();
12982
13000
  init_logger();
12983
13001
  });
@@ -13130,7 +13148,7 @@ function normalizeMediaArray(media) {
13130
13148
 
13131
13149
  // src/weixin/messaging/handle-weixin-message-turn.ts
13132
13150
  import crypto4 from "node:crypto";
13133
- import fs7 from "node:fs/promises";
13151
+ import fs8 from "node:fs/promises";
13134
13152
  import { tmpdir } from "node:os";
13135
13153
  import path11 from "node:path";
13136
13154
  function utf8ByteLength(s) {
@@ -13230,7 +13248,7 @@ function createSaveMediaBuffer(mediaTempDir) {
13230
13248
  throw new Error(`media exceeds ${maxBytes} bytes`);
13231
13249
  }
13232
13250
  const dir = path11.join(resolveMediaTempDir(mediaTempDir), subdir ?? "");
13233
- await fs7.mkdir(dir, { recursive: true });
13251
+ await fs8.mkdir(dir, { recursive: true });
13234
13252
  let ext = ".bin";
13235
13253
  if (originalFilename) {
13236
13254
  ext = path11.extname(originalFilename) || ".bin";
@@ -13239,7 +13257,7 @@ function createSaveMediaBuffer(mediaTempDir) {
13239
13257
  }
13240
13258
  const name = `${Date.now()}-${crypto4.randomBytes(4).toString("hex")}${ext}`;
13241
13259
  const filePath = path11.join(dir, name);
13242
- await fs7.writeFile(filePath, buffer);
13260
+ await fs8.writeFile(filePath, buffer);
13243
13261
  return { path: filePath };
13244
13262
  };
13245
13263
  }
@@ -13379,7 +13397,7 @@ async function handleWeixinMessageTurn(full, deps) {
13379
13397
  continue;
13380
13398
  }
13381
13399
  try {
13382
- const buffer = await fs7.readFile(filePath);
13400
+ const buffer = await fs8.readFile(filePath);
13383
13401
  const mimeType = downloaded.fileMediaType ?? downloaded.voiceMediaType ?? defaultWeixinMime(descriptor.kind);
13384
13402
  media.push(await mediaStore.saveMediaBuffer({
13385
13403
  channelId: "weixin",
@@ -13393,7 +13411,7 @@ async function handleWeixinMessageTurn(full, deps) {
13393
13411
  maxBytes: descriptor.kind === "image" ? DEFAULT_IMAGE_MAX_BYTES : DEFAULT_ATTACHMENT_MAX_BYTES
13394
13412
  }));
13395
13413
  } finally {
13396
- await fs7.rm(filePath, { force: true }).catch(() => {});
13414
+ await fs8.rm(filePath, { force: true }).catch(() => {});
13397
13415
  }
13398
13416
  } catch (err) {
13399
13417
  deps.errLog(`media download failed: ${String(err)}`);
@@ -13565,7 +13583,7 @@ var init_handle_weixin_message_turn = __esm(() => {
13565
13583
  });
13566
13584
 
13567
13585
  // src/weixin/storage/sync-buf.ts
13568
- import fs8 from "node:fs";
13586
+ import fs9 from "node:fs";
13569
13587
  import path12 from "node:path";
13570
13588
  function resolveAccountsDir2() {
13571
13589
  return path12.join(resolveStateDir(), "openclaw-weixin", "accounts");
@@ -13578,7 +13596,7 @@ function getLegacySyncBufDefaultJsonPath() {
13578
13596
  }
13579
13597
  function readSyncBufFile(filePath) {
13580
13598
  try {
13581
- const raw = fs8.readFileSync(filePath, "utf-8");
13599
+ const raw = fs9.readFileSync(filePath, "utf-8");
13582
13600
  const data = JSON.parse(raw);
13583
13601
  if (typeof data.get_updates_buf === "string") {
13584
13602
  return data.get_updates_buf;
@@ -13602,11 +13620,12 @@ function loadGetUpdatesBuf(filePath) {
13602
13620
  }
13603
13621
  function saveGetUpdatesBuf(filePath, getUpdatesBuf) {
13604
13622
  const dir = path12.dirname(filePath);
13605
- fs8.mkdirSync(dir, { recursive: true });
13606
- fs8.writeFileSync(filePath, JSON.stringify({ get_updates_buf: getUpdatesBuf }, null, 0), "utf-8");
13623
+ ensureDirSync(dir);
13624
+ fs9.writeFileSync(filePath, JSON.stringify({ get_updates_buf: getUpdatesBuf }, null, 0), "utf-8");
13607
13625
  }
13608
13626
  var init_sync_buf = __esm(() => {
13609
13627
  init_accounts();
13628
+ init_ensure_dir();
13610
13629
  init_state_dir();
13611
13630
  });
13612
13631
 
@@ -14153,7 +14172,7 @@ import { dirname as dirname6, join as join3 } from "node:path";
14153
14172
  import { homedir as homedir3 } from "node:os";
14154
14173
  function createWeixinConsumerLock(options = {}) {
14155
14174
  const lockFilePath = options.lockFilePath ?? join3(homedir3(), ".weacpx", "runtime", "weixin-consumer.lock.json");
14156
- const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning3;
14175
+ const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning4;
14157
14176
  const onDiagnostic = options.onDiagnostic;
14158
14177
  return {
14159
14178
  async acquire(meta2) {
@@ -14240,7 +14259,7 @@ async function loadLockMetadata(path13) {
14240
14259
  return null;
14241
14260
  }
14242
14261
  }
14243
- function defaultIsProcessRunning3(pid) {
14262
+ function defaultIsProcessRunning4(pid) {
14244
14263
  try {
14245
14264
  process.kill(pid, 0);
14246
14265
  return true;
@@ -16569,7 +16588,7 @@ async function handleSessionRemove(context, chatKey, alias) {
16569
16588
  return { text: lines.join(`
16570
16589
  `) };
16571
16590
  }
16572
- async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal) {
16591
+ async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent) {
16573
16592
  const effectiveReplyMode = session.replyMode ?? context.config?.channel.replyMode ?? "verbose";
16574
16593
  if (!session.replyMode)
16575
16594
  session.replyMode = effectiveReplyMode;
@@ -16594,7 +16613,7 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
16594
16613
  const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId);
16595
16614
  try {
16596
16615
  const replyContext = transportReply && context.quota && getChannelIdFromChatKey(chatKey) === "weixin" ? { chatKey, quota: context.quota } : undefined;
16597
- const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext, media, abortSignal);
16616
+ const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext, media, abortSignal, onToolEvent);
16598
16617
  if (claimHumanReply) {
16599
16618
  try {
16600
16619
  await context.orchestration?.claimActiveHumanReply?.(claimHumanReply);
@@ -16614,17 +16633,17 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
16614
16633
  throw error2;
16615
16634
  }
16616
16635
  }
16617
- async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal) {
16636
+ async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent) {
16618
16637
  const session = await context.sessions.getCurrentSession(chatKey);
16619
16638
  if (!session) {
16620
16639
  return { text: NO_CURRENT_SESSION_TEXT };
16621
16640
  }
16622
16641
  try {
16623
- return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal);
16642
+ return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
16624
16643
  } catch (error2) {
16625
16644
  const recovered = await context.recovery.tryRecoverMissingSession(session, error2);
16626
16645
  if (recovered) {
16627
- return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal);
16646
+ return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
16628
16647
  }
16629
16648
  return context.recovery.renderTransportError(session, error2);
16630
16649
  }
@@ -18150,7 +18169,7 @@ class CommandRouter {
18150
18169
  this.quota = quota;
18151
18170
  this.logger = logger2 ?? createNoopAppLogger();
18152
18171
  }
18153
- async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal) {
18172
+ async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent) {
18154
18173
  const startedAt = Date.now();
18155
18174
  const command = parseCommand(input);
18156
18175
  await this.logger.debug("command.parsed", "parsed inbound command", {
@@ -18264,7 +18283,7 @@ class CommandRouter {
18264
18283
  case "task.cancel":
18265
18284
  return await handleTaskCancel(this.createHandlerContext(), chatKey, command.taskId);
18266
18285
  case "prompt":
18267
- return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal);
18286
+ return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
18268
18287
  }
18269
18288
  });
18270
18289
  }
@@ -18316,7 +18335,7 @@ class CommandRouter {
18316
18335
  return {
18317
18336
  setModeTransportSession: (session, modeId) => this.setModeTransportSession(session, modeId),
18318
18337
  cancelTransportSession: (session) => this.cancelTransportSession(session),
18319
- promptTransportSession: (session, text, reply, replyContext, media, abortSignal) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal)
18338
+ promptTransportSession: (session, text, reply, replyContext, media, abortSignal, onToolEvent) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent)
18320
18339
  };
18321
18340
  }
18322
18341
  createSessionRenderRecoveryOps() {
@@ -18479,7 +18498,7 @@ class CommandRouter {
18479
18498
  async checkTransportSession(session) {
18480
18499
  return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
18481
18500
  }
18482
- async promptTransportSession(session, text, reply, replyContext, media, abortSignal) {
18501
+ async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent) {
18483
18502
  session.mcpCoordinatorSession ??= session.transportSession;
18484
18503
  let done = false;
18485
18504
  let cancelOnAbort;
@@ -18516,7 +18535,10 @@ class CommandRouter {
18516
18535
  abortSignal.addEventListener("abort", cancelOnAbort, { once: true });
18517
18536
  }
18518
18537
  try {
18519
- return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext, media ? { media } : undefined));
18538
+ return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext, {
18539
+ ...media ? { media } : {},
18540
+ ...onToolEvent ? { onToolEvent } : {}
18541
+ }));
18520
18542
  } finally {
18521
18543
  done = true;
18522
18544
  if (cancelOnAbort && abortSignal) {
@@ -18667,7 +18689,7 @@ class ConsoleAgent {
18667
18689
  mimeType: m.mimeType,
18668
18690
  ...m.fileName ? { fileName: m.fileName } : {}
18669
18691
  })) : undefined;
18670
- return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal);
18692
+ return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent);
18671
18693
  }
18672
18694
  isKnownCommand(text) {
18673
18695
  return isKnownWeacpxCommandText(text);
@@ -22767,6 +22789,10 @@ function encodeBridgePromptSegmentEvent(event) {
22767
22789
  return `${JSON.stringify(event)}
22768
22790
  `;
22769
22791
  }
22792
+ function encodeBridgePromptToolEvent(event) {
22793
+ return `${JSON.stringify(event)}
22794
+ `;
22795
+ }
22770
22796
  function encodeBridgeSessionProgressEvent(event) {
22771
22797
  return `${JSON.stringify(event)}
22772
22798
  `;
@@ -22834,6 +22860,11 @@ class AcpxBridgeClient {
22834
22860
  type: "prompt.segment",
22835
22861
  text: message.text
22836
22862
  });
22863
+ } else if (message.event === "prompt.tool_event") {
22864
+ pending.onEvent?.({
22865
+ type: "prompt.tool_event",
22866
+ event: message.toolEvent
22867
+ });
22837
22868
  } else if (message.event === "session.progress") {
22838
22869
  pending.onEvent?.({
22839
22870
  type: "session.progress",
@@ -23157,6 +23188,17 @@ var init_quota_gated_reply_sink = __esm(() => {
23157
23188
  ADAPTIVE_WINDOW_SCHEDULE_MS = [3000, 6000, 12000, 24000, 48000, 60000];
23158
23189
  });
23159
23190
 
23191
+ // src/transport/tool-event-mode.ts
23192
+ function resolveToolEventMode(input) {
23193
+ if (input?.toolEventMode !== undefined) {
23194
+ return input.toolEventMode;
23195
+ }
23196
+ if (input?.onToolEvent !== undefined) {
23197
+ return "structured";
23198
+ }
23199
+ return "text";
23200
+ }
23201
+
23160
23202
  // src/transport/acpx-bridge/acpx-bridge-transport.ts
23161
23203
  class AcpxBridgeTransport {
23162
23204
  client;
@@ -23179,10 +23221,18 @@ class AcpxBridgeTransport {
23179
23221
  }) : null;
23180
23222
  let segmentError;
23181
23223
  let segmentChain = Promise.resolve();
23224
+ let toolEventError;
23225
+ let toolEventChain = Promise.resolve();
23226
+ let toolEventMode = resolveToolEventMode(options);
23227
+ if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
23228
+ toolEventMode = "text";
23229
+ }
23182
23230
  const result = await this.client.request("prompt", {
23183
23231
  ...this.toParams(session),
23184
23232
  text,
23185
- ...options?.media ? { media: options.media } : {}
23233
+ ...options?.media ? { media: options.media } : {},
23234
+ ...toolEventMode === "structured" || toolEventMode === "both" ? { toolEvents: true } : {},
23235
+ toolEventMode
23186
23236
  }, (event) => {
23187
23237
  if (event.type === "prompt.segment") {
23188
23238
  const onSegment = options?.onSegment;
@@ -23193,9 +23243,21 @@ class AcpxBridgeTransport {
23193
23243
  });
23194
23244
  }
23195
23245
  sink?.feedSegment(event.text);
23246
+ return;
23247
+ }
23248
+ if (event.type === "prompt.tool_event") {
23249
+ const onToolEvent = options?.onToolEvent;
23250
+ if (onToolEvent) {
23251
+ const toolEvent = event.event;
23252
+ toolEventChain = toolEventChain.then(() => onToolEvent(toolEvent)).catch((error2) => {
23253
+ toolEventError ??= error2;
23254
+ });
23255
+ }
23256
+ return;
23196
23257
  }
23197
23258
  });
23198
23259
  await segmentChain;
23260
+ await toolEventChain;
23199
23261
  if (sink) {
23200
23262
  const { overflowCount } = sink.finalize();
23201
23263
  await sink.drain({ timeoutMs: 30000 });
@@ -23207,6 +23269,9 @@ class AcpxBridgeTransport {
23207
23269
  if (segmentError) {
23208
23270
  throw segmentError;
23209
23271
  }
23272
+ if (toolEventError) {
23273
+ throw toolEventError;
23274
+ }
23210
23275
  return { text: summary ? `${summary}
23211
23276
 
23212
23277
  ${result.text}` : "" };
@@ -23214,6 +23279,9 @@ ${result.text}` : "" };
23214
23279
  if (segmentError) {
23215
23280
  throw segmentError;
23216
23281
  }
23282
+ if (toolEventError) {
23283
+ throw toolEventError;
23284
+ }
23217
23285
  return result;
23218
23286
  }
23219
23287
  async setMode(session, modeId) {
@@ -23400,8 +23468,37 @@ var init_prompt_media = __esm(() => {
23400
23468
  };
23401
23469
  });
23402
23470
 
23471
+ // src/transport/tool-kind-emoji.ts
23472
+ var TOOL_KIND_EMOJI, DEFAULT_TOOL_EMOJI;
23473
+ var init_tool_kind_emoji = __esm(() => {
23474
+ TOOL_KIND_EMOJI = {
23475
+ read: "\uD83D\uDCD6",
23476
+ search: "\uD83D\uDD0D",
23477
+ execute: "\uD83D\uDCBB",
23478
+ edit: "✏️",
23479
+ think: "\uD83E\uDDE0",
23480
+ other: "\uD83D\uDD27"
23481
+ };
23482
+ DEFAULT_TOOL_EMOJI = TOOL_KIND_EMOJI.other;
23483
+ });
23484
+
23403
23485
  // src/transport/streaming-prompt.ts
23404
- function createStreamingPromptState(formatToolCalls = false) {
23486
+ function createStreamingPromptState(formatToolCalls = false, options) {
23487
+ let toolEventMode;
23488
+ let onToolEvent;
23489
+ if (options === undefined) {
23490
+ toolEventMode = "text";
23491
+ onToolEvent = undefined;
23492
+ } else if (typeof options === "function") {
23493
+ onToolEvent = options;
23494
+ toolEventMode = "structured";
23495
+ } else {
23496
+ onToolEvent = options.onToolEvent;
23497
+ toolEventMode = resolveToolEventMode({
23498
+ toolEventMode: options.mode,
23499
+ onToolEvent
23500
+ });
23501
+ }
23405
23502
  return {
23406
23503
  buffer: "",
23407
23504
  segments: [],
@@ -23409,6 +23506,8 @@ function createStreamingPromptState(formatToolCalls = false) {
23409
23506
  pendingLine: "",
23410
23507
  formatToolCalls,
23411
23508
  emittedToolCallIds: new Set,
23509
+ toolEventMode,
23510
+ onToolEvent,
23412
23511
  finalize() {
23413
23512
  if (this.pendingLine.trim().length > 0) {
23414
23513
  parseStreamingChunks(this, this.pendingLine);
@@ -23446,15 +23545,24 @@ function parseStreamingChunks(state, line) {
23446
23545
  if (!update)
23447
23546
  return;
23448
23547
  if (state.formatToolCalls && (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update")) {
23449
- const formatted = formatToolCallEvent(update, update.sessionUpdate);
23450
- if (formatted) {
23451
- const toolCallId = update.toolCallId;
23452
- if (toolCallId) {
23453
- if (state.emittedToolCallIds.has(toolCallId))
23454
- return;
23455
- state.emittedToolCallIds.add(toolCallId);
23548
+ const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
23549
+ const wantsText = state.toolEventMode === "text" || state.toolEventMode === "both";
23550
+ if (wantsStructured && state.onToolEvent) {
23551
+ const toolEvent = buildToolUseEvent(update);
23552
+ if (toolEvent)
23553
+ state.onToolEvent(toolEvent);
23554
+ }
23555
+ if (wantsText) {
23556
+ const formatted = formatToolCallEvent(update, update.sessionUpdate);
23557
+ if (formatted) {
23558
+ const toolCallId = update.toolCallId;
23559
+ if (toolCallId) {
23560
+ if (state.emittedToolCallIds.has(toolCallId))
23561
+ return;
23562
+ state.emittedToolCallIds.add(toolCallId);
23563
+ }
23564
+ state.segments.push(formatted);
23456
23565
  }
23457
- state.segments.push(formatted);
23458
23566
  }
23459
23567
  return;
23460
23568
  }
@@ -23484,16 +23592,51 @@ function formatToolCallEvent(update, sessionUpdate) {
23484
23592
  const title = update.title ?? "";
23485
23593
  if (title.length === 0)
23486
23594
  return null;
23487
- const emoji2 = KIND_EMOJI[kind] ?? "\uD83D\uDD27";
23488
- const inputSummary = summarizeToolInput(update.rawInput);
23595
+ const emoji2 = TOOL_KIND_EMOJI[kind] ?? DEFAULT_TOOL_EMOJI;
23596
+ const inputSummary = summarizeToolInput(update.rawInput, title);
23489
23597
  const status = readString(update, "status");
23598
+ if (!inputSummary && status === "pending")
23599
+ return null;
23490
23600
  if (!inputSummary && isGenericToolTitle(kind, title))
23491
23601
  return null;
23492
- const summaryText = inputSummary ? `: ${truncateToolDisplay(inputSummary)}` : "";
23602
+ const summaryText = inputSummary && inputSummary !== title ? `: ${truncateToolDisplay(inputSummary)}` : "";
23493
23603
  const statusText = status ? ` (${status})` : "";
23494
23604
  return `${emoji2} ${title}${statusText}${summaryText}`;
23495
23605
  }
23496
- function summarizeToolInput(rawInput) {
23606
+ function buildToolUseEvent(update) {
23607
+ if (!update)
23608
+ return null;
23609
+ const toolCallId = update.toolCallId;
23610
+ if (!toolCallId)
23611
+ return null;
23612
+ const kindRaw = update.kind ?? "";
23613
+ const kind = (() => {
23614
+ switch (kindRaw) {
23615
+ case "read":
23616
+ case "search":
23617
+ case "execute":
23618
+ case "edit":
23619
+ case "think":
23620
+ return kindRaw;
23621
+ default:
23622
+ return "other";
23623
+ }
23624
+ })();
23625
+ const title = (update.title ?? "").trim();
23626
+ const toolName = title || "Tool";
23627
+ const summaryRaw = summarizeToolInput(update.rawInput, title);
23628
+ const summary = summaryRaw && summaryRaw !== title ? summaryRaw : undefined;
23629
+ const statusRaw = readString(update, "status");
23630
+ const status = statusRaw === "completed" || statusRaw === "success" ? "success" : statusRaw === "failed" || statusRaw === "error" ? "error" : "running";
23631
+ return {
23632
+ toolCallId,
23633
+ toolName,
23634
+ kind,
23635
+ ...summary ? { summary } : {},
23636
+ status
23637
+ };
23638
+ }
23639
+ function summarizeToolInput(rawInput, title = "") {
23497
23640
  if (rawInput == null)
23498
23641
  return;
23499
23642
  if (typeof rawInput === "string" || typeof rawInput === "number" || typeof rawInput === "boolean") {
@@ -23501,6 +23644,9 @@ function summarizeToolInput(rawInput) {
23501
23644
  }
23502
23645
  if (!isRecord3(rawInput))
23503
23646
  return;
23647
+ const taskSummary = summarizeTaskInput(rawInput, title);
23648
+ if (taskSummary)
23649
+ return taskSummary;
23504
23650
  const command = readFirstString(rawInput, ["command", "cmd", "program"]);
23505
23651
  const args = readFirstStringArray(rawInput, ["args", "arguments"]);
23506
23652
  if (command) {
@@ -23523,6 +23669,7 @@ function summarizeToolInput(rawInput) {
23523
23669
  "file",
23524
23670
  "filePath",
23525
23671
  "filepath",
23672
+ "file_path",
23526
23673
  "target",
23527
23674
  "uri",
23528
23675
  "url",
@@ -23534,6 +23681,16 @@ function summarizeToolInput(rawInput) {
23534
23681
  "description"
23535
23682
  ]);
23536
23683
  }
23684
+ function summarizeTaskInput(rawInput, title) {
23685
+ const subagentType = readFirstString(rawInput, ["subagent_type", "subagentType", "agent", "agentType"]);
23686
+ const description = readFirstString(rawInput, ["description", "task", "summary"]);
23687
+ if (subagentType && description) {
23688
+ return description === title ? subagentType : `${subagentType}: ${description}`;
23689
+ }
23690
+ if (subagentType)
23691
+ return subagentType;
23692
+ return;
23693
+ }
23537
23694
  function readFirstString(record3, keys) {
23538
23695
  for (const key of keys) {
23539
23696
  const value = record3[key];
@@ -23580,14 +23737,8 @@ function isGenericToolTitle(kind, title) {
23580
23737
  }
23581
23738
  return false;
23582
23739
  }
23583
- var KIND_EMOJI;
23584
23740
  var init_streaming_prompt = __esm(() => {
23585
- KIND_EMOJI = {
23586
- read: "\uD83D\uDCD6",
23587
- search: "\uD83D\uDD0D",
23588
- execute: "\uD83D\uDCBB",
23589
- edit: "✏️"
23590
- };
23741
+ init_tool_kind_emoji();
23591
23742
  });
23592
23743
 
23593
23744
  // src/transport/acpx-cli/node-pty-helper.ts
@@ -23897,7 +24048,8 @@ class AcpxCliTransport {
23897
24048
  runCommand;
23898
24049
  runPtyCommand;
23899
24050
  queueOwnerLauncher;
23900
- constructor(options, runCommand = defaultRunner, runPtyCommand = defaultPtyRunner, queueOwnerLauncher) {
24051
+ streamingHooks;
24052
+ constructor(options, runCommand = defaultRunner, runPtyCommand = defaultPtyRunner, queueOwnerLauncher, streamingHooks = {}) {
23901
24053
  this.command = options.command ?? "acpx";
23902
24054
  this.sessionInitTimeoutMs = options.sessionInitTimeoutMs ?? 120000;
23903
24055
  this.permissionMode = options.permissionMode ?? "approve-all";
@@ -23907,6 +24059,7 @@ class AcpxCliTransport {
23907
24059
  this.queueOwnerLauncher = queueOwnerLauncher ?? new AcpxQueueOwnerLauncher({
23908
24060
  acpxCommand: this.command
23909
24061
  });
24062
+ this.streamingHooks = streamingHooks;
23910
24063
  }
23911
24064
  async ensureSession(session, _onProgress) {
23912
24065
  const args = this.buildArgs(session, [
@@ -23925,9 +24078,13 @@ class AcpxCliTransport {
23925
24078
  const structuredPrompt = await createStructuredPromptFile(text, options?.media);
23926
24079
  const args = this.buildPromptArgs(session, text, structuredPrompt?.filePath);
23927
24080
  try {
23928
- if (reply || options?.onSegment) {
24081
+ if (reply || options?.onSegment || options?.onToolEvent) {
23929
24082
  const formatToolCalls = (session.replyMode ?? "verbose") === "verbose";
23930
- const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, 30000, formatToolCalls, replyContext, options?.onSegment);
24083
+ let toolEventMode = resolveToolEventMode(options);
24084
+ if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
24085
+ toolEventMode = "text";
24086
+ }
24087
+ const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, formatToolCalls, toolEventMode, replyContext, options?.onSegment, options?.onToolEvent);
23931
24088
  const baseText = getPromptText(result2);
23932
24089
  if (!reply) {
23933
24090
  return { text: baseText };
@@ -24064,16 +24221,35 @@ ${baseText}` : "" };
24064
24221
  })
24065
24222
  ]);
24066
24223
  }
24067
- async runStreamingPrompt(command, args, reply, maxSegmentWaitMs = 30000, formatToolCalls = false, replyContext, onSegment) {
24224
+ async runStreamingPrompt(command, args, reply, formatToolCalls = false, toolEventMode = "text", replyContext, onSegment, onToolEvent) {
24225
+ const hooks = this.streamingHooks;
24226
+ const doSpawn = hooks.spawnPrompt ?? ((cmd, spawnArgs) => spawn8(cmd, spawnArgs, { stdio: ["ignore", "pipe", "pipe"] }));
24227
+ const setIntervalFn = hooks.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
24228
+ const clearIntervalFn = hooks.clearIntervalFn ?? ((timer) => clearInterval(timer));
24229
+ const maxSegmentWaitMs = hooks.maxSegmentWaitMs ?? 30000;
24230
+ const flushCheckIntervalMs = hooks.flushCheckIntervalMs ?? 5000;
24231
+ const now = hooks.now ?? (() => Date.now());
24068
24232
  return await new Promise((resolve3, reject) => {
24069
24233
  const spawnSpec = resolveSpawnCommand(command, args);
24070
- const child = spawn8(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
24234
+ const child = doSpawn(spawnSpec.command, spawnSpec.args);
24071
24235
  let stdout2 = "";
24072
24236
  let stderr = "";
24073
- const state = createStreamingPromptState(formatToolCalls);
24074
- let lastReplyAt = Date.now();
24237
+ let lastReplyAt = now();
24075
24238
  let segmentChain = Promise.resolve();
24076
24239
  let segmentError;
24240
+ let toolEventChain = Promise.resolve();
24241
+ let toolEventError;
24242
+ const userOnToolEvent = onToolEvent;
24243
+ const state = createStreamingPromptState(formatToolCalls, {
24244
+ mode: toolEventMode,
24245
+ ...userOnToolEvent ? {
24246
+ onToolEvent: (event) => {
24247
+ toolEventChain = toolEventChain.then(() => userOnToolEvent(event)).catch((error2) => {
24248
+ toolEventError ??= error2;
24249
+ });
24250
+ }
24251
+ } : {}
24252
+ });
24077
24253
  const sink = reply ? createQuotaGatedReplySink({
24078
24254
  reply,
24079
24255
  ...replyContext ? { replyContext } : {}
@@ -24085,7 +24261,7 @@ ${baseText}` : "" };
24085
24261
  });
24086
24262
  }
24087
24263
  sink?.feedSegment(segment);
24088
- lastReplyAt = Date.now();
24264
+ lastReplyAt = now();
24089
24265
  };
24090
24266
  const flushBuffer = () => {
24091
24267
  const remaining = state.buffer.trim();
@@ -24094,11 +24270,11 @@ ${baseText}` : "" };
24094
24270
  feedSegment(remaining);
24095
24271
  }
24096
24272
  };
24097
- const timer = setInterval(() => {
24098
- if (state.buffer.trim().length > 0 && Date.now() - lastReplyAt >= maxSegmentWaitMs) {
24273
+ const timer = setIntervalFn(() => {
24274
+ if (state.buffer.trim().length > 0 && now() - lastReplyAt >= maxSegmentWaitMs) {
24099
24275
  flushBuffer();
24100
24276
  }
24101
- }, 5000);
24277
+ }, flushCheckIntervalMs);
24102
24278
  child.stdout.setEncoding("utf8");
24103
24279
  child.stdout.on("data", (chunk) => {
24104
24280
  stdout2 += String(chunk);
@@ -24111,11 +24287,11 @@ ${baseText}` : "" };
24111
24287
  stderr += String(chunk);
24112
24288
  });
24113
24289
  child.on("error", (err) => {
24114
- clearInterval(timer);
24290
+ clearIntervalFn(timer);
24115
24291
  reject(err);
24116
24292
  });
24117
24293
  child.on("close", (code) => {
24118
- clearInterval(timer);
24294
+ clearIntervalFn(timer);
24119
24295
  const remaining = state.finalize();
24120
24296
  if (remaining.length > 0) {
24121
24297
  feedSegment(remaining);
@@ -24123,7 +24299,8 @@ ${baseText}` : "" };
24123
24299
  const { overflowCount } = sink?.finalize() ?? { overflowCount: 0 };
24124
24300
  Promise.all([
24125
24301
  sink?.drain({ timeoutMs: 30000 }) ?? Promise.resolve(),
24126
- segmentChain
24302
+ segmentChain,
24303
+ toolEventChain
24127
24304
  ]).then(() => {
24128
24305
  const deferred = sink?.getPendingError();
24129
24306
  if (deferred) {
@@ -24134,6 +24311,10 @@ ${baseText}` : "" };
24134
24311
  reject(segmentError);
24135
24312
  return;
24136
24313
  }
24314
+ if (toolEventError) {
24315
+ reject(toolEventError);
24316
+ return;
24317
+ }
24137
24318
  resolve3({
24138
24319
  result: { code: code ?? 1, stdout: stdout2, stderr },
24139
24320
  overflowCount
@@ -25120,7 +25301,7 @@ async function checkDaemon(options = {}) {
25120
25301
  cliEntryPath: options.cliEntryPath ?? resolveCliEntryPath(),
25121
25302
  cwd: options.cwd ?? process.cwd(),
25122
25303
  env: options.env ?? process.env,
25123
- isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning4
25304
+ isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning5
25124
25305
  });
25125
25306
  try {
25126
25307
  const status = await controller.getStatus();
@@ -25191,7 +25372,7 @@ async function checkDaemon(options = {}) {
25191
25372
  };
25192
25373
  }
25193
25374
  }
25194
- function defaultIsProcessRunning4(pid) {
25375
+ function defaultIsProcessRunning5(pid) {
25195
25376
  try {
25196
25377
  process.kill(pid, 0);
25197
25378
  return true;
@@ -38712,7 +38893,7 @@ async function asToolResult(action) {
38712
38893
  text: "Outbound budget exhausted; the action has been recorded as pending and will retry automatically after the next user inbound resets the quota window. No further action required."
38713
38894
  }
38714
38895
  ],
38715
- structuredContent: { status: "deferred_quota", chatKey: error2.chatKey },
38896
+ structuredContent: { status: "deferred_quota" },
38716
38897
  isError: false
38717
38898
  };
38718
38899
  }
@@ -38923,7 +39104,9 @@ function renderCoordinatorReviewContestedResultSuccess(task, decision) {
38923
39104
  }
38924
39105
  function formatToolError(error2) {
38925
39106
  const message = error2 instanceof Error ? error2.message : String(error2);
38926
- if (/ECONNREFUSED|ENOENT|server closed without a response|socket hang up|connect /i.test(message)) {
39107
+ const code = typeof error2 === "object" && error2 !== null && "code" in error2 ? error2.code : undefined;
39108
+ const isConnectionError = code === "ECONNREFUSED" || code === "ENOENT" || code === "ECONNRESET" || code === "EPIPE" || /server closed without a response|socket hang up/i.test(message);
39109
+ if (isConnectionError) {
38927
39110
  return `Failed to connect to the orchestration daemon: ${message}`;
38928
39111
  }
38929
39112
  return message;
@@ -39084,9 +39267,9 @@ function createOrchestrationTransport(endpoint, deps = {}) {
39084
39267
  sourceHandle: input.sourceHandle ?? input.coordinatorSession,
39085
39268
  targetAgent: input.targetAgent,
39086
39269
  task: input.task,
39087
- ...input.workingDirectory ? { cwd: input.workingDirectory } : {},
39088
- ...input.role ? { role: input.role } : {},
39089
- ...input.groupId ? { groupId: input.groupId } : {}
39270
+ ...input.workingDirectory !== undefined ? { cwd: input.workingDirectory } : {},
39271
+ ...input.role !== undefined ? { role: input.role } : {},
39272
+ ...input.groupId !== undefined ? { groupId: input.groupId } : {}
39090
39273
  }),
39091
39274
  createGroup: async (input) => await client.createGroup(input),
39092
39275
  getGroup: async (input) => await client.getGroup(input),
@@ -39095,10 +39278,10 @@ function createOrchestrationTransport(endpoint, deps = {}) {
39095
39278
  getTask: async (input) => await client.getTaskForCoordinator(input),
39096
39279
  listTasks: async (input) => await client.listTasks({
39097
39280
  coordinatorSession: input.coordinatorSession,
39098
- ...input.status ? { status: input.status } : {},
39281
+ ...input.status !== undefined ? { status: input.status } : {},
39099
39282
  ...input.stuck !== undefined ? { stuck: input.stuck } : {},
39100
- ...input.sort ? { sort: input.sort } : {},
39101
- ...input.order ? { order: input.order } : {}
39283
+ ...input.sort !== undefined ? { sort: input.sort } : {},
39284
+ ...input.order !== undefined ? { order: input.order } : {}
39102
39285
  }),
39103
39286
  approveTask: async (input) => await client.approveTask(input),
39104
39287
  rejectTask: async (input) => await client.rejectTask(input),
@@ -39205,6 +39388,84 @@ async function resolveMcpIdentity(server, options) {
39205
39388
  }
39206
39389
  throw new McpError(ErrorCode.InvalidRequest, "weacpx MCP identity is not configured; run through `weacpx mcp-stdio` or provide --coordinator-session");
39207
39390
  }
39391
+ function installMcpStdioShutdownHooks(options) {
39392
+ const platform = options.platform ?? process.platform;
39393
+ const signalSource = options.signalSource ?? process;
39394
+ const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning3;
39395
+ const setIntervalFn = options.setIntervalFn ?? ((callback, ms) => setInterval(callback, ms));
39396
+ const clearIntervalFn = options.clearIntervalFn ?? ((handle) => clearInterval(handle));
39397
+ const parentPid = options.parentPid ?? process.ppid;
39398
+ const parentCheckIntervalMs = options.parentCheckIntervalMs ?? parseParentCheckIntervalMs(process.env.WEACPX_MCP_PARENT_CHECK_INTERVAL_MS);
39399
+ let disposed = false;
39400
+ let triggered = false;
39401
+ const triggerShutdown = (reason, context) => {
39402
+ if (disposed || triggered)
39403
+ return;
39404
+ triggered = true;
39405
+ options.onDiagnostic?.("mcp.stdio.shutdown", { reason, ...context ?? {} });
39406
+ options.shutdown();
39407
+ };
39408
+ const onStreamEnd = () => triggerShutdown("stdin.end");
39409
+ const onStreamClose = () => triggerShutdown("stdin.close");
39410
+ const onStdinError = (error2) => triggerShutdown("stdin.error", errorContext(error2));
39411
+ const onStdoutError = (error2) => triggerShutdown("stdout.error", errorContext(error2));
39412
+ const onSignal = (signal) => triggerShutdown("signal", { signal });
39413
+ options.stdin.on("end", onStreamEnd);
39414
+ options.stdin.on("close", onStreamClose);
39415
+ options.stdin.on("error", onStdinError);
39416
+ options.stdout.on("error", onStdoutError);
39417
+ const signals = platform === "win32" ? ["SIGINT", "SIGTERM", "SIGBREAK"] : ["SIGINT", "SIGTERM", "SIGHUP"];
39418
+ const signalListeners = signals.map((signal) => ({ signal, listener: () => onSignal(signal) }));
39419
+ for (const { signal, listener } of signalListeners) {
39420
+ signalSource.on(signal, listener);
39421
+ }
39422
+ let parentTimer;
39423
+ if (parentPid > 1 && parentCheckIntervalMs > 0) {
39424
+ parentTimer = setIntervalFn(() => {
39425
+ if (!isProcessRunning(parentPid)) {
39426
+ triggerShutdown("parent_dead", { parentPid });
39427
+ }
39428
+ }, parentCheckIntervalMs);
39429
+ parentTimer.unref?.();
39430
+ }
39431
+ return () => {
39432
+ if (disposed)
39433
+ return;
39434
+ disposed = true;
39435
+ options.stdin.off("end", onStreamEnd);
39436
+ options.stdin.off("close", onStreamClose);
39437
+ options.stdin.off("error", onStdinError);
39438
+ options.stdout.off("error", onStdoutError);
39439
+ for (const { signal, listener } of signalListeners) {
39440
+ signalSource.off(signal, listener);
39441
+ }
39442
+ if (parentTimer) {
39443
+ clearIntervalFn(parentTimer);
39444
+ }
39445
+ };
39446
+ }
39447
+ function parseParentCheckIntervalMs(raw) {
39448
+ if (raw === undefined || raw.trim().length === 0)
39449
+ return 5000;
39450
+ const parsed = Number(raw);
39451
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 5000;
39452
+ }
39453
+ function errorContext(error2) {
39454
+ const record3 = error2;
39455
+ return {
39456
+ ...typeof record3?.code === "string" ? { code: record3.code } : {},
39457
+ ...typeof record3?.message === "string" ? { message: record3.message } : {}
39458
+ };
39459
+ }
39460
+ function defaultIsProcessRunning3(pid) {
39461
+ try {
39462
+ process.kill(pid, 0);
39463
+ return true;
39464
+ } catch (error2) {
39465
+ const code = error2?.code;
39466
+ return code !== "ESRCH";
39467
+ }
39468
+ }
39208
39469
  async function runWeacpxMcpServer(options) {
39209
39470
  const transport = options.transport ?? createOrchestrationTransport(options.endpoint ?? resolveDefaultOrchestrationEndpoint(process.env, process.platform));
39210
39471
  const server = createWeacpxMcpServer({
@@ -39215,11 +39476,14 @@ async function runWeacpxMcpServer(options) {
39215
39476
  ...options.availableAgents ? { availableAgents: options.availableAgents } : {}
39216
39477
  });
39217
39478
  const stdio = new StdioServerTransport(stdin, stdout);
39479
+ let cleanupShutdownHooks;
39218
39480
  let shuttingDown = false;
39219
39481
  const shutdown = async () => {
39220
39482
  if (shuttingDown)
39221
39483
  return;
39222
39484
  shuttingDown = true;
39485
+ cleanupShutdownHooks?.();
39486
+ options.onDiagnostic?.("mcp.stdio.stopping");
39223
39487
  const forceExit = setTimeout(() => process.exit(0), 3000);
39224
39488
  forceExit.unref();
39225
39489
  try {
@@ -39227,11 +39491,16 @@ async function runWeacpxMcpServer(options) {
39227
39491
  await stdio.close();
39228
39492
  } catch {}
39229
39493
  clearTimeout(forceExit);
39494
+ options.onDiagnostic?.("mcp.stdio.stopped");
39230
39495
  process.exit(0);
39231
39496
  };
39232
- stdin.on("end", () => void shutdown());
39233
- process.on("SIGTERM", () => void shutdown());
39234
- process.on("SIGINT", () => void shutdown());
39497
+ options.onDiagnostic?.("mcp.stdio.start", { parentPid: process.ppid, platform: process.platform });
39498
+ cleanupShutdownHooks = installMcpStdioShutdownHooks({
39499
+ stdin,
39500
+ stdout,
39501
+ shutdown,
39502
+ onDiagnostic: options.onDiagnostic
39503
+ });
39235
39504
  await server.connect(stdio);
39236
39505
  }
39237
39506
  function normalizeInputSchemaJson(schema) {
@@ -39257,18 +39526,18 @@ function sanitizeMcpClientName(input) {
39257
39526
  return normalized.length > 0 ? normalized : "mcp-host";
39258
39527
  }
39259
39528
 
39260
- // src/mcp/parse-coordinator-workspace.ts
39261
- function parseCoordinatorWorkspace(args, env = process.env) {
39529
+ // src/mcp/parse-string-flag.ts
39530
+ function parseStringFlag(args, env, options) {
39262
39531
  let fromFlag = null;
39263
39532
  for (let index = 0;index < args.length; index += 1) {
39264
- if (args[index] === "--workspace") {
39533
+ if (args[index] === options.flag) {
39265
39534
  const value = args[index + 1];
39266
39535
  if (value === undefined) {
39267
- throw new Error("--workspace requires a non-empty value");
39536
+ throw new Error(`${options.flag} requires a non-empty value`);
39268
39537
  }
39269
39538
  const trimmedValue = value.trim();
39270
39539
  if (trimmedValue.length === 0 || trimmedValue.startsWith("-")) {
39271
- throw new Error("--workspace requires a non-empty value");
39540
+ throw new Error(`${options.flag} requires a non-empty value`);
39272
39541
  }
39273
39542
  fromFlag = value;
39274
39543
  }
@@ -39277,56 +39546,32 @@ function parseCoordinatorWorkspace(args, env = process.env) {
39277
39546
  if (trimmedFlag && trimmedFlag.length > 0) {
39278
39547
  return trimmedFlag;
39279
39548
  }
39280
- const trimmedEnv = env.WEACPX_COORDINATOR_WORKSPACE?.trim();
39549
+ const trimmedEnv = env[options.envKey]?.trim();
39281
39550
  return trimmedEnv && trimmedEnv.length > 0 ? trimmedEnv : null;
39282
39551
  }
39283
39552
 
39553
+ // src/mcp/parse-coordinator-workspace.ts
39554
+ function parseCoordinatorWorkspace(args, env = process.env) {
39555
+ return parseStringFlag(args, env, {
39556
+ flag: "--workspace",
39557
+ envKey: "WEACPX_COORDINATOR_WORKSPACE"
39558
+ });
39559
+ }
39560
+
39284
39561
  // src/mcp/parse-coordinator-session.ts
39285
39562
  function parseCoordinatorSession(args, env = process.env) {
39286
- let fromFlag = null;
39287
- for (let index = 0;index < args.length; index += 1) {
39288
- if (args[index] === "--coordinator-session") {
39289
- const value = args[index + 1];
39290
- if (value === undefined) {
39291
- throw new Error("--coordinator-session requires a non-empty value");
39292
- }
39293
- const trimmedValue = value.trim();
39294
- if (trimmedValue.length === 0 || trimmedValue.startsWith("-")) {
39295
- throw new Error("--coordinator-session requires a non-empty value");
39296
- }
39297
- fromFlag = value;
39298
- }
39299
- }
39300
- const trimmedFlag = fromFlag?.trim();
39301
- if (trimmedFlag && trimmedFlag.length > 0) {
39302
- return trimmedFlag;
39303
- }
39304
- const trimmedEnv = env.WEACPX_COORDINATOR_SESSION?.trim();
39305
- return trimmedEnv && trimmedEnv.length > 0 ? trimmedEnv : null;
39563
+ return parseStringFlag(args, env, {
39564
+ flag: "--coordinator-session",
39565
+ envKey: "WEACPX_COORDINATOR_SESSION"
39566
+ });
39306
39567
  }
39307
39568
 
39308
39569
  // src/mcp/parse-source-handle.ts
39309
39570
  function parseSourceHandle(args, env = process.env) {
39310
- let fromFlag = null;
39311
- for (let index = 0;index < args.length; index += 1) {
39312
- if (args[index] === "--source-handle") {
39313
- const value = args[index + 1];
39314
- if (value === undefined) {
39315
- throw new Error("--source-handle requires a non-empty value");
39316
- }
39317
- const trimmedValue = value.trim();
39318
- if (trimmedValue.length === 0 || trimmedValue.startsWith("-")) {
39319
- throw new Error("--source-handle requires a non-empty value");
39320
- }
39321
- fromFlag = value;
39322
- }
39323
- }
39324
- const trimmedFlag = fromFlag?.trim();
39325
- if (trimmedFlag && trimmedFlag.length > 0) {
39326
- return trimmedFlag;
39327
- }
39328
- const trimmedEnv = env.WEACPX_SOURCE_HANDLE?.trim();
39329
- return trimmedEnv && trimmedEnv.length > 0 ? trimmedEnv : null;
39571
+ return parseStringFlag(args, env, {
39572
+ flag: "--source-handle",
39573
+ envKey: "WEACPX_SOURCE_HANDLE"
39574
+ });
39330
39575
  }
39331
39576
 
39332
39577
  // src/cli.ts
@@ -41023,7 +41268,12 @@ async function defaultMcpStdio(args, deps = {}) {
41023
41268
  ...coordinatorSession ? { coordinatorSession } : {},
41024
41269
  ...sourceHandle ? { sourceHandle } : {},
41025
41270
  ...identityResolver ? { resolveIdentity: identityResolver } : {},
41026
- ...availableAgents ? { availableAgents } : {}
41271
+ ...availableAgents ? { availableAgents } : {},
41272
+ onDiagnostic: (event, context) => {
41273
+ const suffix = context && Object.keys(context).length > 0 ? ` ${JSON.stringify(context)}` : "";
41274
+ (deps.stderr ?? ((text) => process.stderr.write(text)))(`[weacpx:mcp] ${event}${suffix}
41275
+ `);
41276
+ }
41027
41277
  });
41028
41278
  return 0;
41029
41279
  }