@autohq/cli 0.1.195 → 0.1.197

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.197",
26644
26644
  license: "SEE LICENSE IN README.md",
26645
26645
  publishConfig: {
26646
26646
  access: "public"
@@ -27109,13 +27109,14 @@ var ClaudeCodeProjector = class {
27109
27109
  }
27110
27110
  };
27111
27111
  function projectClaudeCodeResult(result) {
27112
+ const failedButRecoverable = isRecoverableToolUseDiagnostic(result);
27112
27113
  return {
27113
27114
  type: "entry",
27114
27115
  entry: {
27115
27116
  role: "system",
27116
27117
  kind: "status",
27117
27118
  status: result.isError ? "failed" : "completed",
27118
- sessionStatusAfter: result.isError ? "failed" : "awaiting",
27119
+ sessionStatusAfter: result.isError && !failedButRecoverable ? "failed" : "awaiting",
27119
27120
  content: {
27120
27121
  parts: [
27121
27122
  {
@@ -27127,6 +27128,13 @@ function projectClaudeCodeResult(result) {
27127
27128
  }
27128
27129
  };
27129
27130
  }
27131
+ function isRecoverableToolUseDiagnostic(result) {
27132
+ if (!result.isError) {
27133
+ return false;
27134
+ }
27135
+ const message = result.errorMessage ?? "";
27136
+ return message.startsWith("[ede_diagnostic]") && message.includes("stop_reason=tool_use");
27137
+ }
27130
27138
  function anthropicMessageIdFromStreamStart(message) {
27131
27139
  const event = message.event;
27132
27140
  if (event.type !== "message_start") {
@@ -46955,6 +46963,7 @@ function withClaudeStderrDiagnosticsPointer(error51, capturedStderr) {
46955
46963
 
46956
46964
  // src/commands/agent-bridge/harness/claude-code/session.ts
46957
46965
  var CLAUDE_AGENT_STARTUP_TIMEOUT_MS = 3e4;
46966
+ var CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS = 1e4;
46958
46967
  var CLAUDE_STARTUP_PROFILE_HOOK_EVENTS = [
46959
46968
  "Setup",
46960
46969
  "SessionStart",
@@ -46991,6 +47000,17 @@ var ClaudeAgentBridgeSessionImpl = class {
46991
47000
  // held here instead of injected mid-turn (which would reject a pending
46992
47001
  // tool_use) and flushed once the turn ends and the session is idle.
46993
47002
  deferredMessages = [];
47003
+ // tool_use ids the assistant has emitted whose tool_result has not yet been
47004
+ // observed. A non-empty set means a tool call is in flight, so an immediate
47005
+ // interrupt would append the new user message after an unresolved
47006
+ // stop_reason=tool_use — the invalid transcript that trips Claude Code's
47007
+ // ede_diagnostic and auto-restarts the session (FRA-3049).
47008
+ pendingToolUseIds = /* @__PURE__ */ new Set();
47009
+ // Incremented on every turn-terminal `result`. An interrupt that aborts an
47010
+ // in-flight tool call waits for this to advance before injecting, so the
47011
+ // message lands only once the interrupted turn has settled its tool_use.
47012
+ turnResultCount = 0;
47013
+ turnSettlementWaiters = /* @__PURE__ */ new Set();
46994
47014
  constructor(input) {
46995
47015
  const optionsStartedAt = Date.now();
46996
47016
  this.input = input;
@@ -47052,6 +47072,8 @@ var ClaudeAgentBridgeSessionImpl = class {
47052
47072
  }
47053
47073
  this.state = { kind: "closed" };
47054
47074
  this.activeTurnCount = 0;
47075
+ this.pendingToolUseIds.clear();
47076
+ this.resolveTurnSettlement(false);
47055
47077
  if (this.deferredMessages.length > 0) {
47056
47078
  this.input.writeOutput?.(
47057
47079
  `agent_bridge_claude_deferred_dropped count=${this.deferredMessages.length} reason=session_closed`
@@ -47140,8 +47162,12 @@ var ClaudeAgentBridgeSessionImpl = class {
47140
47162
  void (async () => {
47141
47163
  try {
47142
47164
  for await (const message of query) {
47165
+ this.trackToolUseLifecycle(message);
47143
47166
  if (isClaudeAgentTurnResult(message)) {
47144
47167
  this.activeTurnCount = Math.max(0, this.activeTurnCount - 1);
47168
+ this.pendingToolUseIds.clear();
47169
+ this.turnResultCount += 1;
47170
+ this.resolveTurnSettlement(true);
47145
47171
  if (this.activeTurnCount === 0) {
47146
47172
  this.flushDeferredMessages();
47147
47173
  }
@@ -47168,6 +47194,8 @@ var ClaudeAgentBridgeSessionImpl = class {
47168
47194
  }
47169
47195
  } finally {
47170
47196
  this.activeTurnCount = 0;
47197
+ this.pendingToolUseIds.clear();
47198
+ this.resolveTurnSettlement(false);
47171
47199
  this.state = { kind: "closed" };
47172
47200
  this.reportExit();
47173
47201
  }
@@ -47189,6 +47217,65 @@ var ClaudeAgentBridgeSessionImpl = class {
47189
47217
  hasInterruptibleTurn() {
47190
47218
  return this.activeTurnCount > 0 && this.state.kind === "running";
47191
47219
  }
47220
+ // True while the assistant has an emitted tool_use with no matching
47221
+ // tool_result yet — the window where an immediate interrupt would strand an
47222
+ // unresolved stop_reason=tool_use ahead of the injected user message.
47223
+ hasInFlightToolUse() {
47224
+ return this.pendingToolUseIds.size > 0;
47225
+ }
47226
+ // Track tool_use/tool_result pairs as they stream so hasInFlightToolUse()
47227
+ // reflects the live transcript. A tool_use may appear first as a streaming
47228
+ // content block before the full assistant message is available; the matching
47229
+ // tool_result closes it; the turn's terminal result clears any remainder.
47230
+ trackToolUseLifecycle(message) {
47231
+ for (const id of toolUseIds(message)) {
47232
+ this.pendingToolUseIds.add(id);
47233
+ }
47234
+ for (const id of toolResultIds(message)) {
47235
+ this.pendingToolUseIds.delete(id);
47236
+ }
47237
+ }
47238
+ // Resolve once the interrupted turn emits its terminal `result` (turnResultCount
47239
+ // advances past the pre-interrupt baseline), bounded so a tool that never
47240
+ // returns after interrupt cannot wedge delivery. On timeout the caller falls
47241
+ // back to immediate injection rather than hanging or dropping the message.
47242
+ awaitTurnSettlement(baseline, startedAt) {
47243
+ if (this.turnResultCount > baseline) {
47244
+ return Promise.resolve();
47245
+ }
47246
+ return new Promise((resolve) => {
47247
+ let done = false;
47248
+ const finish = (outcome) => {
47249
+ if (done) {
47250
+ return;
47251
+ }
47252
+ done = true;
47253
+ clearTimeout(timer);
47254
+ this.turnSettlementWaiters.delete(waiter);
47255
+ this.input.writeOutput?.(
47256
+ `agent_bridge_claude_interrupt_settle_${outcome} duration_ms=${Date.now() - startedAt}`
47257
+ );
47258
+ resolve();
47259
+ };
47260
+ const waiter = (settled) => finish(settled ? "ready" : "aborted");
47261
+ const timer = setTimeout(
47262
+ () => finish("timeout"),
47263
+ CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS
47264
+ );
47265
+ timer.unref?.();
47266
+ this.turnSettlementWaiters.add(waiter);
47267
+ });
47268
+ }
47269
+ resolveTurnSettlement(settled) {
47270
+ if (this.turnSettlementWaiters.size === 0) {
47271
+ return;
47272
+ }
47273
+ const waiters = [...this.turnSettlementWaiters];
47274
+ this.turnSettlementWaiters.clear();
47275
+ for (const waiter of waiters) {
47276
+ waiter(settled);
47277
+ }
47278
+ }
47192
47279
  flushDeferredMessages() {
47193
47280
  if (this.deferredMessages.length === 0) {
47194
47281
  return;
@@ -47213,9 +47300,11 @@ var ClaudeAgentBridgeSessionImpl = class {
47213
47300
  return;
47214
47301
  }
47215
47302
  const query = this.state.query;
47303
+ const hadInFlightToolUse = this.hasInFlightToolUse();
47304
+ const settlementBaseline = this.turnResultCount;
47216
47305
  const startedAt = Date.now();
47217
47306
  this.input.writeOutput?.(
47218
- `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()}`
47307
+ `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()} in_flight_tool_use=${hadInFlightToolUse}`
47219
47308
  );
47220
47309
  const interruptPromise = (async () => {
47221
47310
  try {
@@ -47228,6 +47317,9 @@ var ClaudeAgentBridgeSessionImpl = class {
47228
47317
  `agent_bridge_claude_mid_turn_interrupt_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
47229
47318
  );
47230
47319
  }
47320
+ if (hadInFlightToolUse) {
47321
+ await this.awaitTurnSettlement(settlementBaseline, startedAt);
47322
+ }
47231
47323
  })();
47232
47324
  this.interruptInFlight = interruptPromise;
47233
47325
  try {
@@ -47385,6 +47477,38 @@ function claudeAgentUserMessage(message) {
47385
47477
  function isClaudeAgentTurnResult(message) {
47386
47478
  return message.type === "result";
47387
47479
  }
47480
+ function toolUseIds(message) {
47481
+ if (message.type === "assistant") {
47482
+ return contentBlocks(message.message.content).flatMap(
47483
+ (block2) => block2.type === "tool_use" && typeof block2.id === "string" ? [block2.id] : []
47484
+ );
47485
+ }
47486
+ if (message.type !== "stream_event") {
47487
+ return [];
47488
+ }
47489
+ const event = message.event;
47490
+ if (event.type !== "content_block_start") {
47491
+ return [];
47492
+ }
47493
+ const block = event.content_block;
47494
+ return block.type === "tool_use" && typeof block.id === "string" ? [block.id] : [];
47495
+ }
47496
+ function toolResultIds(message) {
47497
+ if (message.type !== "user") {
47498
+ return [];
47499
+ }
47500
+ return contentBlocks(message.message.content).flatMap(
47501
+ (block) => block.type === "tool_result" && typeof block.tool_use_id === "string" ? [block.tool_use_id] : []
47502
+ );
47503
+ }
47504
+ function contentBlocks(content) {
47505
+ if (!Array.isArray(content)) {
47506
+ return [];
47507
+ }
47508
+ return content.filter(
47509
+ (block) => typeof block === "object" && block !== null
47510
+ );
47511
+ }
47388
47512
 
47389
47513
  // src/commands/agent-bridge/harness/claude-code/index.ts
47390
47514
  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.197",
21627
21627
  license: "SEE LICENSE IN README.md",
21628
21628
  publishConfig: {
21629
21629
  access: "public"
@@ -31701,13 +31701,14 @@ var ClaudeCodeProjector = class {
31701
31701
  }
31702
31702
  };
31703
31703
  function projectClaudeCodeResult(result) {
31704
+ const failedButRecoverable = isRecoverableToolUseDiagnostic(result);
31704
31705
  return {
31705
31706
  type: "entry",
31706
31707
  entry: {
31707
31708
  role: "system",
31708
31709
  kind: "status",
31709
31710
  status: result.isError ? "failed" : "completed",
31710
- sessionStatusAfter: result.isError ? "failed" : "awaiting",
31711
+ sessionStatusAfter: result.isError && !failedButRecoverable ? "failed" : "awaiting",
31711
31712
  content: {
31712
31713
  parts: [
31713
31714
  {
@@ -31719,6 +31720,13 @@ function projectClaudeCodeResult(result) {
31719
31720
  }
31720
31721
  };
31721
31722
  }
31723
+ function isRecoverableToolUseDiagnostic(result) {
31724
+ if (!result.isError) {
31725
+ return false;
31726
+ }
31727
+ const message = result.errorMessage ?? "";
31728
+ return message.startsWith("[ede_diagnostic]") && message.includes("stop_reason=tool_use");
31729
+ }
31722
31730
  function anthropicMessageIdFromStreamStart(message) {
31723
31731
  const event = message.event;
31724
31732
  if (event.type !== "message_start") {
@@ -31975,6 +31983,7 @@ function withClaudeStderrDiagnosticsPointer(error51, capturedStderr) {
31975
31983
 
31976
31984
  // src/commands/agent-bridge/harness/claude-code/session.ts
31977
31985
  var CLAUDE_AGENT_STARTUP_TIMEOUT_MS = 3e4;
31986
+ var CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS = 1e4;
31978
31987
  var CLAUDE_STARTUP_PROFILE_HOOK_EVENTS = [
31979
31988
  "Setup",
31980
31989
  "SessionStart",
@@ -32011,6 +32020,17 @@ var ClaudeAgentBridgeSessionImpl = class {
32011
32020
  // held here instead of injected mid-turn (which would reject a pending
32012
32021
  // tool_use) and flushed once the turn ends and the session is idle.
32013
32022
  deferredMessages = [];
32023
+ // tool_use ids the assistant has emitted whose tool_result has not yet been
32024
+ // observed. A non-empty set means a tool call is in flight, so an immediate
32025
+ // interrupt would append the new user message after an unresolved
32026
+ // stop_reason=tool_use — the invalid transcript that trips Claude Code's
32027
+ // ede_diagnostic and auto-restarts the session (FRA-3049).
32028
+ pendingToolUseIds = /* @__PURE__ */ new Set();
32029
+ // Incremented on every turn-terminal `result`. An interrupt that aborts an
32030
+ // in-flight tool call waits for this to advance before injecting, so the
32031
+ // message lands only once the interrupted turn has settled its tool_use.
32032
+ turnResultCount = 0;
32033
+ turnSettlementWaiters = /* @__PURE__ */ new Set();
32014
32034
  constructor(input) {
32015
32035
  const optionsStartedAt = Date.now();
32016
32036
  this.input = input;
@@ -32072,6 +32092,8 @@ var ClaudeAgentBridgeSessionImpl = class {
32072
32092
  }
32073
32093
  this.state = { kind: "closed" };
32074
32094
  this.activeTurnCount = 0;
32095
+ this.pendingToolUseIds.clear();
32096
+ this.resolveTurnSettlement(false);
32075
32097
  if (this.deferredMessages.length > 0) {
32076
32098
  this.input.writeOutput?.(
32077
32099
  `agent_bridge_claude_deferred_dropped count=${this.deferredMessages.length} reason=session_closed`
@@ -32160,8 +32182,12 @@ var ClaudeAgentBridgeSessionImpl = class {
32160
32182
  void (async () => {
32161
32183
  try {
32162
32184
  for await (const message of query) {
32185
+ this.trackToolUseLifecycle(message);
32163
32186
  if (isClaudeAgentTurnResult(message)) {
32164
32187
  this.activeTurnCount = Math.max(0, this.activeTurnCount - 1);
32188
+ this.pendingToolUseIds.clear();
32189
+ this.turnResultCount += 1;
32190
+ this.resolveTurnSettlement(true);
32165
32191
  if (this.activeTurnCount === 0) {
32166
32192
  this.flushDeferredMessages();
32167
32193
  }
@@ -32188,6 +32214,8 @@ var ClaudeAgentBridgeSessionImpl = class {
32188
32214
  }
32189
32215
  } finally {
32190
32216
  this.activeTurnCount = 0;
32217
+ this.pendingToolUseIds.clear();
32218
+ this.resolveTurnSettlement(false);
32191
32219
  this.state = { kind: "closed" };
32192
32220
  this.reportExit();
32193
32221
  }
@@ -32209,6 +32237,65 @@ var ClaudeAgentBridgeSessionImpl = class {
32209
32237
  hasInterruptibleTurn() {
32210
32238
  return this.activeTurnCount > 0 && this.state.kind === "running";
32211
32239
  }
32240
+ // True while the assistant has an emitted tool_use with no matching
32241
+ // tool_result yet — the window where an immediate interrupt would strand an
32242
+ // unresolved stop_reason=tool_use ahead of the injected user message.
32243
+ hasInFlightToolUse() {
32244
+ return this.pendingToolUseIds.size > 0;
32245
+ }
32246
+ // Track tool_use/tool_result pairs as they stream so hasInFlightToolUse()
32247
+ // reflects the live transcript. A tool_use may appear first as a streaming
32248
+ // content block before the full assistant message is available; the matching
32249
+ // tool_result closes it; the turn's terminal result clears any remainder.
32250
+ trackToolUseLifecycle(message) {
32251
+ for (const id of toolUseIds(message)) {
32252
+ this.pendingToolUseIds.add(id);
32253
+ }
32254
+ for (const id of toolResultIds(message)) {
32255
+ this.pendingToolUseIds.delete(id);
32256
+ }
32257
+ }
32258
+ // Resolve once the interrupted turn emits its terminal `result` (turnResultCount
32259
+ // advances past the pre-interrupt baseline), bounded so a tool that never
32260
+ // returns after interrupt cannot wedge delivery. On timeout the caller falls
32261
+ // back to immediate injection rather than hanging or dropping the message.
32262
+ awaitTurnSettlement(baseline, startedAt) {
32263
+ if (this.turnResultCount > baseline) {
32264
+ return Promise.resolve();
32265
+ }
32266
+ return new Promise((resolve4) => {
32267
+ let done = false;
32268
+ const finish = (outcome) => {
32269
+ if (done) {
32270
+ return;
32271
+ }
32272
+ done = true;
32273
+ clearTimeout(timer);
32274
+ this.turnSettlementWaiters.delete(waiter);
32275
+ this.input.writeOutput?.(
32276
+ `agent_bridge_claude_interrupt_settle_${outcome} duration_ms=${Date.now() - startedAt}`
32277
+ );
32278
+ resolve4();
32279
+ };
32280
+ const waiter = (settled) => finish(settled ? "ready" : "aborted");
32281
+ const timer = setTimeout(
32282
+ () => finish("timeout"),
32283
+ CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS
32284
+ );
32285
+ timer.unref?.();
32286
+ this.turnSettlementWaiters.add(waiter);
32287
+ });
32288
+ }
32289
+ resolveTurnSettlement(settled) {
32290
+ if (this.turnSettlementWaiters.size === 0) {
32291
+ return;
32292
+ }
32293
+ const waiters = [...this.turnSettlementWaiters];
32294
+ this.turnSettlementWaiters.clear();
32295
+ for (const waiter of waiters) {
32296
+ waiter(settled);
32297
+ }
32298
+ }
32212
32299
  flushDeferredMessages() {
32213
32300
  if (this.deferredMessages.length === 0) {
32214
32301
  return;
@@ -32233,9 +32320,11 @@ var ClaudeAgentBridgeSessionImpl = class {
32233
32320
  return;
32234
32321
  }
32235
32322
  const query = this.state.query;
32323
+ const hadInFlightToolUse = this.hasInFlightToolUse();
32324
+ const settlementBaseline = this.turnResultCount;
32236
32325
  const startedAt = Date.now();
32237
32326
  this.input.writeOutput?.(
32238
- `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()}`
32327
+ `agent_bridge_claude_mid_turn_interrupt_started at=${new Date(startedAt).toISOString()} in_flight_tool_use=${hadInFlightToolUse}`
32239
32328
  );
32240
32329
  const interruptPromise = (async () => {
32241
32330
  try {
@@ -32248,6 +32337,9 @@ var ClaudeAgentBridgeSessionImpl = class {
32248
32337
  `agent_bridge_claude_mid_turn_interrupt_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
32249
32338
  );
32250
32339
  }
32340
+ if (hadInFlightToolUse) {
32341
+ await this.awaitTurnSettlement(settlementBaseline, startedAt);
32342
+ }
32251
32343
  })();
32252
32344
  this.interruptInFlight = interruptPromise;
32253
32345
  try {
@@ -32405,6 +32497,38 @@ function claudeAgentUserMessage(message) {
32405
32497
  function isClaudeAgentTurnResult(message) {
32406
32498
  return message.type === "result";
32407
32499
  }
32500
+ function toolUseIds(message) {
32501
+ if (message.type === "assistant") {
32502
+ return contentBlocks(message.message.content).flatMap(
32503
+ (block2) => block2.type === "tool_use" && typeof block2.id === "string" ? [block2.id] : []
32504
+ );
32505
+ }
32506
+ if (message.type !== "stream_event") {
32507
+ return [];
32508
+ }
32509
+ const event = message.event;
32510
+ if (event.type !== "content_block_start") {
32511
+ return [];
32512
+ }
32513
+ const block = event.content_block;
32514
+ return block.type === "tool_use" && typeof block.id === "string" ? [block.id] : [];
32515
+ }
32516
+ function toolResultIds(message) {
32517
+ if (message.type !== "user") {
32518
+ return [];
32519
+ }
32520
+ return contentBlocks(message.message.content).flatMap(
32521
+ (block) => block.type === "tool_result" && typeof block.tool_use_id === "string" ? [block.tool_use_id] : []
32522
+ );
32523
+ }
32524
+ function contentBlocks(content) {
32525
+ if (!Array.isArray(content)) {
32526
+ return [];
32527
+ }
32528
+ return content.filter(
32529
+ (block) => typeof block === "object" && block !== null
32530
+ );
32531
+ }
32408
32532
 
32409
32533
  // src/commands/agent-bridge/harness/claude-code/index.ts
32410
32534
  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.197",
4
4
  "license": "SEE LICENSE IN README.md",
5
5
  "publishConfig": {
6
6
  "access": "public"