@autohq/cli 0.1.448 → 0.1.450

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.
@@ -30825,7 +30825,7 @@ Object.assign(lookup, {
30825
30825
  // package.json
30826
30826
  var package_default = {
30827
30827
  name: "@autohq/cli",
30828
- version: "0.1.448",
30828
+ version: "0.1.450",
30829
30829
  license: "SEE LICENSE IN README.md",
30830
30830
  publishConfig: {
30831
30831
  access: "public"
@@ -36480,23 +36480,14 @@ var SafeTelemetryTextSchema = external_exports.string().transform(
36480
36480
  return codePoint < 32 || codePoint === 127 ? " " : character;
36481
36481
  }).join("").replace(/\s+/g, " ").trim().slice(0, 300).trim()
36482
36482
  ).pipe(external_exports.string().min(1).max(300));
36483
- var PlatformUsageUserSchema = external_exports.object({
36484
- id: external_exports.string().trim().min(1).max(200),
36485
- displayName: SafeTelemetryTextSchema.nullable(),
36486
- email: SafeTelemetryTextSchema.nullable()
36487
- });
36488
- var PlatformUsageSessionActorSchema = external_exports.discriminatedUnion("kind", [
36489
- external_exports.object({
36490
- kind: external_exports.literal("user"),
36491
- user: PlatformUsageUserSchema
36492
- }),
36493
- external_exports.object({
36494
- kind: external_exports.literal("provider"),
36495
- provider: SafeTelemetryTextSchema,
36496
- externalId: external_exports.string().trim().min(1).max(200),
36497
- displayName: SafeTelemetryTextSchema.nullable()
36498
- })
36499
- ]);
36483
+ var PlatformUsageActorSchema = external_exports.object({
36484
+ displayName: SafeTelemetryTextSchema,
36485
+ source: SafeTelemetryTextSchema.nullable()
36486
+ }).strict();
36487
+ var PlatformUsageAgentSchema = external_exports.object({
36488
+ displayName: SafeTelemetryTextSchema,
36489
+ model: SafeTelemetryTextSchema.nullable()
36490
+ }).strict();
36500
36491
  var PlatformUsageScopeSchema = external_exports.object({
36501
36492
  environment: SafeTelemetryTextSchema,
36502
36493
  organizationId: external_exports.string().trim().min(1).max(200),
@@ -36504,13 +36495,13 @@ var PlatformUsageScopeSchema = external_exports.object({
36504
36495
  projectId: external_exports.string().trim().min(1).max(200).nullable(),
36505
36496
  projectName: SafeTelemetryTextSchema.nullable()
36506
36497
  });
36507
- var ScopedUserEventSchema = external_exports.object({
36508
- user: PlatformUsageUserSchema,
36498
+ var ScopedActorEventSchema = external_exports.object({
36499
+ actor: PlatformUsageActorSchema,
36509
36500
  scope: PlatformUsageScopeSchema
36510
36501
  });
36511
36502
  var PlatformUsageEventSchema = external_exports.discriminatedUnion("kind", [
36512
- ScopedUserEventSchema.extend({ kind: external_exports.literal("user.signup") }),
36513
- ScopedUserEventSchema.extend({
36503
+ ScopedActorEventSchema.extend({ kind: external_exports.literal("user.signup") }),
36504
+ ScopedActorEventSchema.extend({
36514
36505
  kind: external_exports.literal("onboarding.lifecycle"),
36515
36506
  stage: external_exports.enum(["team_selected", "github_connected", "team_applied"]),
36516
36507
  team: SafeTelemetryTextSchema,
@@ -36520,23 +36511,21 @@ var PlatformUsageEventSchema = external_exports.discriminatedUnion("kind", [
36520
36511
  failureSummary: SafeTelemetryTextSchema.nullable()
36521
36512
  }).nullable()
36522
36513
  }),
36523
- ScopedUserEventSchema.extend({
36514
+ ScopedActorEventSchema.extend({
36524
36515
  kind: external_exports.literal("agent.created"),
36525
- agentName: SafeTelemetryTextSchema
36516
+ agent: PlatformUsageAgentSchema
36526
36517
  }),
36527
- external_exports.object({
36518
+ ScopedActorEventSchema.extend({
36528
36519
  kind: external_exports.literal("session.started"),
36529
- actor: PlatformUsageSessionActorSchema,
36530
- scope: PlatformUsageScopeSchema,
36531
- agentName: SafeTelemetryTextSchema,
36520
+ agent: PlatformUsageAgentSchema,
36532
36521
  sessionId: external_exports.string().trim().min(1).max(200)
36533
36522
  }),
36534
- ScopedUserEventSchema.extend({
36523
+ ScopedActorEventSchema.extend({
36535
36524
  kind: external_exports.literal("credits.added"),
36536
36525
  amountUsd: external_exports.number().positive().finite(),
36537
36526
  source: external_exports.enum(["purchase", "auto_topup", "adjustment"])
36538
36527
  }),
36539
- ScopedUserEventSchema.extend({
36528
+ ScopedActorEventSchema.extend({
36540
36529
  kind: external_exports.literal("credits.exhausted"),
36541
36530
  autoReloadOutcome: external_exports.enum([
36542
36531
  "not_enabled",
@@ -42808,6 +42797,15 @@ triggers:
42808
42797
  content: "harness: claude-code\nenvironment:\n name: auto-default-runtime\n image:\n kind: preset\n name: node24\nidentity:\n displayName: Default\n username: default\n description: Auto's built-in general-purpose agent.\ntools:\n auto:\n kind: local\n implementation: auto\n"
42809
42798
  }
42810
42799
  ]
42800
+ },
42801
+ {
42802
+ version: "1.1.0",
42803
+ files: [
42804
+ {
42805
+ path: "fragments/default.yaml",
42806
+ content: "# 1.1.0 bakes the built-in catalog avatar (default.png) into Default's identity so\n# every provisioned agent/default shows the house face instead of an initials\n# fallback. Otherwise byte-identical to 1.0.0.\nharness: claude-code\nenvironment:\n name: auto-default-runtime\n image:\n kind: preset\n name: node24\nidentity:\n displayName: Default\n username: default\n avatar:\n asset: .auto/assets/default.png\n sha256: a5dd97676173a83dfc6fb9bdf30e7f50c7392f9e382fca40a23d6ab9285e9bf2\n description: Auto's built-in general-purpose agent.\ntools:\n auto:\n kind: local\n implementation: auto\n"
42807
+ }
42808
+ ]
42811
42809
  }
42812
42810
  ],
42813
42811
  "@auto/engineering-tier": [
@@ -71453,6 +71451,10 @@ function jsonRecordString2(value2, key) {
71453
71451
 
71454
71452
  // src/commands/agent-bridge/harness/claude-code/projector.ts
71455
71453
  var ClaudeCodeProjector = class {
71454
+ constructor(input = {}) {
71455
+ this.input = input;
71456
+ }
71457
+ input;
71456
71458
  currentMessageId = null;
71457
71459
  // The turn's UI message id. Claude emits a `message_start` per assistant
71458
71460
  // message, but a tool-using turn spans several (message A with the tool call,
@@ -71471,13 +71473,17 @@ var ClaudeCodeProjector = class {
71471
71473
  // real turn failure (FRA-3548). Cleared on every result and on the next
71472
71474
  // message_start so a stale marker can never reclassify a later failure.
71473
71475
  interruptMarkerSeen = false;
71474
- project(message) {
71476
+ project(message, context = {}) {
71475
71477
  if (isClaudeCodeInterruptMarker(message)) {
71476
71478
  this.interruptMarkerSeen = true;
71477
71479
  }
71478
71480
  if (message.type === "stream_event") {
71479
71481
  return this.projectStreamEvent(message);
71480
71482
  }
71483
+ const interruptContext = {
71484
+ marker: this.interruptMarkerSeen,
71485
+ bridge: context.bridgeInterruptSettling === true
71486
+ };
71481
71487
  const parsed = parseClaudeCodeStreamRecord(message);
71482
71488
  const snapshotMessageId = assistantSnapshotMessageId(message);
71483
71489
  const suppressStreamed = snapshotMessageId !== null && this.streamedMessageIds.has(snapshotMessageId);
@@ -71489,18 +71495,24 @@ var ClaudeCodeProjector = class {
71489
71495
  return [];
71490
71496
  }
71491
71497
  return conversationProjectionToUiChunks(entry, {
71492
- interrupted: this.interruptMarkerSeen
71498
+ interruptContext,
71499
+ emitDiagnostic: this.input.writeOutput
71493
71500
  });
71494
71501
  });
71495
71502
  if (parsed.result) {
71496
71503
  outputs.push(...this.endActiveParts(), ...this.finishActiveToolInputs());
71497
71504
  this.currentMessageId = null;
71498
71505
  this.activeResponseMessageId = null;
71499
- outputs.push(
71500
- projectClaudeCodeResult(parsed.result, {
71501
- interrupted: this.isInterruptCancellation(parsed.result)
71502
- })
71506
+ const interrupted = this.isInterruptCancellation(
71507
+ parsed.result,
71508
+ interruptContext
71503
71509
  );
71510
+ if (interrupted) {
71511
+ this.input.writeOutput?.(
71512
+ `agent_bridge_claude_result_interrupt_cancellation gate=${interruptGate(interruptContext)}`
71513
+ );
71514
+ }
71515
+ outputs.push(projectClaudeCodeResult(parsed.result, { interrupted }));
71504
71516
  this.interruptMarkerSeen = false;
71505
71517
  }
71506
71518
  return outputs;
@@ -71510,9 +71522,11 @@ var ClaudeCodeProjector = class {
71510
71522
  // surfaces as a diagnostic-only `error_during_execution`. Treating that as a
71511
71523
  // turn failure fed the FRA-3548 redelivery loop (the bridge redelivered a
71512
71524
  // message the SDK was actively answering), so a failed result bracketed by
71513
- // the SDK's own interrupt marker is classified as a cancellation instead.
71514
- isInterruptCancellation(result) {
71515
- return result.isError && this.interruptMarkerSeen && isClaudeCodeEdeDiagnosticOnlyError(result.errorMessage);
71525
+ // interrupt context — the SDK's own marker, or the session's knowledge that
71526
+ // a bridge interrupt is settling (which covers a missing or late marker)
71527
+ // is classified as a cancellation instead.
71528
+ isInterruptCancellation(result, context) {
71529
+ return result.isError && (context.marker || context.bridge) && isClaudeCodeEdeDiagnosticOnlyError(result.errorMessage);
71516
71530
  }
71517
71531
  flushPendingAssistantMessages() {
71518
71532
  const outputs = [
@@ -71633,7 +71647,8 @@ var ClaudeCodeProjector = class {
71633
71647
  const messageChunks = this.assistantSnapshotMessageChunks(projections);
71634
71648
  const rest = projections.filter((projection) => !isAssistantMessageOrToolCall(projection)).flatMap(
71635
71649
  (projection) => conversationProjectionToUiChunks(projection, {
71636
- interrupted: this.interruptMarkerSeen
71650
+ interruptContext: { marker: this.interruptMarkerSeen, bridge: false },
71651
+ emitDiagnostic: this.input.writeOutput
71637
71652
  })
71638
71653
  );
71639
71654
  const last = messageChunks.at(-1);
@@ -71857,7 +71872,7 @@ function textContentFromDelta(delta) {
71857
71872
  return null;
71858
71873
  }
71859
71874
  }
71860
- function conversationProjectionToUiChunks(projection, options = { interrupted: false }) {
71875
+ function conversationProjectionToUiChunks(projection, options) {
71861
71876
  if (projection.role === "assistant" && projection.kind === "message") {
71862
71877
  return messageContentChunks(projection);
71863
71878
  }
@@ -71878,15 +71893,12 @@ function conversationProjectionToUiChunks(projection, options = { interrupted: f
71878
71893
  })
71879
71894
  ];
71880
71895
  }
71881
- const remapped = typeof part.output === "string" ? remapClaudeCodeInterruptToolResultErrorText(part.output, {
71882
- interrupted: options.interrupted
71883
- }) : null;
71884
71896
  return [
71885
- uiChunk({
71886
- type: "tool-output-error",
71887
- toolCallId: part.toolUseId ?? UNKNOWN_MESSAGE_ID,
71888
- errorText: remapped ?? JSON.stringify(part.output)
71889
- })
71897
+ toolResultErrorChunk(
71898
+ part.toolUseId ?? UNKNOWN_MESSAGE_ID,
71899
+ part.output,
71900
+ options
71901
+ )
71890
71902
  ];
71891
71903
  });
71892
71904
  }
@@ -71930,6 +71942,32 @@ function conversationProjectionToUiChunks(projection, options = { interrupted: f
71930
71942
  }
71931
71943
  ];
71932
71944
  }
71945
+ function toolResultErrorChunk(toolCallId, output, options) {
71946
+ const context = options.interruptContext;
71947
+ const remapped = typeof output === "string" ? remapClaudeCodeInterruptToolResultErrorText(output, {
71948
+ interrupted: context.marker || context.bridge
71949
+ }) : null;
71950
+ if (remapped !== null) {
71951
+ options.emitDiagnostic?.(
71952
+ `agent_bridge_claude_tool_cancellation_remapped tool_use_id=${toolCallId} gate=${interruptGate(context)}`
71953
+ );
71954
+ } else if (typeof output === "string" && isClaudeCodePermissionDenialText(output)) {
71955
+ options.emitDiagnostic?.(
71956
+ `agent_bridge_claude_permission_denial_passthrough tool_use_id=${toolCallId}`
71957
+ );
71958
+ }
71959
+ return uiChunk({
71960
+ type: "tool-output-error",
71961
+ toolCallId,
71962
+ errorText: remapped ?? JSON.stringify(output)
71963
+ });
71964
+ }
71965
+ function interruptGate(context) {
71966
+ if (context.marker && context.bridge) {
71967
+ return "marker+bridge";
71968
+ }
71969
+ return context.marker ? "marker" : "bridge";
71970
+ }
71933
71971
  function isAssistantMessageOrToolCall(projection) {
71934
71972
  return projection.role === "assistant" && (projection.kind === "message" || projection.kind === "tool_call");
71935
71973
  }
@@ -91974,7 +92012,7 @@ function withClaudeStderrDiagnosticsPointer(error51, capturedStderr) {
91974
92012
 
91975
92013
  // src/commands/agent-bridge/harness/claude-code/session.ts
91976
92014
  var CLAUDE_AGENT_STARTUP_TIMEOUT_MS = 3e4;
91977
- var CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS = 1e4;
92015
+ var CLAUDE_TOOL_BOUNDARY_WAIT_TIMEOUT_MS = 12e4;
91978
92016
  var CLAUDE_INTERRUPT_ACK_TIMEOUT_MS = 1e4;
91979
92017
  var CLAUDE_MCP_REGISTRATION_TIMEOUT_MS = 3e4;
91980
92018
  var CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS = 100;
@@ -92050,16 +92088,33 @@ var ClaudeAgentBridgeSessionImpl = class {
92050
92088
  // flushed command id.
92051
92089
  openTurnCommandIds = [];
92052
92090
  // tool_use ids the assistant has emitted whose tool_result has not yet been
92053
- // observed. A non-empty set means a tool call is in flight, so an immediate
92054
- // interrupt would append the new user message after an unresolved
92091
+ // observed, with the name/detail needed to describe them in a cancellation
92092
+ // notice. A non-empty map means a tool call is in flight: interrupting now
92093
+ // would destroy its work and record a denial-worded cancellation, and
92094
+ // injecting now would append the new user message after an unresolved
92055
92095
  // stop_reason=tool_use — the invalid transcript that trips Claude Code's
92056
92096
  // ede_diagnostic and auto-restarts the session (FRA-3049).
92057
- pendingToolUseIds = /* @__PURE__ */ new Set();
92058
- // Incremented on every turn-terminal `result`. An interrupt that aborts an
92059
- // in-flight tool call waits for this to advance before injecting, so the
92060
- // message lands only once the interrupted turn has settled its tool_use.
92097
+ pendingToolUses = /* @__PURE__ */ new Map();
92098
+ // Incremented on every turn-terminal `result`; the interrupt-timeout latch
92099
+ // compares against it to tell whether the same wedged turn is still running.
92061
92100
  turnResultCount = 0;
92062
- turnSettlementWaiters = /* @__PURE__ */ new Set();
92101
+ // A steering message is waiting for the in-flight tool call to settle so the
92102
+ // turn can be interrupted at the tool boundary instead of mid-tool. Armed by
92103
+ // sendMessage, fired by the output pump when the pending tool_use set
92104
+ // drains, bounded by the budget timer below.
92105
+ toolBoundaryWait = null;
92106
+ // True from the moment a bridge-initiated interrupt control request fires
92107
+ // until the targeted turn emits its terminal result (or the stream ends).
92108
+ // Reported to the projector on every message so the SDK's denial-worded
92109
+ // cancellation records are classified as platform cancellations even though
92110
+ // the SDK's own interrupt marker arrives after them (probe-verified wire
92111
+ // order, 2026-07-13: tool_result → marker → error_during_execution result).
92112
+ interruptSettling = false;
92113
+ // Tool calls the SDK cancelled while a bridge-initiated interrupt was
92114
+ // settling. Consumed by the deferred flush: the injected message gets a
92115
+ // platform-cancellation notice naming this work so the model re-runs it
92116
+ // instead of treating the denial-worded tool_result as a human rejection.
92117
+ cancelledToolRuns = [];
92063
92118
  // Memoized MCP-registration gate, keyed by the attached query. A runtime
92064
92119
  // restart warms a brand-new session + query, so keying off the query object
92065
92120
  // (rather than a session-lifetime flag) re-arms the gate on every restart:
@@ -92118,18 +92173,34 @@ var ClaudeAgentBridgeSessionImpl = class {
92118
92173
  delivery_mode: mode,
92119
92174
  state: this.state.kind,
92120
92175
  active_turn_count: this.activeTurnCount,
92121
- pending_tool_use_count: this.pendingToolUseIds.size,
92176
+ pending_tool_use_count: this.pendingToolUses.size,
92122
92177
  deferred_count: this.deferredMessages.length
92123
92178
  });
92124
92179
  if (mode === "deferred" && this.hasInterruptibleTurn()) {
92125
92180
  this.deferredMessages.push({ text: message, commandIds });
92126
92181
  this.input.runtimeLogger?.info("agent_bridge_claude_message_deferred", {
92127
92182
  active_turn_count: this.activeTurnCount,
92128
- pending_tool_use_count: this.pendingToolUseIds.size,
92183
+ pending_tool_use_count: this.pendingToolUses.size,
92129
92184
  deferred_count: this.deferredMessages.length
92130
92185
  });
92131
92186
  return;
92132
92187
  }
92188
+ if (this.hasInterruptibleTurn() && this.hasInFlightToolUse()) {
92189
+ this.deferredMessages.push({ text: message, commandIds });
92190
+ this.armToolBoundaryWait();
92191
+ this.input.runtimeLogger?.info(
92192
+ "agent_bridge_claude_message_deferred_tool_boundary",
92193
+ {
92194
+ active_turn_count: this.activeTurnCount,
92195
+ pending_tool_use_count: this.pendingToolUses.size,
92196
+ pending_tools: [...this.pendingToolUses.values()].map(
92197
+ (tool) => tool.name
92198
+ ),
92199
+ deferred_count: this.deferredMessages.length
92200
+ }
92201
+ );
92202
+ return;
92203
+ }
92133
92204
  const interruption = await this.interruptActiveTurnBeforeMessage();
92134
92205
  if (interruption === "defer" && this.hasInterruptibleTurn()) {
92135
92206
  this.deferredMessages.push({ text: message, commandIds });
@@ -92137,7 +92208,7 @@ var ClaudeAgentBridgeSessionImpl = class {
92137
92208
  "agent_bridge_claude_message_deferred_after_interrupt_timeout",
92138
92209
  {
92139
92210
  active_turn_count: this.activeTurnCount,
92140
- pending_tool_use_count: this.pendingToolUseIds.size,
92211
+ pending_tool_use_count: this.pendingToolUses.size,
92141
92212
  deferred_count: this.deferredMessages.length
92142
92213
  }
92143
92214
  );
@@ -92150,7 +92221,7 @@ var ClaudeAgentBridgeSessionImpl = class {
92150
92221
  duration_ms: Date.now() - startedAt,
92151
92222
  state: this.state.kind,
92152
92223
  active_turn_count: this.activeTurnCount,
92153
- pending_tool_use_count: this.pendingToolUseIds.size,
92224
+ pending_tool_use_count: this.pendingToolUses.size,
92154
92225
  deferred_count: this.deferredMessages.length
92155
92226
  });
92156
92227
  }
@@ -92234,8 +92305,10 @@ var ClaudeAgentBridgeSessionImpl = class {
92234
92305
  }
92235
92306
  this.state = { kind: "closed" };
92236
92307
  this.activeTurnCount = 0;
92237
- this.pendingToolUseIds.clear();
92238
- this.resolveTurnSettlement(false);
92308
+ this.pendingToolUses.clear();
92309
+ this.cancelledToolRuns = [];
92310
+ this.interruptSettling = false;
92311
+ this.disarmToolBoundaryWait();
92239
92312
  if (this.deferredMessages.length > 0) {
92240
92313
  this.input.writeOutput?.(
92241
92314
  `agent_bridge_claude_deferred_dropped count=${this.deferredMessages.length} reason=session_closed`
@@ -92326,19 +92399,21 @@ var ClaudeAgentBridgeSessionImpl = class {
92326
92399
  void (async () => {
92327
92400
  try {
92328
92401
  for await (const message of query) {
92402
+ const interruptSettling = this.interruptSettling;
92329
92403
  this.trackToolUseLifecycle(message);
92330
- let meta3;
92404
+ let meta3 = { interruptSettling };
92331
92405
  if (isClaudeAgentTurnResult(message)) {
92332
92406
  this.activeTurnCount = Math.max(0, this.activeTurnCount - 1);
92333
92407
  meta3 = {
92408
+ interruptSettling,
92334
92409
  consumedCommandIds: this.openTurnCommandIds.shift() ?? []
92335
92410
  };
92336
- this.pendingToolUseIds.clear();
92411
+ this.pendingToolUses.clear();
92412
+ this.interruptSettling = false;
92337
92413
  this.turnResultCount += 1;
92338
- this.resolveTurnSettlement(true);
92339
92414
  this.input.runtimeLogger?.info("agent_bridge_claude_turn_result", {
92340
92415
  active_turn_count: this.activeTurnCount,
92341
- pending_tool_use_count: this.pendingToolUseIds.size,
92416
+ pending_tool_use_count: this.pendingToolUses.size,
92342
92417
  turn_result_count: this.turnResultCount,
92343
92418
  deferred_count: this.deferredMessages.length,
92344
92419
  consumed_command_ids: meta3.consumedCommandIds
@@ -92346,6 +92421,9 @@ var ClaudeAgentBridgeSessionImpl = class {
92346
92421
  if (this.activeTurnCount === 0) {
92347
92422
  this.flushDeferredMessages();
92348
92423
  }
92424
+ if (this.deferredMessages.length === 0) {
92425
+ this.cancelledToolRuns = [];
92426
+ }
92349
92427
  }
92350
92428
  const failure = probeClaudeAgentMessage(
92351
92429
  message,
@@ -92370,8 +92448,9 @@ var ClaudeAgentBridgeSessionImpl = class {
92370
92448
  } finally {
92371
92449
  this.activeTurnCount = 0;
92372
92450
  this.openTurnCommandIds.length = 0;
92373
- this.pendingToolUseIds.clear();
92374
- this.resolveTurnSettlement(false);
92451
+ this.pendingToolUses.clear();
92452
+ this.interruptSettling = false;
92453
+ this.disarmToolBoundaryWait();
92375
92454
  this.state = { kind: "closed" };
92376
92455
  this.reportExit();
92377
92456
  }
@@ -92388,7 +92467,7 @@ var ClaudeAgentBridgeSessionImpl = class {
92388
92467
  this.input.runtimeLogger?.info("agent_bridge_claude_message_enqueued", {
92389
92468
  state: this.state.kind,
92390
92469
  active_turn_count: this.activeTurnCount,
92391
- pending_tool_use_count: this.pendingToolUseIds.size,
92470
+ pending_tool_use_count: this.pendingToolUses.size,
92392
92471
  deferred_count: this.deferredMessages.length
92393
92472
  });
92394
92473
  void this.ensureRunningQuery().catch((error51) => {
@@ -92401,75 +92480,105 @@ var ClaudeAgentBridgeSessionImpl = class {
92401
92480
  return this.activeTurnCount > 0 && this.state.kind === "running";
92402
92481
  }
92403
92482
  // True while the assistant has an emitted tool_use with no matching
92404
- // tool_result yet — the window where an immediate interrupt would strand an
92483
+ // tool_result yet — the window where an immediate interrupt would cancel
92484
+ // real tool work, and where an immediate injection would strand an
92405
92485
  // unresolved stop_reason=tool_use ahead of the injected user message.
92406
92486
  hasInFlightToolUse() {
92407
- return this.pendingToolUseIds.size > 0;
92487
+ return this.pendingToolUses.size > 0;
92408
92488
  }
92409
92489
  // Track tool_use/tool_result pairs as they stream so hasInFlightToolUse()
92410
92490
  // reflects the live transcript. A tool_use may appear first as a streaming
92411
92491
  // content block before the full assistant message is available; the matching
92412
92492
  // tool_result closes it; the turn's terminal result clears any remainder.
92493
+ // Two side jobs ride on the same observation point:
92494
+ // - a denial-worded error tool_result arriving while a bridge interrupt is
92495
+ // settling is that interrupt's cancellation of the tool — record it so the
92496
+ // flushed message can tell the model to re-run the cancelled work;
92497
+ // - a tool_result that drains the pending set is the tool boundary an armed
92498
+ // steering message has been waiting for — interrupt the turn now, while no
92499
+ // tool work is at risk.
92413
92500
  trackToolUseLifecycle(message) {
92414
- for (const id of toolUseIds(message)) {
92415
- this.pendingToolUseIds.add(id);
92501
+ for (const entry of toolUseEntries(message)) {
92502
+ this.pendingToolUses.set(entry.id, {
92503
+ name: entry.name,
92504
+ detail: entry.detail
92505
+ });
92416
92506
  }
92417
- for (const id of toolResultIds(message)) {
92418
- this.pendingToolUseIds.delete(id);
92507
+ const hadInFlightToolUse = this.hasInFlightToolUse();
92508
+ for (const result of toolResultEntries(message)) {
92509
+ const pending = this.pendingToolUses.get(result.id);
92510
+ this.pendingToolUses.delete(result.id);
92511
+ if (pending && this.interruptSettling && result.isError && result.text !== null && isClaudeCodePermissionDenialText(result.text)) {
92512
+ this.cancelledToolRuns.push(pending);
92513
+ this.input.writeOutput?.(
92514
+ `agent_bridge_claude_tool_cancellation_observed tool=${pending.name}`
92515
+ );
92516
+ }
92517
+ }
92518
+ if (hadInFlightToolUse && !this.hasInFlightToolUse()) {
92519
+ this.fireToolBoundaryInterrupt();
92419
92520
  }
92420
92521
  }
92421
- // Resolve once the interrupted turn emits its terminal `result` (turnResultCount
92422
- // advances past the pre-interrupt baseline), bounded so a tool that never
92423
- // returns after interrupt cannot wedge delivery. The caller reads the
92424
- // outcome: "timeout" means the turn is still running and the message must
92425
- // defer to the turn boundary rather than inject mid-turn.
92426
- awaitTurnSettlement(baseline, startedAt) {
92427
- if (this.turnResultCount > baseline) {
92428
- return Promise.resolve("ready");
92522
+ // The pending tool_use set just drained mid-turn. If a steering message is
92523
+ // waiting on that boundary, interrupt the turn now: nothing is executing, so
92524
+ // the cancellation costs only the model's remaining reasoning, and the
92525
+ // turn's terminal result flushes the message. Fire-and-forget the flush at
92526
+ // the turn result owns delivery whether or not this interrupt lands.
92527
+ fireToolBoundaryInterrupt() {
92528
+ if (!this.toolBoundaryWait || this.deferredMessages.length === 0 || !this.hasInterruptibleTurn()) {
92529
+ return;
92429
92530
  }
92430
- return new Promise((resolve2) => {
92431
- let done = false;
92432
- const finish = (outcome) => {
92433
- if (done) {
92434
- return;
92435
- }
92436
- done = true;
92437
- clearTimeout(timer);
92438
- this.turnSettlementWaiters.delete(waiter);
92439
- this.input.writeOutput?.(
92440
- `agent_bridge_claude_interrupt_settle_${outcome} duration_ms=${Date.now() - startedAt}`
92441
- );
92442
- resolve2(outcome);
92443
- };
92444
- const waiter = (settled) => finish(settled ? "ready" : "aborted");
92445
- const timer = setTimeout(
92446
- () => finish("timeout"),
92447
- CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS
92531
+ this.disarmToolBoundaryWait();
92532
+ this.input.writeOutput?.(
92533
+ `agent_bridge_claude_tool_boundary_interrupt deferred_count=${this.deferredMessages.length}`
92534
+ );
92535
+ void this.interruptActiveTurn();
92536
+ }
92537
+ // Bound the tool-boundary wait: past the budget the steering message is
92538
+ // judged more urgent than the in-flight tool, which is cancelled mid-run.
92539
+ // The cancellation is observed by trackToolUseLifecycle and reported to the
92540
+ // model in the flushed message's platform notice.
92541
+ armToolBoundaryWait() {
92542
+ if (this.toolBoundaryWait) {
92543
+ return;
92544
+ }
92545
+ const timer = setTimeout(() => {
92546
+ this.toolBoundaryWait = null;
92547
+ if (!this.hasInterruptibleTurn() || this.deferredMessages.length === 0) {
92548
+ return;
92549
+ }
92550
+ this.input.writeOutput?.(
92551
+ `agent_bridge_claude_tool_boundary_wait_timeout pending_tools=${[
92552
+ ...this.pendingToolUses.values()
92553
+ ].map((tool) => tool.name).join(",")} deferred_count=${this.deferredMessages.length}`
92448
92554
  );
92449
- timer.unref?.();
92450
- this.turnSettlementWaiters.add(waiter);
92451
- });
92555
+ void this.interruptActiveTurn();
92556
+ }, CLAUDE_TOOL_BOUNDARY_WAIT_TIMEOUT_MS);
92557
+ timer.unref?.();
92558
+ this.toolBoundaryWait = { timer };
92452
92559
  }
92453
- resolveTurnSettlement(settled) {
92454
- if (this.turnSettlementWaiters.size === 0) {
92560
+ disarmToolBoundaryWait() {
92561
+ if (!this.toolBoundaryWait) {
92455
92562
  return;
92456
92563
  }
92457
- const waiters = [...this.turnSettlementWaiters];
92458
- this.turnSettlementWaiters.clear();
92459
- for (const waiter of waiters) {
92460
- waiter(settled);
92461
- }
92564
+ clearTimeout(this.toolBoundaryWait.timer);
92565
+ this.toolBoundaryWait = null;
92462
92566
  }
92463
92567
  flushDeferredMessages() {
92464
92568
  if (this.deferredMessages.length === 0) {
92465
92569
  return;
92466
92570
  }
92467
92571
  const pending = this.deferredMessages.splice(0);
92572
+ const cancelledToolRuns = this.cancelledToolRuns.splice(0);
92573
+ this.disarmToolBoundaryWait();
92468
92574
  this.input.writeOutput?.(
92469
- `agent_bridge_claude_deferred_flush count=${pending.length}`
92575
+ `agent_bridge_claude_deferred_flush count=${pending.length} cancelled_tool_count=${cancelledToolRuns.length}`
92470
92576
  );
92577
+ const text2 = pending.map((entry) => entry.text).join("\n\n");
92471
92578
  this.enqueueUserMessage(
92472
- pending.map((entry) => entry.text).join("\n\n"),
92579
+ cancelledToolRuns.length > 0 ? `${text2}
92580
+
92581
+ ${cancelledToolPlatformNotice(cancelledToolRuns)}` : text2,
92473
92582
  pending.flatMap((entry) => entry.commandIds)
92474
92583
  );
92475
92584
  }
@@ -92490,11 +92599,10 @@ var ClaudeAgentBridgeSessionImpl = class {
92490
92599
  return "proceed";
92491
92600
  }
92492
92601
  const query = this.state.query;
92493
- const hadInFlightToolUse = this.hasInFlightToolUse();
92494
92602
  const settlementBaseline = this.turnResultCount;
92495
92603
  const startedAt = Date.now();
92496
92604
  this.input.writeOutput?.(
92497
- `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()} in_flight_tool_use=${hadInFlightToolUse}`
92605
+ `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()} in_flight_tool_use=${this.hasInFlightToolUse()}`
92498
92606
  );
92499
92607
  const interruptPromise = (async () => {
92500
92608
  const ack = await this.requestInterruptAck(query, startedAt);
@@ -92502,16 +92610,6 @@ var ClaudeAgentBridgeSessionImpl = class {
92502
92610
  this.interruptTimeoutTurn = settlementBaseline;
92503
92611
  return "defer";
92504
92612
  }
92505
- if (hadInFlightToolUse) {
92506
- const settle = await this.awaitTurnSettlement(
92507
- settlementBaseline,
92508
- startedAt
92509
- );
92510
- if (settle === "timeout") {
92511
- this.interruptTimeoutTurn = settlementBaseline;
92512
- return "defer";
92513
- }
92514
- }
92515
92613
  return "proceed";
92516
92614
  })();
92517
92615
  this.interruptInFlight = interruptPromise;
@@ -92548,6 +92646,7 @@ var ClaudeAgentBridgeSessionImpl = class {
92548
92646
  );
92549
92647
  }, CLAUDE_INTERRUPT_ACK_TIMEOUT_MS);
92550
92648
  timer.unref?.();
92649
+ this.interruptSettling = true;
92551
92650
  query.interrupt().then(
92552
92651
  () => {
92553
92652
  if (done) {
@@ -92904,10 +93003,10 @@ function claudeAgentUserMessage(message) {
92904
93003
  function isClaudeAgentTurnResult(message) {
92905
93004
  return message.type === "result";
92906
93005
  }
92907
- function toolUseIds(message) {
93006
+ function toolUseEntries(message) {
92908
93007
  if (message.type === "assistant") {
92909
93008
  return contentBlocks2(message.message.content).flatMap(
92910
- (block2) => block2.type === "tool_use" && typeof block2.id === "string" ? [block2.id] : []
93009
+ (block2) => block2.type === "tool_use" && typeof block2.id === "string" ? [toolUseEntryFromBlock(block2)] : []
92911
93010
  );
92912
93011
  }
92913
93012
  if (message.type !== "stream_event") {
@@ -92918,16 +93017,54 @@ function toolUseIds(message) {
92918
93017
  return [];
92919
93018
  }
92920
93019
  const block = event.content_block;
92921
- return block.type === "tool_use" && typeof block.id === "string" ? [block.id] : [];
93020
+ return block.type === "tool_use" && typeof block.id === "string" ? [toolUseEntryFromBlock(block)] : [];
93021
+ }
93022
+ function toolUseEntryFromBlock(block) {
93023
+ return {
93024
+ id: String(block.id),
93025
+ name: typeof block.name === "string" ? block.name : "unknown_tool",
93026
+ detail: toolUseDetail(block.input)
93027
+ };
93028
+ }
93029
+ function toolUseDetail(input) {
93030
+ if (input === void 0 || input === null) {
93031
+ return "";
93032
+ }
93033
+ let text2;
93034
+ try {
93035
+ text2 = JSON.stringify(input);
93036
+ } catch {
93037
+ return "";
93038
+ }
93039
+ if (!text2 || text2 === "{}") {
93040
+ return "";
93041
+ }
93042
+ return text2.length > 160 ? `${text2.slice(0, 160)}\u2026` : text2;
92922
93043
  }
92923
- function toolResultIds(message) {
93044
+ function toolResultEntries(message) {
92924
93045
  if (message.type !== "user") {
92925
93046
  return [];
92926
93047
  }
92927
93048
  return contentBlocks2(message.message.content).flatMap(
92928
- (block) => block.type === "tool_result" && typeof block.tool_use_id === "string" ? [block.tool_use_id] : []
93049
+ (block) => block.type === "tool_result" && typeof block.tool_use_id === "string" ? [
93050
+ {
93051
+ id: block.tool_use_id,
93052
+ isError: block.is_error === true,
93053
+ text: typeof block.content === "string" ? block.content : null
93054
+ }
93055
+ ] : []
92929
93056
  );
92930
93057
  }
93058
+ function cancelledToolPlatformNotice(cancelled) {
93059
+ const lines = cancelled.map(
93060
+ (tool) => `- ${tool.name}${tool.detail ? ` ${tool.detail}` : ""}`
93061
+ );
93062
+ return [
93063
+ "[Auto platform notice] To deliver the message above, the platform cancelled the following in-flight tool call(s) mid-execution:",
93064
+ ...lines,
93065
+ "This was a platform-initiated cancellation for message delivery, NOT a rejection or permission denial by the user \u2014 ignore any tool result saying the user doesn't want to proceed. Re-run or resume that tool work if it is still needed once you have applied the message."
93066
+ ].join("\n");
93067
+ }
92931
93068
  function contentBlocks2(content) {
92932
93069
  if (!Array.isArray(content)) {
92933
93070
  return [];
@@ -92946,6 +93083,9 @@ var ClaudeCodeCommandHandler = class {
92946
93083
  this.input = input;
92947
93084
  this.claudeConfig = input.claude;
92948
93085
  this.outputBuffer = new AgentBridgeOutputBuffer(input);
93086
+ this.projector = new ClaudeCodeProjector({
93087
+ writeOutput: input.writeOutput
93088
+ });
92949
93089
  this.readState = input.readState ? new ClaudeReadStateTracker(input.readState) : null;
92950
93090
  }
92951
93091
  input;
@@ -92967,7 +93107,7 @@ var ClaudeCodeCommandHandler = class {
92967
93107
  inFlightMessageCommands = /* @__PURE__ */ new Map();
92968
93108
  pendingQuestions = /* @__PURE__ */ new Map();
92969
93109
  outputBuffer;
92970
- projector = new ClaudeCodeProjector();
93110
+ projector;
92971
93111
  readState;
92972
93112
  livenessTicker = null;
92973
93113
  // -----------------------------------------------------------------------------
@@ -93348,7 +93488,9 @@ var ClaudeCodeCommandHandler = class {
93348
93488
  this.trackReadState(activeContext, message);
93349
93489
  let projections;
93350
93490
  try {
93351
- projections = this.projector.project(message);
93491
+ projections = this.projector.project(message, {
93492
+ bridgeInterruptSettling: meta3?.interruptSettling === true
93493
+ });
93352
93494
  } catch (error51) {
93353
93495
  this.input.writeOutput?.(
93354
93496
  `agent_bridge_claude_parse_failed error=${error51 instanceof Error ? error51.message : String(error51)}`