weacpx 0.4.0-beta.2 → 0.4.0-beta.3

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.
@@ -56,6 +56,10 @@ function encodeBridgePromptSegmentEvent(event) {
56
56
  return `${JSON.stringify(event)}
57
57
  `;
58
58
  }
59
+ function encodeBridgePromptToolEvent(event) {
60
+ return `${JSON.stringify(event)}
61
+ `;
62
+ }
59
63
  function encodeBridgeSessionProgressEvent(event) {
60
64
  return `${JSON.stringify(event)}
61
65
  `;
@@ -343,8 +347,48 @@ var init_prompt_media = __esm(() => {
343
347
  };
344
348
  });
345
349
 
350
+ // src/transport/tool-event-mode.ts
351
+ function resolveToolEventMode(input) {
352
+ if (input?.toolEventMode !== undefined) {
353
+ return input.toolEventMode;
354
+ }
355
+ if (input?.onToolEvent !== undefined) {
356
+ return "structured";
357
+ }
358
+ return "text";
359
+ }
360
+
361
+ // src/transport/tool-kind-emoji.ts
362
+ var TOOL_KIND_EMOJI, DEFAULT_TOOL_EMOJI;
363
+ var init_tool_kind_emoji = __esm(() => {
364
+ TOOL_KIND_EMOJI = {
365
+ read: "\uD83D\uDCD6",
366
+ search: "\uD83D\uDD0D",
367
+ execute: "\uD83D\uDCBB",
368
+ edit: "✏️",
369
+ think: "\uD83E\uDDE0",
370
+ other: "\uD83D\uDD27"
371
+ };
372
+ DEFAULT_TOOL_EMOJI = TOOL_KIND_EMOJI.other;
373
+ });
374
+
346
375
  // src/transport/streaming-prompt.ts
347
- function createStreamingPromptState(formatToolCalls = false) {
376
+ function createStreamingPromptState(formatToolCalls = false, options) {
377
+ let toolEventMode;
378
+ let onToolEvent;
379
+ if (options === undefined) {
380
+ toolEventMode = "text";
381
+ onToolEvent = undefined;
382
+ } else if (typeof options === "function") {
383
+ onToolEvent = options;
384
+ toolEventMode = "structured";
385
+ } else {
386
+ onToolEvent = options.onToolEvent;
387
+ toolEventMode = resolveToolEventMode({
388
+ toolEventMode: options.mode,
389
+ onToolEvent
390
+ });
391
+ }
348
392
  return {
349
393
  buffer: "",
350
394
  segments: [],
@@ -352,6 +396,8 @@ function createStreamingPromptState(formatToolCalls = false) {
352
396
  pendingLine: "",
353
397
  formatToolCalls,
354
398
  emittedToolCallIds: new Set,
399
+ toolEventMode,
400
+ onToolEvent,
355
401
  finalize() {
356
402
  if (this.pendingLine.trim().length > 0) {
357
403
  parseStreamingChunks(this, this.pendingLine);
@@ -389,15 +435,24 @@ function parseStreamingChunks(state, line) {
389
435
  if (!update)
390
436
  return;
391
437
  if (state.formatToolCalls && (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update")) {
392
- const formatted = formatToolCallEvent(update, update.sessionUpdate);
393
- if (formatted) {
394
- const toolCallId = update.toolCallId;
395
- if (toolCallId) {
396
- if (state.emittedToolCallIds.has(toolCallId))
397
- return;
398
- state.emittedToolCallIds.add(toolCallId);
438
+ const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
439
+ const wantsText = state.toolEventMode === "text" || state.toolEventMode === "both";
440
+ if (wantsStructured && state.onToolEvent) {
441
+ const toolEvent = buildToolUseEvent(update);
442
+ if (toolEvent)
443
+ state.onToolEvent(toolEvent);
444
+ }
445
+ if (wantsText) {
446
+ const formatted = formatToolCallEvent(update, update.sessionUpdate);
447
+ if (formatted) {
448
+ const toolCallId = update.toolCallId;
449
+ if (toolCallId) {
450
+ if (state.emittedToolCallIds.has(toolCallId))
451
+ return;
452
+ state.emittedToolCallIds.add(toolCallId);
453
+ }
454
+ state.segments.push(formatted);
399
455
  }
400
- state.segments.push(formatted);
401
456
  }
402
457
  return;
403
458
  }
@@ -427,16 +482,51 @@ function formatToolCallEvent(update, sessionUpdate) {
427
482
  const title = update.title ?? "";
428
483
  if (title.length === 0)
429
484
  return null;
430
- const emoji = KIND_EMOJI[kind] ?? "\uD83D\uDD27";
431
- const inputSummary = summarizeToolInput(update.rawInput);
485
+ const emoji = TOOL_KIND_EMOJI[kind] ?? DEFAULT_TOOL_EMOJI;
486
+ const inputSummary = summarizeToolInput(update.rawInput, title);
432
487
  const status = readString(update, "status");
488
+ if (!inputSummary && status === "pending")
489
+ return null;
433
490
  if (!inputSummary && isGenericToolTitle(kind, title))
434
491
  return null;
435
- const summaryText = inputSummary ? `: ${truncateToolDisplay(inputSummary)}` : "";
492
+ const summaryText = inputSummary && inputSummary !== title ? `: ${truncateToolDisplay(inputSummary)}` : "";
436
493
  const statusText = status ? ` (${status})` : "";
437
494
  return `${emoji} ${title}${statusText}${summaryText}`;
438
495
  }
439
- function summarizeToolInput(rawInput) {
496
+ function buildToolUseEvent(update) {
497
+ if (!update)
498
+ return null;
499
+ const toolCallId = update.toolCallId;
500
+ if (!toolCallId)
501
+ return null;
502
+ const kindRaw = update.kind ?? "";
503
+ const kind = (() => {
504
+ switch (kindRaw) {
505
+ case "read":
506
+ case "search":
507
+ case "execute":
508
+ case "edit":
509
+ case "think":
510
+ return kindRaw;
511
+ default:
512
+ return "other";
513
+ }
514
+ })();
515
+ const title = (update.title ?? "").trim();
516
+ const toolName = title || "Tool";
517
+ const summaryRaw = summarizeToolInput(update.rawInput, title);
518
+ const summary = summaryRaw && summaryRaw !== title ? summaryRaw : undefined;
519
+ const statusRaw = readString(update, "status");
520
+ const status = statusRaw === "completed" || statusRaw === "success" ? "success" : statusRaw === "failed" || statusRaw === "error" ? "error" : "running";
521
+ return {
522
+ toolCallId,
523
+ toolName,
524
+ kind,
525
+ ...summary ? { summary } : {},
526
+ status
527
+ };
528
+ }
529
+ function summarizeToolInput(rawInput, title = "") {
440
530
  if (rawInput == null)
441
531
  return;
442
532
  if (typeof rawInput === "string" || typeof rawInput === "number" || typeof rawInput === "boolean") {
@@ -444,6 +534,9 @@ function summarizeToolInput(rawInput) {
444
534
  }
445
535
  if (!isRecord(rawInput))
446
536
  return;
537
+ const taskSummary = summarizeTaskInput(rawInput, title);
538
+ if (taskSummary)
539
+ return taskSummary;
447
540
  const command = readFirstString(rawInput, ["command", "cmd", "program"]);
448
541
  const args = readFirstStringArray(rawInput, ["args", "arguments"]);
449
542
  if (command) {
@@ -466,6 +559,7 @@ function summarizeToolInput(rawInput) {
466
559
  "file",
467
560
  "filePath",
468
561
  "filepath",
562
+ "file_path",
469
563
  "target",
470
564
  "uri",
471
565
  "url",
@@ -477,6 +571,16 @@ function summarizeToolInput(rawInput) {
477
571
  "description"
478
572
  ]);
479
573
  }
574
+ function summarizeTaskInput(rawInput, title) {
575
+ const subagentType = readFirstString(rawInput, ["subagent_type", "subagentType", "agent", "agentType"]);
576
+ const description = readFirstString(rawInput, ["description", "task", "summary"]);
577
+ if (subagentType && description) {
578
+ return description === title ? subagentType : `${subagentType}: ${description}`;
579
+ }
580
+ if (subagentType)
581
+ return subagentType;
582
+ return;
583
+ }
480
584
  function readFirstString(record, keys) {
481
585
  for (const key of keys) {
482
586
  const value = record[key];
@@ -523,14 +627,8 @@ function isGenericToolTitle(kind, title) {
523
627
  }
524
628
  return false;
525
629
  }
526
- var KIND_EMOJI;
527
630
  var init_streaming_prompt = __esm(() => {
528
- KIND_EMOJI = {
529
- read: "\uD83D\uDCD6",
530
- search: "\uD83D\uDD0D",
531
- execute: "\uD83D\uDCBB",
532
- edit: "✏️"
533
- };
631
+ init_tool_kind_emoji();
534
632
  });
535
633
 
536
634
  // src/recovery/discover-parent-package-paths.ts
@@ -1123,8 +1221,12 @@ class BridgeRuntime {
1123
1221
  ...structuredPrompt ? ["--file", structuredPrompt.filePath] : [input.text]
1124
1222
  ]));
1125
1223
  const formatToolCalls = (input.replyMode ?? "verbose") === "verbose";
1224
+ const toolEventMode = input.toolEventMode ?? (input.toolEvents === true ? "structured" : "text");
1126
1225
  try {
1127
- const result = onEvent ? await this.runPromptCommand(spawnSpec.command, spawnSpec.args, onEvent, { formatToolCalls }) : await this.run(spawnSpec.command, spawnSpec.args);
1226
+ const result = onEvent ? await this.runPromptCommand(spawnSpec.command, spawnSpec.args, onEvent, {
1227
+ formatToolCalls,
1228
+ toolEventMode
1229
+ }) : await this.run(spawnSpec.command, spawnSpec.args);
1128
1230
  return { text: getPromptText(result) };
1129
1231
  } finally {
1130
1232
  try {
@@ -1303,7 +1405,11 @@ async function runStreamingPrompt(command, args, onEvent, options = {}) {
1303
1405
  const child = spawnPrompt(command, args);
1304
1406
  let stdout = "";
1305
1407
  let stderr = "";
1306
- const state = createStreamingPromptState(options.formatToolCalls ?? false);
1408
+ const toolEventMode = options.toolEventMode ?? "text";
1409
+ const state = createStreamingPromptState(options.formatToolCalls ?? false, {
1410
+ mode: toolEventMode,
1411
+ ...onEvent && (toolEventMode === "structured" || toolEventMode === "both") ? { onToolEvent: (toolEvent) => onEvent({ type: "prompt.tool_event", event: toolEvent }) } : {}
1412
+ });
1307
1413
  let lastReplyAt = now();
1308
1414
  const flushBuffer = () => {
1309
1415
  const remaining = state.buffer.trim();
@@ -1525,6 +1631,7 @@ class BridgeServer {
1525
1631
  });
1526
1632
  case "prompt":
1527
1633
  const media = asOptionalPromptMediaInput(params.media);
1634
+ const resolvedToolEventMode = asOptionalToolEventMode(params.toolEventMode);
1528
1635
  return await this.runtime.prompt({
1529
1636
  agent: requireString(params, "agent"),
1530
1637
  agentCommand: asOptionalString(params.agentCommand),
@@ -1534,6 +1641,8 @@ class BridgeServer {
1534
1641
  mcpSourceHandle: asOptionalString(params.mcpSourceHandle),
1535
1642
  text: requirePromptText(params, media),
1536
1643
  replyMode: asOptionalReplyMode(params.replyMode),
1644
+ toolEvents: params.toolEvents === true,
1645
+ ...resolvedToolEventMode ? { toolEventMode: resolvedToolEventMode } : {},
1537
1646
  media
1538
1647
  }, (event) => {
1539
1648
  if (event.type === "prompt.segment") {
@@ -1542,6 +1651,12 @@ class BridgeServer {
1542
1651
  event: "prompt.segment",
1543
1652
  text: event.text
1544
1653
  }));
1654
+ } else if (event.type === "prompt.tool_event") {
1655
+ writeLine?.(encodeBridgePromptToolEvent({
1656
+ id: requestId,
1657
+ event: "prompt.tool_event",
1658
+ toolEvent: event.event
1659
+ }));
1545
1660
  }
1546
1661
  });
1547
1662
  case "setMode":
@@ -1707,6 +1822,13 @@ function asOptionalReplyMode(value) {
1707
1822
  }
1708
1823
  return value;
1709
1824
  }
1825
+ var VALID_TOOL_EVENT_MODES = new Set(["text", "structured", "both"]);
1826
+ function asOptionalToolEventMode(value) {
1827
+ if (typeof value !== "string" || !VALID_TOOL_EVENT_MODES.has(value)) {
1828
+ return;
1829
+ }
1830
+ return value;
1831
+ }
1710
1832
 
1711
1833
  // src/bridge/bridge-main.ts
1712
1834
  async function processBridgeInput(options) {
@@ -59,3 +59,17 @@ export interface MessageChannelRuntime {
59
59
  notifyTaskProgress(task: OrchestrationTaskRecord, text: string): Promise<void>;
60
60
  sendCoordinatorMessage(input: CoordinatorMessageInput): Promise<void>;
61
61
  }
62
+ export type ToolUseStatus = "running" | "success" | "error";
63
+ export type ToolUseKind = "read" | "search" | "execute" | "edit" | "think" | "other";
64
+ export interface ToolUseEvent {
65
+ toolCallId: string;
66
+ /** Free-form tool name from the agent (e.g. "Read File", "Bash"). */
67
+ toolName: string;
68
+ /** Coarse classifier produced by the transport from the agent's tool kind; channels use it to pick an icon. */
69
+ kind: ToolUseKind;
70
+ /** Best-effort one-line summary derived from `rawInput`. */
71
+ summary?: string;
72
+ status: ToolUseStatus;
73
+ /** Set when status transitions out of "running". */
74
+ durationMs?: number;
75
+ }
package/dist/cli.js CHANGED
@@ -14153,7 +14153,7 @@ import { dirname as dirname6, join as join3 } from "node:path";
14153
14153
  import { homedir as homedir3 } from "node:os";
14154
14154
  function createWeixinConsumerLock(options = {}) {
14155
14155
  const lockFilePath = options.lockFilePath ?? join3(homedir3(), ".weacpx", "runtime", "weixin-consumer.lock.json");
14156
- const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning3;
14156
+ const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning4;
14157
14157
  const onDiagnostic = options.onDiagnostic;
14158
14158
  return {
14159
14159
  async acquire(meta2) {
@@ -14240,7 +14240,7 @@ async function loadLockMetadata(path13) {
14240
14240
  return null;
14241
14241
  }
14242
14242
  }
14243
- function defaultIsProcessRunning3(pid) {
14243
+ function defaultIsProcessRunning4(pid) {
14244
14244
  try {
14245
14245
  process.kill(pid, 0);
14246
14246
  return true;
@@ -16569,7 +16569,7 @@ async function handleSessionRemove(context, chatKey, alias) {
16569
16569
  return { text: lines.join(`
16570
16570
  `) };
16571
16571
  }
16572
- async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal) {
16572
+ async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent) {
16573
16573
  const effectiveReplyMode = session.replyMode ?? context.config?.channel.replyMode ?? "verbose";
16574
16574
  if (!session.replyMode)
16575
16575
  session.replyMode = effectiveReplyMode;
@@ -16594,7 +16594,7 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
16594
16594
  const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId);
16595
16595
  try {
16596
16596
  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);
16597
+ const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext, media, abortSignal, onToolEvent);
16598
16598
  if (claimHumanReply) {
16599
16599
  try {
16600
16600
  await context.orchestration?.claimActiveHumanReply?.(claimHumanReply);
@@ -16614,17 +16614,17 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
16614
16614
  throw error2;
16615
16615
  }
16616
16616
  }
16617
- async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal) {
16617
+ async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent) {
16618
16618
  const session = await context.sessions.getCurrentSession(chatKey);
16619
16619
  if (!session) {
16620
16620
  return { text: NO_CURRENT_SESSION_TEXT };
16621
16621
  }
16622
16622
  try {
16623
- return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal);
16623
+ return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
16624
16624
  } catch (error2) {
16625
16625
  const recovered = await context.recovery.tryRecoverMissingSession(session, error2);
16626
16626
  if (recovered) {
16627
- return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal);
16627
+ return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
16628
16628
  }
16629
16629
  return context.recovery.renderTransportError(session, error2);
16630
16630
  }
@@ -18150,7 +18150,7 @@ class CommandRouter {
18150
18150
  this.quota = quota;
18151
18151
  this.logger = logger2 ?? createNoopAppLogger();
18152
18152
  }
18153
- async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal) {
18153
+ async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent) {
18154
18154
  const startedAt = Date.now();
18155
18155
  const command = parseCommand(input);
18156
18156
  await this.logger.debug("command.parsed", "parsed inbound command", {
@@ -18264,7 +18264,7 @@ class CommandRouter {
18264
18264
  case "task.cancel":
18265
18265
  return await handleTaskCancel(this.createHandlerContext(), chatKey, command.taskId);
18266
18266
  case "prompt":
18267
- return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal);
18267
+ return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
18268
18268
  }
18269
18269
  });
18270
18270
  }
@@ -18316,7 +18316,7 @@ class CommandRouter {
18316
18316
  return {
18317
18317
  setModeTransportSession: (session, modeId) => this.setModeTransportSession(session, modeId),
18318
18318
  cancelTransportSession: (session) => this.cancelTransportSession(session),
18319
- promptTransportSession: (session, text, reply, replyContext, media, abortSignal) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal)
18319
+ promptTransportSession: (session, text, reply, replyContext, media, abortSignal, onToolEvent) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent)
18320
18320
  };
18321
18321
  }
18322
18322
  createSessionRenderRecoveryOps() {
@@ -18479,7 +18479,7 @@ class CommandRouter {
18479
18479
  async checkTransportSession(session) {
18480
18480
  return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
18481
18481
  }
18482
- async promptTransportSession(session, text, reply, replyContext, media, abortSignal) {
18482
+ async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent) {
18483
18483
  session.mcpCoordinatorSession ??= session.transportSession;
18484
18484
  let done = false;
18485
18485
  let cancelOnAbort;
@@ -18516,7 +18516,10 @@ class CommandRouter {
18516
18516
  abortSignal.addEventListener("abort", cancelOnAbort, { once: true });
18517
18517
  }
18518
18518
  try {
18519
- return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext, media ? { media } : undefined));
18519
+ return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext, {
18520
+ ...media ? { media } : {},
18521
+ ...onToolEvent ? { onToolEvent } : {}
18522
+ }));
18520
18523
  } finally {
18521
18524
  done = true;
18522
18525
  if (cancelOnAbort && abortSignal) {
@@ -18667,7 +18670,7 @@ class ConsoleAgent {
18667
18670
  mimeType: m.mimeType,
18668
18671
  ...m.fileName ? { fileName: m.fileName } : {}
18669
18672
  })) : undefined;
18670
- return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal);
18673
+ return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent);
18671
18674
  }
18672
18675
  isKnownCommand(text) {
18673
18676
  return isKnownWeacpxCommandText(text);
@@ -22767,6 +22770,10 @@ function encodeBridgePromptSegmentEvent(event) {
22767
22770
  return `${JSON.stringify(event)}
22768
22771
  `;
22769
22772
  }
22773
+ function encodeBridgePromptToolEvent(event) {
22774
+ return `${JSON.stringify(event)}
22775
+ `;
22776
+ }
22770
22777
  function encodeBridgeSessionProgressEvent(event) {
22771
22778
  return `${JSON.stringify(event)}
22772
22779
  `;
@@ -22834,6 +22841,11 @@ class AcpxBridgeClient {
22834
22841
  type: "prompt.segment",
22835
22842
  text: message.text
22836
22843
  });
22844
+ } else if (message.event === "prompt.tool_event") {
22845
+ pending.onEvent?.({
22846
+ type: "prompt.tool_event",
22847
+ event: message.toolEvent
22848
+ });
22837
22849
  } else if (message.event === "session.progress") {
22838
22850
  pending.onEvent?.({
22839
22851
  type: "session.progress",
@@ -23157,6 +23169,17 @@ var init_quota_gated_reply_sink = __esm(() => {
23157
23169
  ADAPTIVE_WINDOW_SCHEDULE_MS = [3000, 6000, 12000, 24000, 48000, 60000];
23158
23170
  });
23159
23171
 
23172
+ // src/transport/tool-event-mode.ts
23173
+ function resolveToolEventMode(input) {
23174
+ if (input?.toolEventMode !== undefined) {
23175
+ return input.toolEventMode;
23176
+ }
23177
+ if (input?.onToolEvent !== undefined) {
23178
+ return "structured";
23179
+ }
23180
+ return "text";
23181
+ }
23182
+
23160
23183
  // src/transport/acpx-bridge/acpx-bridge-transport.ts
23161
23184
  class AcpxBridgeTransport {
23162
23185
  client;
@@ -23179,10 +23202,18 @@ class AcpxBridgeTransport {
23179
23202
  }) : null;
23180
23203
  let segmentError;
23181
23204
  let segmentChain = Promise.resolve();
23205
+ let toolEventError;
23206
+ let toolEventChain = Promise.resolve();
23207
+ let toolEventMode = resolveToolEventMode(options);
23208
+ if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
23209
+ toolEventMode = "text";
23210
+ }
23182
23211
  const result = await this.client.request("prompt", {
23183
23212
  ...this.toParams(session),
23184
23213
  text,
23185
- ...options?.media ? { media: options.media } : {}
23214
+ ...options?.media ? { media: options.media } : {},
23215
+ ...toolEventMode === "structured" || toolEventMode === "both" ? { toolEvents: true } : {},
23216
+ toolEventMode
23186
23217
  }, (event) => {
23187
23218
  if (event.type === "prompt.segment") {
23188
23219
  const onSegment = options?.onSegment;
@@ -23193,9 +23224,21 @@ class AcpxBridgeTransport {
23193
23224
  });
23194
23225
  }
23195
23226
  sink?.feedSegment(event.text);
23227
+ return;
23228
+ }
23229
+ if (event.type === "prompt.tool_event") {
23230
+ const onToolEvent = options?.onToolEvent;
23231
+ if (onToolEvent) {
23232
+ const toolEvent = event.event;
23233
+ toolEventChain = toolEventChain.then(() => onToolEvent(toolEvent)).catch((error2) => {
23234
+ toolEventError ??= error2;
23235
+ });
23236
+ }
23237
+ return;
23196
23238
  }
23197
23239
  });
23198
23240
  await segmentChain;
23241
+ await toolEventChain;
23199
23242
  if (sink) {
23200
23243
  const { overflowCount } = sink.finalize();
23201
23244
  await sink.drain({ timeoutMs: 30000 });
@@ -23207,6 +23250,9 @@ class AcpxBridgeTransport {
23207
23250
  if (segmentError) {
23208
23251
  throw segmentError;
23209
23252
  }
23253
+ if (toolEventError) {
23254
+ throw toolEventError;
23255
+ }
23210
23256
  return { text: summary ? `${summary}
23211
23257
 
23212
23258
  ${result.text}` : "" };
@@ -23214,6 +23260,9 @@ ${result.text}` : "" };
23214
23260
  if (segmentError) {
23215
23261
  throw segmentError;
23216
23262
  }
23263
+ if (toolEventError) {
23264
+ throw toolEventError;
23265
+ }
23217
23266
  return result;
23218
23267
  }
23219
23268
  async setMode(session, modeId) {
@@ -23400,8 +23449,37 @@ var init_prompt_media = __esm(() => {
23400
23449
  };
23401
23450
  });
23402
23451
 
23452
+ // src/transport/tool-kind-emoji.ts
23453
+ var TOOL_KIND_EMOJI, DEFAULT_TOOL_EMOJI;
23454
+ var init_tool_kind_emoji = __esm(() => {
23455
+ TOOL_KIND_EMOJI = {
23456
+ read: "\uD83D\uDCD6",
23457
+ search: "\uD83D\uDD0D",
23458
+ execute: "\uD83D\uDCBB",
23459
+ edit: "✏️",
23460
+ think: "\uD83E\uDDE0",
23461
+ other: "\uD83D\uDD27"
23462
+ };
23463
+ DEFAULT_TOOL_EMOJI = TOOL_KIND_EMOJI.other;
23464
+ });
23465
+
23403
23466
  // src/transport/streaming-prompt.ts
23404
- function createStreamingPromptState(formatToolCalls = false) {
23467
+ function createStreamingPromptState(formatToolCalls = false, options) {
23468
+ let toolEventMode;
23469
+ let onToolEvent;
23470
+ if (options === undefined) {
23471
+ toolEventMode = "text";
23472
+ onToolEvent = undefined;
23473
+ } else if (typeof options === "function") {
23474
+ onToolEvent = options;
23475
+ toolEventMode = "structured";
23476
+ } else {
23477
+ onToolEvent = options.onToolEvent;
23478
+ toolEventMode = resolveToolEventMode({
23479
+ toolEventMode: options.mode,
23480
+ onToolEvent
23481
+ });
23482
+ }
23405
23483
  return {
23406
23484
  buffer: "",
23407
23485
  segments: [],
@@ -23409,6 +23487,8 @@ function createStreamingPromptState(formatToolCalls = false) {
23409
23487
  pendingLine: "",
23410
23488
  formatToolCalls,
23411
23489
  emittedToolCallIds: new Set,
23490
+ toolEventMode,
23491
+ onToolEvent,
23412
23492
  finalize() {
23413
23493
  if (this.pendingLine.trim().length > 0) {
23414
23494
  parseStreamingChunks(this, this.pendingLine);
@@ -23446,15 +23526,24 @@ function parseStreamingChunks(state, line) {
23446
23526
  if (!update)
23447
23527
  return;
23448
23528
  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);
23529
+ const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
23530
+ const wantsText = state.toolEventMode === "text" || state.toolEventMode === "both";
23531
+ if (wantsStructured && state.onToolEvent) {
23532
+ const toolEvent = buildToolUseEvent(update);
23533
+ if (toolEvent)
23534
+ state.onToolEvent(toolEvent);
23535
+ }
23536
+ if (wantsText) {
23537
+ const formatted = formatToolCallEvent(update, update.sessionUpdate);
23538
+ if (formatted) {
23539
+ const toolCallId = update.toolCallId;
23540
+ if (toolCallId) {
23541
+ if (state.emittedToolCallIds.has(toolCallId))
23542
+ return;
23543
+ state.emittedToolCallIds.add(toolCallId);
23544
+ }
23545
+ state.segments.push(formatted);
23456
23546
  }
23457
- state.segments.push(formatted);
23458
23547
  }
23459
23548
  return;
23460
23549
  }
@@ -23484,16 +23573,51 @@ function formatToolCallEvent(update, sessionUpdate) {
23484
23573
  const title = update.title ?? "";
23485
23574
  if (title.length === 0)
23486
23575
  return null;
23487
- const emoji2 = KIND_EMOJI[kind] ?? "\uD83D\uDD27";
23488
- const inputSummary = summarizeToolInput(update.rawInput);
23576
+ const emoji2 = TOOL_KIND_EMOJI[kind] ?? DEFAULT_TOOL_EMOJI;
23577
+ const inputSummary = summarizeToolInput(update.rawInput, title);
23489
23578
  const status = readString(update, "status");
23579
+ if (!inputSummary && status === "pending")
23580
+ return null;
23490
23581
  if (!inputSummary && isGenericToolTitle(kind, title))
23491
23582
  return null;
23492
- const summaryText = inputSummary ? `: ${truncateToolDisplay(inputSummary)}` : "";
23583
+ const summaryText = inputSummary && inputSummary !== title ? `: ${truncateToolDisplay(inputSummary)}` : "";
23493
23584
  const statusText = status ? ` (${status})` : "";
23494
23585
  return `${emoji2} ${title}${statusText}${summaryText}`;
23495
23586
  }
23496
- function summarizeToolInput(rawInput) {
23587
+ function buildToolUseEvent(update) {
23588
+ if (!update)
23589
+ return null;
23590
+ const toolCallId = update.toolCallId;
23591
+ if (!toolCallId)
23592
+ return null;
23593
+ const kindRaw = update.kind ?? "";
23594
+ const kind = (() => {
23595
+ switch (kindRaw) {
23596
+ case "read":
23597
+ case "search":
23598
+ case "execute":
23599
+ case "edit":
23600
+ case "think":
23601
+ return kindRaw;
23602
+ default:
23603
+ return "other";
23604
+ }
23605
+ })();
23606
+ const title = (update.title ?? "").trim();
23607
+ const toolName = title || "Tool";
23608
+ const summaryRaw = summarizeToolInput(update.rawInput, title);
23609
+ const summary = summaryRaw && summaryRaw !== title ? summaryRaw : undefined;
23610
+ const statusRaw = readString(update, "status");
23611
+ const status = statusRaw === "completed" || statusRaw === "success" ? "success" : statusRaw === "failed" || statusRaw === "error" ? "error" : "running";
23612
+ return {
23613
+ toolCallId,
23614
+ toolName,
23615
+ kind,
23616
+ ...summary ? { summary } : {},
23617
+ status
23618
+ };
23619
+ }
23620
+ function summarizeToolInput(rawInput, title = "") {
23497
23621
  if (rawInput == null)
23498
23622
  return;
23499
23623
  if (typeof rawInput === "string" || typeof rawInput === "number" || typeof rawInput === "boolean") {
@@ -23501,6 +23625,9 @@ function summarizeToolInput(rawInput) {
23501
23625
  }
23502
23626
  if (!isRecord3(rawInput))
23503
23627
  return;
23628
+ const taskSummary = summarizeTaskInput(rawInput, title);
23629
+ if (taskSummary)
23630
+ return taskSummary;
23504
23631
  const command = readFirstString(rawInput, ["command", "cmd", "program"]);
23505
23632
  const args = readFirstStringArray(rawInput, ["args", "arguments"]);
23506
23633
  if (command) {
@@ -23523,6 +23650,7 @@ function summarizeToolInput(rawInput) {
23523
23650
  "file",
23524
23651
  "filePath",
23525
23652
  "filepath",
23653
+ "file_path",
23526
23654
  "target",
23527
23655
  "uri",
23528
23656
  "url",
@@ -23534,6 +23662,16 @@ function summarizeToolInput(rawInput) {
23534
23662
  "description"
23535
23663
  ]);
23536
23664
  }
23665
+ function summarizeTaskInput(rawInput, title) {
23666
+ const subagentType = readFirstString(rawInput, ["subagent_type", "subagentType", "agent", "agentType"]);
23667
+ const description = readFirstString(rawInput, ["description", "task", "summary"]);
23668
+ if (subagentType && description) {
23669
+ return description === title ? subagentType : `${subagentType}: ${description}`;
23670
+ }
23671
+ if (subagentType)
23672
+ return subagentType;
23673
+ return;
23674
+ }
23537
23675
  function readFirstString(record3, keys) {
23538
23676
  for (const key of keys) {
23539
23677
  const value = record3[key];
@@ -23580,14 +23718,8 @@ function isGenericToolTitle(kind, title) {
23580
23718
  }
23581
23719
  return false;
23582
23720
  }
23583
- var KIND_EMOJI;
23584
23721
  var init_streaming_prompt = __esm(() => {
23585
- KIND_EMOJI = {
23586
- read: "\uD83D\uDCD6",
23587
- search: "\uD83D\uDD0D",
23588
- execute: "\uD83D\uDCBB",
23589
- edit: "✏️"
23590
- };
23722
+ init_tool_kind_emoji();
23591
23723
  });
23592
23724
 
23593
23725
  // src/transport/acpx-cli/node-pty-helper.ts
@@ -23897,7 +24029,8 @@ class AcpxCliTransport {
23897
24029
  runCommand;
23898
24030
  runPtyCommand;
23899
24031
  queueOwnerLauncher;
23900
- constructor(options, runCommand = defaultRunner, runPtyCommand = defaultPtyRunner, queueOwnerLauncher) {
24032
+ streamingHooks;
24033
+ constructor(options, runCommand = defaultRunner, runPtyCommand = defaultPtyRunner, queueOwnerLauncher, streamingHooks = {}) {
23901
24034
  this.command = options.command ?? "acpx";
23902
24035
  this.sessionInitTimeoutMs = options.sessionInitTimeoutMs ?? 120000;
23903
24036
  this.permissionMode = options.permissionMode ?? "approve-all";
@@ -23907,6 +24040,7 @@ class AcpxCliTransport {
23907
24040
  this.queueOwnerLauncher = queueOwnerLauncher ?? new AcpxQueueOwnerLauncher({
23908
24041
  acpxCommand: this.command
23909
24042
  });
24043
+ this.streamingHooks = streamingHooks;
23910
24044
  }
23911
24045
  async ensureSession(session, _onProgress) {
23912
24046
  const args = this.buildArgs(session, [
@@ -23925,9 +24059,13 @@ class AcpxCliTransport {
23925
24059
  const structuredPrompt = await createStructuredPromptFile(text, options?.media);
23926
24060
  const args = this.buildPromptArgs(session, text, structuredPrompt?.filePath);
23927
24061
  try {
23928
- if (reply || options?.onSegment) {
24062
+ if (reply || options?.onSegment || options?.onToolEvent) {
23929
24063
  const formatToolCalls = (session.replyMode ?? "verbose") === "verbose";
23930
- const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, 30000, formatToolCalls, replyContext, options?.onSegment);
24064
+ let toolEventMode = resolveToolEventMode(options);
24065
+ if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
24066
+ toolEventMode = "text";
24067
+ }
24068
+ const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, formatToolCalls, toolEventMode, replyContext, options?.onSegment, options?.onToolEvent);
23931
24069
  const baseText = getPromptText(result2);
23932
24070
  if (!reply) {
23933
24071
  return { text: baseText };
@@ -24064,16 +24202,35 @@ ${baseText}` : "" };
24064
24202
  })
24065
24203
  ]);
24066
24204
  }
24067
- async runStreamingPrompt(command, args, reply, maxSegmentWaitMs = 30000, formatToolCalls = false, replyContext, onSegment) {
24205
+ async runStreamingPrompt(command, args, reply, formatToolCalls = false, toolEventMode = "text", replyContext, onSegment, onToolEvent) {
24206
+ const hooks = this.streamingHooks;
24207
+ const doSpawn = hooks.spawnPrompt ?? ((cmd, spawnArgs) => spawn8(cmd, spawnArgs, { stdio: ["ignore", "pipe", "pipe"] }));
24208
+ const setIntervalFn = hooks.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
24209
+ const clearIntervalFn = hooks.clearIntervalFn ?? ((timer) => clearInterval(timer));
24210
+ const maxSegmentWaitMs = hooks.maxSegmentWaitMs ?? 30000;
24211
+ const flushCheckIntervalMs = hooks.flushCheckIntervalMs ?? 5000;
24212
+ const now = hooks.now ?? (() => Date.now());
24068
24213
  return await new Promise((resolve3, reject) => {
24069
24214
  const spawnSpec = resolveSpawnCommand(command, args);
24070
- const child = spawn8(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
24215
+ const child = doSpawn(spawnSpec.command, spawnSpec.args);
24071
24216
  let stdout2 = "";
24072
24217
  let stderr = "";
24073
- const state = createStreamingPromptState(formatToolCalls);
24074
- let lastReplyAt = Date.now();
24218
+ let lastReplyAt = now();
24075
24219
  let segmentChain = Promise.resolve();
24076
24220
  let segmentError;
24221
+ let toolEventChain = Promise.resolve();
24222
+ let toolEventError;
24223
+ const userOnToolEvent = onToolEvent;
24224
+ const state = createStreamingPromptState(formatToolCalls, {
24225
+ mode: toolEventMode,
24226
+ ...userOnToolEvent ? {
24227
+ onToolEvent: (event) => {
24228
+ toolEventChain = toolEventChain.then(() => userOnToolEvent(event)).catch((error2) => {
24229
+ toolEventError ??= error2;
24230
+ });
24231
+ }
24232
+ } : {}
24233
+ });
24077
24234
  const sink = reply ? createQuotaGatedReplySink({
24078
24235
  reply,
24079
24236
  ...replyContext ? { replyContext } : {}
@@ -24085,7 +24242,7 @@ ${baseText}` : "" };
24085
24242
  });
24086
24243
  }
24087
24244
  sink?.feedSegment(segment);
24088
- lastReplyAt = Date.now();
24245
+ lastReplyAt = now();
24089
24246
  };
24090
24247
  const flushBuffer = () => {
24091
24248
  const remaining = state.buffer.trim();
@@ -24094,11 +24251,11 @@ ${baseText}` : "" };
24094
24251
  feedSegment(remaining);
24095
24252
  }
24096
24253
  };
24097
- const timer = setInterval(() => {
24098
- if (state.buffer.trim().length > 0 && Date.now() - lastReplyAt >= maxSegmentWaitMs) {
24254
+ const timer = setIntervalFn(() => {
24255
+ if (state.buffer.trim().length > 0 && now() - lastReplyAt >= maxSegmentWaitMs) {
24099
24256
  flushBuffer();
24100
24257
  }
24101
- }, 5000);
24258
+ }, flushCheckIntervalMs);
24102
24259
  child.stdout.setEncoding("utf8");
24103
24260
  child.stdout.on("data", (chunk) => {
24104
24261
  stdout2 += String(chunk);
@@ -24111,11 +24268,11 @@ ${baseText}` : "" };
24111
24268
  stderr += String(chunk);
24112
24269
  });
24113
24270
  child.on("error", (err) => {
24114
- clearInterval(timer);
24271
+ clearIntervalFn(timer);
24115
24272
  reject(err);
24116
24273
  });
24117
24274
  child.on("close", (code) => {
24118
- clearInterval(timer);
24275
+ clearIntervalFn(timer);
24119
24276
  const remaining = state.finalize();
24120
24277
  if (remaining.length > 0) {
24121
24278
  feedSegment(remaining);
@@ -24123,7 +24280,8 @@ ${baseText}` : "" };
24123
24280
  const { overflowCount } = sink?.finalize() ?? { overflowCount: 0 };
24124
24281
  Promise.all([
24125
24282
  sink?.drain({ timeoutMs: 30000 }) ?? Promise.resolve(),
24126
- segmentChain
24283
+ segmentChain,
24284
+ toolEventChain
24127
24285
  ]).then(() => {
24128
24286
  const deferred = sink?.getPendingError();
24129
24287
  if (deferred) {
@@ -24134,6 +24292,10 @@ ${baseText}` : "" };
24134
24292
  reject(segmentError);
24135
24293
  return;
24136
24294
  }
24295
+ if (toolEventError) {
24296
+ reject(toolEventError);
24297
+ return;
24298
+ }
24137
24299
  resolve3({
24138
24300
  result: { code: code ?? 1, stdout: stdout2, stderr },
24139
24301
  overflowCount
@@ -25120,7 +25282,7 @@ async function checkDaemon(options = {}) {
25120
25282
  cliEntryPath: options.cliEntryPath ?? resolveCliEntryPath(),
25121
25283
  cwd: options.cwd ?? process.cwd(),
25122
25284
  env: options.env ?? process.env,
25123
- isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning4
25285
+ isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning5
25124
25286
  });
25125
25287
  try {
25126
25288
  const status = await controller.getStatus();
@@ -25191,7 +25353,7 @@ async function checkDaemon(options = {}) {
25191
25353
  };
25192
25354
  }
25193
25355
  }
25194
- function defaultIsProcessRunning4(pid) {
25356
+ function defaultIsProcessRunning5(pid) {
25195
25357
  try {
25196
25358
  process.kill(pid, 0);
25197
25359
  return true;
@@ -39205,6 +39367,82 @@ async function resolveMcpIdentity(server, options) {
39205
39367
  }
39206
39368
  throw new McpError(ErrorCode.InvalidRequest, "weacpx MCP identity is not configured; run through `weacpx mcp-stdio` or provide --coordinator-session");
39207
39369
  }
39370
+ function installMcpStdioShutdownHooks(options) {
39371
+ const platform = options.platform ?? process.platform;
39372
+ const signalSource = options.signalSource ?? process;
39373
+ const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning3;
39374
+ const setIntervalFn = options.setIntervalFn ?? ((callback, ms) => setInterval(callback, ms));
39375
+ const clearIntervalFn = options.clearIntervalFn ?? ((handle) => clearInterval(handle));
39376
+ const parentPid = options.parentPid ?? process.ppid;
39377
+ const parentCheckIntervalMs = options.parentCheckIntervalMs ?? parseParentCheckIntervalMs(process.env.WEACPX_MCP_PARENT_CHECK_INTERVAL_MS);
39378
+ let disposed = false;
39379
+ const triggerShutdown = (reason, context) => {
39380
+ if (disposed)
39381
+ return;
39382
+ options.onDiagnostic?.("mcp.stdio.shutdown", { reason, ...context ?? {} });
39383
+ options.shutdown();
39384
+ };
39385
+ const onStreamEnd = () => triggerShutdown("stdin.end");
39386
+ const onStreamClose = () => triggerShutdown("stdin.close");
39387
+ const onStdinError = (error2) => triggerShutdown("stdin.error", errorContext(error2));
39388
+ const onStdoutError = (error2) => triggerShutdown("stdout.error", errorContext(error2));
39389
+ const onSignal = (signal) => triggerShutdown("signal", { signal });
39390
+ options.stdin.on("end", onStreamEnd);
39391
+ options.stdin.on("close", onStreamClose);
39392
+ options.stdin.on("error", onStdinError);
39393
+ options.stdout.on("error", onStdoutError);
39394
+ const signals = platform === "win32" ? ["SIGINT", "SIGTERM", "SIGBREAK"] : ["SIGINT", "SIGTERM", "SIGHUP"];
39395
+ const signalListeners = signals.map((signal) => ({ signal, listener: () => onSignal(signal) }));
39396
+ for (const { signal, listener } of signalListeners) {
39397
+ signalSource.on(signal, listener);
39398
+ }
39399
+ let parentTimer;
39400
+ if (parentPid > 1 && parentCheckIntervalMs > 0) {
39401
+ parentTimer = setIntervalFn(() => {
39402
+ if (!isProcessRunning(parentPid)) {
39403
+ triggerShutdown("parent_dead", { parentPid });
39404
+ }
39405
+ }, parentCheckIntervalMs);
39406
+ parentTimer.unref?.();
39407
+ }
39408
+ return () => {
39409
+ if (disposed)
39410
+ return;
39411
+ disposed = true;
39412
+ options.stdin.off("end", onStreamEnd);
39413
+ options.stdin.off("close", onStreamClose);
39414
+ options.stdin.off("error", onStdinError);
39415
+ options.stdout.off("error", onStdoutError);
39416
+ for (const { signal, listener } of signalListeners) {
39417
+ signalSource.off(signal, listener);
39418
+ }
39419
+ if (parentTimer) {
39420
+ clearIntervalFn(parentTimer);
39421
+ }
39422
+ };
39423
+ }
39424
+ function parseParentCheckIntervalMs(raw) {
39425
+ if (raw === undefined || raw.trim().length === 0)
39426
+ return 5000;
39427
+ const parsed = Number(raw);
39428
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 5000;
39429
+ }
39430
+ function errorContext(error2) {
39431
+ const record3 = error2;
39432
+ return {
39433
+ ...typeof record3?.code === "string" ? { code: record3.code } : {},
39434
+ ...typeof record3?.message === "string" ? { message: record3.message } : {}
39435
+ };
39436
+ }
39437
+ function defaultIsProcessRunning3(pid) {
39438
+ try {
39439
+ process.kill(pid, 0);
39440
+ return true;
39441
+ } catch (error2) {
39442
+ const code = error2?.code;
39443
+ return code !== "ESRCH";
39444
+ }
39445
+ }
39208
39446
  async function runWeacpxMcpServer(options) {
39209
39447
  const transport = options.transport ?? createOrchestrationTransport(options.endpoint ?? resolveDefaultOrchestrationEndpoint(process.env, process.platform));
39210
39448
  const server = createWeacpxMcpServer({
@@ -39215,11 +39453,14 @@ async function runWeacpxMcpServer(options) {
39215
39453
  ...options.availableAgents ? { availableAgents: options.availableAgents } : {}
39216
39454
  });
39217
39455
  const stdio = new StdioServerTransport(stdin, stdout);
39456
+ let cleanupShutdownHooks;
39218
39457
  let shuttingDown = false;
39219
39458
  const shutdown = async () => {
39220
39459
  if (shuttingDown)
39221
39460
  return;
39222
39461
  shuttingDown = true;
39462
+ cleanupShutdownHooks?.();
39463
+ options.onDiagnostic?.("mcp.stdio.stopping");
39223
39464
  const forceExit = setTimeout(() => process.exit(0), 3000);
39224
39465
  forceExit.unref();
39225
39466
  try {
@@ -39227,11 +39468,16 @@ async function runWeacpxMcpServer(options) {
39227
39468
  await stdio.close();
39228
39469
  } catch {}
39229
39470
  clearTimeout(forceExit);
39471
+ options.onDiagnostic?.("mcp.stdio.stopped");
39230
39472
  process.exit(0);
39231
39473
  };
39232
- stdin.on("end", () => void shutdown());
39233
- process.on("SIGTERM", () => void shutdown());
39234
- process.on("SIGINT", () => void shutdown());
39474
+ options.onDiagnostic?.("mcp.stdio.start", { parentPid: process.ppid, platform: process.platform });
39475
+ cleanupShutdownHooks = installMcpStdioShutdownHooks({
39476
+ stdin,
39477
+ stdout,
39478
+ shutdown,
39479
+ onDiagnostic: options.onDiagnostic
39480
+ });
39235
39481
  await server.connect(stdio);
39236
39482
  }
39237
39483
  function normalizeInputSchemaJson(schema) {
@@ -41023,7 +41269,12 @@ async function defaultMcpStdio(args, deps = {}) {
41023
41269
  ...coordinatorSession ? { coordinatorSession } : {},
41024
41270
  ...sourceHandle ? { sourceHandle } : {},
41025
41271
  ...identityResolver ? { resolveIdentity: identityResolver } : {},
41026
- ...availableAgents ? { availableAgents } : {}
41272
+ ...availableAgents ? { availableAgents } : {},
41273
+ onDiagnostic: (event, context) => {
41274
+ const suffix = context && Object.keys(context).length > 0 ? ` ${JSON.stringify(context)}` : "";
41275
+ (deps.stderr ?? ((text) => process.stderr.write(text)))(`[weacpx:mcp] ${event}${suffix}
41276
+ `);
41277
+ }
41027
41278
  });
41028
41279
  return 0;
41029
41280
  }
@@ -1,6 +1,6 @@
1
1
  export type { ChannelPluginDefinition } from "./channels/plugin.js";
2
2
  export type { ChannelFactory, CreateChannelDeps } from "./channels/create-channel.js";
3
- export type { ChannelStartInput, ConsumerLock, ConsumerLockMetadata, ConsumerLockOptions, CoordinatorMessageInput, MessageChannelRuntime, OrchestrationDeliveryCallbacks, OutboundQuota, } from "./channels/types.js";
3
+ export type { ChannelStartInput, ConsumerLock, ConsumerLockMetadata, ConsumerLockOptions, CoordinatorMessageInput, MessageChannelRuntime, OrchestrationDeliveryCallbacks, OutboundQuota, ToolUseEvent, ToolUseKind, ToolUseStatus, } from "./channels/types.js";
4
4
  export type { ChannelCliInput, ChannelCliIo, ChannelCliParseResult, ChannelCliProvider, ChannelCliValidationIssue, } from "./channels/cli/provider.js";
5
5
  export type { ChannelRuntimeConfig } from "./config/types.js";
6
6
  export type { AppLogger } from "./logging/app-logger.js";
@@ -1,4 +1,5 @@
1
1
  import type { ChannelMediaAttachment, OutboundChannelMedia } from "../../channels/media-types.js";
2
+ import type { ToolUseEvent } from "../../channels/types.js";
2
3
  /**
3
4
  * Agent interface — any AI backend that can handle a chat message.
4
5
  *
@@ -42,6 +43,8 @@ export interface ChatRequest {
42
43
  * output produced after abort.
43
44
  */
44
45
  abortSignal?: AbortSignal;
46
+ /** Structured tool-use side-channel; see PromptOptions.onToolEvent. */
47
+ onToolEvent?: (event: ToolUseEvent) => void | Promise<void>;
45
48
  }
46
49
  export interface ChatRequestMetadata {
47
50
  channel?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weacpx",
3
- "version": "0.4.0-beta.2",
3
+ "version": "0.4.0-beta.3",
4
4
  "description": "使用微信 ClawBot 随时随地通过 `acpx` 控制 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",