@autohq/cli 0.1.195 → 0.1.196

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.
@@ -26640,7 +26640,7 @@ Object.assign(lookup, {
26640
26640
  // package.json
26641
26641
  var package_default = {
26642
26642
  name: "@autohq/cli",
26643
- version: "0.1.195",
26643
+ version: "0.1.196",
26644
26644
  license: "SEE LICENSE IN README.md",
26645
26645
  publishConfig: {
26646
26646
  access: "public"
@@ -46955,6 +46955,7 @@ function withClaudeStderrDiagnosticsPointer(error51, capturedStderr) {
46955
46955
 
46956
46956
  // src/commands/agent-bridge/harness/claude-code/session.ts
46957
46957
  var CLAUDE_AGENT_STARTUP_TIMEOUT_MS = 3e4;
46958
+ var CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS = 1e4;
46958
46959
  var CLAUDE_STARTUP_PROFILE_HOOK_EVENTS = [
46959
46960
  "Setup",
46960
46961
  "SessionStart",
@@ -46991,6 +46992,17 @@ var ClaudeAgentBridgeSessionImpl = class {
46991
46992
  // held here instead of injected mid-turn (which would reject a pending
46992
46993
  // tool_use) and flushed once the turn ends and the session is idle.
46993
46994
  deferredMessages = [];
46995
+ // tool_use ids the assistant has emitted whose tool_result has not yet been
46996
+ // observed. A non-empty set means a tool call is in flight, so an immediate
46997
+ // interrupt would append the new user message after an unresolved
46998
+ // stop_reason=tool_use — the invalid transcript that trips Claude Code's
46999
+ // ede_diagnostic and auto-restarts the session (FRA-3049).
47000
+ pendingToolUseIds = /* @__PURE__ */ new Set();
47001
+ // Incremented on every turn-terminal `result`. An interrupt that aborts an
47002
+ // in-flight tool call waits for this to advance before injecting, so the
47003
+ // message lands only once the interrupted turn has settled its tool_use.
47004
+ turnResultCount = 0;
47005
+ turnSettlementWaiters = /* @__PURE__ */ new Set();
46994
47006
  constructor(input) {
46995
47007
  const optionsStartedAt = Date.now();
46996
47008
  this.input = input;
@@ -47052,6 +47064,8 @@ var ClaudeAgentBridgeSessionImpl = class {
47052
47064
  }
47053
47065
  this.state = { kind: "closed" };
47054
47066
  this.activeTurnCount = 0;
47067
+ this.pendingToolUseIds.clear();
47068
+ this.resolveTurnSettlement(false);
47055
47069
  if (this.deferredMessages.length > 0) {
47056
47070
  this.input.writeOutput?.(
47057
47071
  `agent_bridge_claude_deferred_dropped count=${this.deferredMessages.length} reason=session_closed`
@@ -47140,8 +47154,12 @@ var ClaudeAgentBridgeSessionImpl = class {
47140
47154
  void (async () => {
47141
47155
  try {
47142
47156
  for await (const message of query) {
47157
+ this.trackToolUseLifecycle(message);
47143
47158
  if (isClaudeAgentTurnResult(message)) {
47144
47159
  this.activeTurnCount = Math.max(0, this.activeTurnCount - 1);
47160
+ this.pendingToolUseIds.clear();
47161
+ this.turnResultCount += 1;
47162
+ this.resolveTurnSettlement(true);
47145
47163
  if (this.activeTurnCount === 0) {
47146
47164
  this.flushDeferredMessages();
47147
47165
  }
@@ -47168,6 +47186,8 @@ var ClaudeAgentBridgeSessionImpl = class {
47168
47186
  }
47169
47187
  } finally {
47170
47188
  this.activeTurnCount = 0;
47189
+ this.pendingToolUseIds.clear();
47190
+ this.resolveTurnSettlement(false);
47171
47191
  this.state = { kind: "closed" };
47172
47192
  this.reportExit();
47173
47193
  }
@@ -47189,6 +47209,64 @@ var ClaudeAgentBridgeSessionImpl = class {
47189
47209
  hasInterruptibleTurn() {
47190
47210
  return this.activeTurnCount > 0 && this.state.kind === "running";
47191
47211
  }
47212
+ // True while the assistant has an emitted tool_use with no matching
47213
+ // tool_result yet — the window where an immediate interrupt would strand an
47214
+ // unresolved stop_reason=tool_use ahead of the injected user message.
47215
+ hasInFlightToolUse() {
47216
+ return this.pendingToolUseIds.size > 0;
47217
+ }
47218
+ // Track tool_use/tool_result pairs as they stream so hasInFlightToolUse()
47219
+ // reflects the live transcript. tool_use opens a call; the matching
47220
+ // tool_result closes it; the turn's terminal result clears any remainder.
47221
+ trackToolUseLifecycle(message) {
47222
+ for (const id of toolUseIds(message)) {
47223
+ this.pendingToolUseIds.add(id);
47224
+ }
47225
+ for (const id of toolResultIds(message)) {
47226
+ this.pendingToolUseIds.delete(id);
47227
+ }
47228
+ }
47229
+ // Resolve once the interrupted turn emits its terminal `result` (turnResultCount
47230
+ // advances past the pre-interrupt baseline), bounded so a tool that never
47231
+ // returns after interrupt cannot wedge delivery. On timeout the caller falls
47232
+ // back to immediate injection rather than hanging or dropping the message.
47233
+ awaitTurnSettlement(baseline, startedAt) {
47234
+ if (this.turnResultCount > baseline) {
47235
+ return Promise.resolve();
47236
+ }
47237
+ return new Promise((resolve) => {
47238
+ let done = false;
47239
+ const finish = (outcome) => {
47240
+ if (done) {
47241
+ return;
47242
+ }
47243
+ done = true;
47244
+ clearTimeout(timer);
47245
+ this.turnSettlementWaiters.delete(waiter);
47246
+ this.input.writeOutput?.(
47247
+ `agent_bridge_claude_interrupt_settle_${outcome} duration_ms=${Date.now() - startedAt}`
47248
+ );
47249
+ resolve();
47250
+ };
47251
+ const waiter = (settled) => finish(settled ? "ready" : "aborted");
47252
+ const timer = setTimeout(
47253
+ () => finish("timeout"),
47254
+ CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS
47255
+ );
47256
+ timer.unref?.();
47257
+ this.turnSettlementWaiters.add(waiter);
47258
+ });
47259
+ }
47260
+ resolveTurnSettlement(settled) {
47261
+ if (this.turnSettlementWaiters.size === 0) {
47262
+ return;
47263
+ }
47264
+ const waiters = [...this.turnSettlementWaiters];
47265
+ this.turnSettlementWaiters.clear();
47266
+ for (const waiter of waiters) {
47267
+ waiter(settled);
47268
+ }
47269
+ }
47192
47270
  flushDeferredMessages() {
47193
47271
  if (this.deferredMessages.length === 0) {
47194
47272
  return;
@@ -47213,9 +47291,11 @@ var ClaudeAgentBridgeSessionImpl = class {
47213
47291
  return;
47214
47292
  }
47215
47293
  const query = this.state.query;
47294
+ const hadInFlightToolUse = this.hasInFlightToolUse();
47295
+ const settlementBaseline = this.turnResultCount;
47216
47296
  const startedAt = Date.now();
47217
47297
  this.input.writeOutput?.(
47218
- `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()}`
47298
+ `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()} in_flight_tool_use=${hadInFlightToolUse}`
47219
47299
  );
47220
47300
  const interruptPromise = (async () => {
47221
47301
  try {
@@ -47228,6 +47308,9 @@ var ClaudeAgentBridgeSessionImpl = class {
47228
47308
  `agent_bridge_claude_mid_turn_interrupt_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
47229
47309
  );
47230
47310
  }
47311
+ if (hadInFlightToolUse) {
47312
+ await this.awaitTurnSettlement(settlementBaseline, startedAt);
47313
+ }
47231
47314
  })();
47232
47315
  this.interruptInFlight = interruptPromise;
47233
47316
  try {
@@ -47385,6 +47468,30 @@ function claudeAgentUserMessage(message) {
47385
47468
  function isClaudeAgentTurnResult(message) {
47386
47469
  return message.type === "result";
47387
47470
  }
47471
+ function toolUseIds(message) {
47472
+ if (message.type !== "assistant") {
47473
+ return [];
47474
+ }
47475
+ return contentBlocks(message.message.content).flatMap(
47476
+ (block) => block.type === "tool_use" && typeof block.id === "string" ? [block.id] : []
47477
+ );
47478
+ }
47479
+ function toolResultIds(message) {
47480
+ if (message.type !== "user") {
47481
+ return [];
47482
+ }
47483
+ return contentBlocks(message.message.content).flatMap(
47484
+ (block) => block.type === "tool_result" && typeof block.tool_use_id === "string" ? [block.tool_use_id] : []
47485
+ );
47486
+ }
47487
+ function contentBlocks(content) {
47488
+ if (!Array.isArray(content)) {
47489
+ return [];
47490
+ }
47491
+ return content.filter(
47492
+ (block) => typeof block === "object" && block !== null
47493
+ );
47494
+ }
47388
47495
 
47389
47496
  // src/commands/agent-bridge/harness/claude-code/index.ts
47390
47497
  async function runAgentBridgeClaudeCode(options) {
package/dist/index.js CHANGED
@@ -21623,7 +21623,7 @@ var init_package = __esm({
21623
21623
  "package.json"() {
21624
21624
  package_default = {
21625
21625
  name: "@autohq/cli",
21626
- version: "0.1.195",
21626
+ version: "0.1.196",
21627
21627
  license: "SEE LICENSE IN README.md",
21628
21628
  publishConfig: {
21629
21629
  access: "public"
@@ -31975,6 +31975,7 @@ function withClaudeStderrDiagnosticsPointer(error51, capturedStderr) {
31975
31975
 
31976
31976
  // src/commands/agent-bridge/harness/claude-code/session.ts
31977
31977
  var CLAUDE_AGENT_STARTUP_TIMEOUT_MS = 3e4;
31978
+ var CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS = 1e4;
31978
31979
  var CLAUDE_STARTUP_PROFILE_HOOK_EVENTS = [
31979
31980
  "Setup",
31980
31981
  "SessionStart",
@@ -32011,6 +32012,17 @@ var ClaudeAgentBridgeSessionImpl = class {
32011
32012
  // held here instead of injected mid-turn (which would reject a pending
32012
32013
  // tool_use) and flushed once the turn ends and the session is idle.
32013
32014
  deferredMessages = [];
32015
+ // tool_use ids the assistant has emitted whose tool_result has not yet been
32016
+ // observed. A non-empty set means a tool call is in flight, so an immediate
32017
+ // interrupt would append the new user message after an unresolved
32018
+ // stop_reason=tool_use — the invalid transcript that trips Claude Code's
32019
+ // ede_diagnostic and auto-restarts the session (FRA-3049).
32020
+ pendingToolUseIds = /* @__PURE__ */ new Set();
32021
+ // Incremented on every turn-terminal `result`. An interrupt that aborts an
32022
+ // in-flight tool call waits for this to advance before injecting, so the
32023
+ // message lands only once the interrupted turn has settled its tool_use.
32024
+ turnResultCount = 0;
32025
+ turnSettlementWaiters = /* @__PURE__ */ new Set();
32014
32026
  constructor(input) {
32015
32027
  const optionsStartedAt = Date.now();
32016
32028
  this.input = input;
@@ -32072,6 +32084,8 @@ var ClaudeAgentBridgeSessionImpl = class {
32072
32084
  }
32073
32085
  this.state = { kind: "closed" };
32074
32086
  this.activeTurnCount = 0;
32087
+ this.pendingToolUseIds.clear();
32088
+ this.resolveTurnSettlement(false);
32075
32089
  if (this.deferredMessages.length > 0) {
32076
32090
  this.input.writeOutput?.(
32077
32091
  `agent_bridge_claude_deferred_dropped count=${this.deferredMessages.length} reason=session_closed`
@@ -32160,8 +32174,12 @@ var ClaudeAgentBridgeSessionImpl = class {
32160
32174
  void (async () => {
32161
32175
  try {
32162
32176
  for await (const message of query) {
32177
+ this.trackToolUseLifecycle(message);
32163
32178
  if (isClaudeAgentTurnResult(message)) {
32164
32179
  this.activeTurnCount = Math.max(0, this.activeTurnCount - 1);
32180
+ this.pendingToolUseIds.clear();
32181
+ this.turnResultCount += 1;
32182
+ this.resolveTurnSettlement(true);
32165
32183
  if (this.activeTurnCount === 0) {
32166
32184
  this.flushDeferredMessages();
32167
32185
  }
@@ -32188,6 +32206,8 @@ var ClaudeAgentBridgeSessionImpl = class {
32188
32206
  }
32189
32207
  } finally {
32190
32208
  this.activeTurnCount = 0;
32209
+ this.pendingToolUseIds.clear();
32210
+ this.resolveTurnSettlement(false);
32191
32211
  this.state = { kind: "closed" };
32192
32212
  this.reportExit();
32193
32213
  }
@@ -32209,6 +32229,64 @@ var ClaudeAgentBridgeSessionImpl = class {
32209
32229
  hasInterruptibleTurn() {
32210
32230
  return this.activeTurnCount > 0 && this.state.kind === "running";
32211
32231
  }
32232
+ // True while the assistant has an emitted tool_use with no matching
32233
+ // tool_result yet — the window where an immediate interrupt would strand an
32234
+ // unresolved stop_reason=tool_use ahead of the injected user message.
32235
+ hasInFlightToolUse() {
32236
+ return this.pendingToolUseIds.size > 0;
32237
+ }
32238
+ // Track tool_use/tool_result pairs as they stream so hasInFlightToolUse()
32239
+ // reflects the live transcript. tool_use opens a call; the matching
32240
+ // tool_result closes it; the turn's terminal result clears any remainder.
32241
+ trackToolUseLifecycle(message) {
32242
+ for (const id of toolUseIds(message)) {
32243
+ this.pendingToolUseIds.add(id);
32244
+ }
32245
+ for (const id of toolResultIds(message)) {
32246
+ this.pendingToolUseIds.delete(id);
32247
+ }
32248
+ }
32249
+ // Resolve once the interrupted turn emits its terminal `result` (turnResultCount
32250
+ // advances past the pre-interrupt baseline), bounded so a tool that never
32251
+ // returns after interrupt cannot wedge delivery. On timeout the caller falls
32252
+ // back to immediate injection rather than hanging or dropping the message.
32253
+ awaitTurnSettlement(baseline, startedAt) {
32254
+ if (this.turnResultCount > baseline) {
32255
+ return Promise.resolve();
32256
+ }
32257
+ return new Promise((resolve4) => {
32258
+ let done = false;
32259
+ const finish = (outcome) => {
32260
+ if (done) {
32261
+ return;
32262
+ }
32263
+ done = true;
32264
+ clearTimeout(timer);
32265
+ this.turnSettlementWaiters.delete(waiter);
32266
+ this.input.writeOutput?.(
32267
+ `agent_bridge_claude_interrupt_settle_${outcome} duration_ms=${Date.now() - startedAt}`
32268
+ );
32269
+ resolve4();
32270
+ };
32271
+ const waiter = (settled) => finish(settled ? "ready" : "aborted");
32272
+ const timer = setTimeout(
32273
+ () => finish("timeout"),
32274
+ CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS
32275
+ );
32276
+ timer.unref?.();
32277
+ this.turnSettlementWaiters.add(waiter);
32278
+ });
32279
+ }
32280
+ resolveTurnSettlement(settled) {
32281
+ if (this.turnSettlementWaiters.size === 0) {
32282
+ return;
32283
+ }
32284
+ const waiters = [...this.turnSettlementWaiters];
32285
+ this.turnSettlementWaiters.clear();
32286
+ for (const waiter of waiters) {
32287
+ waiter(settled);
32288
+ }
32289
+ }
32212
32290
  flushDeferredMessages() {
32213
32291
  if (this.deferredMessages.length === 0) {
32214
32292
  return;
@@ -32233,9 +32311,11 @@ var ClaudeAgentBridgeSessionImpl = class {
32233
32311
  return;
32234
32312
  }
32235
32313
  const query = this.state.query;
32314
+ const hadInFlightToolUse = this.hasInFlightToolUse();
32315
+ const settlementBaseline = this.turnResultCount;
32236
32316
  const startedAt = Date.now();
32237
32317
  this.input.writeOutput?.(
32238
- `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()}`
32318
+ `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()} in_flight_tool_use=${hadInFlightToolUse}`
32239
32319
  );
32240
32320
  const interruptPromise = (async () => {
32241
32321
  try {
@@ -32248,6 +32328,9 @@ var ClaudeAgentBridgeSessionImpl = class {
32248
32328
  `agent_bridge_claude_mid_turn_interrupt_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
32249
32329
  );
32250
32330
  }
32331
+ if (hadInFlightToolUse) {
32332
+ await this.awaitTurnSettlement(settlementBaseline, startedAt);
32333
+ }
32251
32334
  })();
32252
32335
  this.interruptInFlight = interruptPromise;
32253
32336
  try {
@@ -32405,6 +32488,30 @@ function claudeAgentUserMessage(message) {
32405
32488
  function isClaudeAgentTurnResult(message) {
32406
32489
  return message.type === "result";
32407
32490
  }
32491
+ function toolUseIds(message) {
32492
+ if (message.type !== "assistant") {
32493
+ return [];
32494
+ }
32495
+ return contentBlocks(message.message.content).flatMap(
32496
+ (block) => block.type === "tool_use" && typeof block.id === "string" ? [block.id] : []
32497
+ );
32498
+ }
32499
+ function toolResultIds(message) {
32500
+ if (message.type !== "user") {
32501
+ return [];
32502
+ }
32503
+ return contentBlocks(message.message.content).flatMap(
32504
+ (block) => block.type === "tool_result" && typeof block.tool_use_id === "string" ? [block.tool_use_id] : []
32505
+ );
32506
+ }
32507
+ function contentBlocks(content) {
32508
+ if (!Array.isArray(content)) {
32509
+ return [];
32510
+ }
32511
+ return content.filter(
32512
+ (block) => typeof block === "object" && block !== null
32513
+ );
32514
+ }
32408
32515
 
32409
32516
  // src/commands/agent-bridge/harness/claude-code/index.ts
32410
32517
  async function runAgentBridgeClaudeCode(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autohq/cli",
3
- "version": "0.1.195",
3
+ "version": "0.1.196",
4
4
  "license": "SEE LICENSE IN README.md",
5
5
  "publishConfig": {
6
6
  "access": "public"