@autohq/cli 0.1.259 → 0.1.260

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.
@@ -21270,11 +21270,11 @@ var SocketWithoutUpgrade = class _SocketWithoutUpgrade extends Emitter {
21270
21270
  */
21271
21271
  _resetPingTimeout() {
21272
21272
  this.clearTimeoutFn(this._pingTimeoutTimer);
21273
- const delay = this._pingInterval + this._pingTimeout;
21274
- this._pingTimeoutTime = Date.now() + delay;
21273
+ const delay2 = this._pingInterval + this._pingTimeout;
21274
+ this._pingTimeoutTime = Date.now() + delay2;
21275
21275
  this._pingTimeoutTimer = this.setTimeoutFn(() => {
21276
21276
  this._onClose("ping timeout");
21277
- }, delay);
21277
+ }, delay2);
21278
21278
  if (this.opts.autoUnref) {
21279
21279
  this._pingTimeoutTimer.unref();
21280
21280
  }
@@ -23255,8 +23255,8 @@ var Manager = class extends Emitter {
23255
23255
  this.emitReserved("reconnect_failed");
23256
23256
  this._reconnecting = false;
23257
23257
  } else {
23258
- const delay = this.backoff.duration();
23259
- debug10("will wait %dms before reconnect attempt", delay);
23258
+ const delay2 = this.backoff.duration();
23259
+ debug10("will wait %dms before reconnect attempt", delay2);
23260
23260
  this._reconnecting = true;
23261
23261
  const timer = this.setTimeoutFn(() => {
23262
23262
  if (self2.skipReconnect)
@@ -23276,7 +23276,7 @@ var Manager = class extends Emitter {
23276
23276
  self2.onreconnect();
23277
23277
  }
23278
23278
  });
23279
- }, delay);
23279
+ }, delay2);
23280
23280
  if (this.opts.autoUnref) {
23281
23281
  timer.unref();
23282
23282
  }
@@ -23340,7 +23340,7 @@ Object.assign(lookup, {
23340
23340
  // package.json
23341
23341
  var package_default = {
23342
23342
  name: "@autohq/cli",
23343
- version: "0.1.259",
23343
+ version: "0.1.260",
23344
23344
  license: "SEE LICENSE IN README.md",
23345
23345
  publishConfig: {
23346
23346
  access: "public"
@@ -48192,6 +48192,8 @@ function withClaudeStderrDiagnosticsPointer(error51, capturedStderr) {
48192
48192
  // src/commands/agent-bridge/harness/claude-code/session.ts
48193
48193
  var CLAUDE_AGENT_STARTUP_TIMEOUT_MS = 3e4;
48194
48194
  var CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS = 1e4;
48195
+ var CLAUDE_MCP_REGISTRATION_TIMEOUT_MS = 3e3;
48196
+ var CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS = 100;
48195
48197
  var CLAUDE_STARTUP_PROFILE_HOOK_EVENTS = [
48196
48198
  "Setup",
48197
48199
  "SessionStart",
@@ -48239,6 +48241,12 @@ var ClaudeAgentBridgeSessionImpl = class {
48239
48241
  // message lands only once the interrupted turn has settled its tool_use.
48240
48242
  turnResultCount = 0;
48241
48243
  turnSettlementWaiters = /* @__PURE__ */ new Set();
48244
+ // Memoized MCP-registration gate, keyed by the attached query. A runtime
48245
+ // restart warms a brand-new session + query, so keying off the query object
48246
+ // (rather than a session-lifetime flag) re-arms the gate on every restart:
48247
+ // each fresh warm gates its own first turn, while later turns on the same
48248
+ // query share the resolved promise and never re-poll.
48249
+ mcpRegistrationGate = null;
48242
48250
  constructor(input) {
48243
48251
  const optionsStartedAt = Date.now();
48244
48252
  this.input = input;
@@ -48301,6 +48309,7 @@ var ClaudeAgentBridgeSessionImpl = class {
48301
48309
  return;
48302
48310
  }
48303
48311
  await this.interruptActiveTurnBeforeMessage();
48312
+ await this.awaitMcpRegistration();
48304
48313
  this.enqueueUserMessage(message);
48305
48314
  this.input.runtimeLogger?.info("agent_bridge_claude_send_message_queued", {
48306
48315
  delivery_mode: mode,
@@ -48588,6 +48597,86 @@ var ClaudeAgentBridgeSessionImpl = class {
48588
48597
  }
48589
48598
  }
48590
48599
  }
48600
+ // Why this gate is correct without a provider-backed test (it relies on real
48601
+ // SDK mcpServerStatus() timing that fakes cannot prove):
48602
+ // - Safe-degrade: pollMcpRegistration never rejects — on timeout, a
48603
+ // status-read failure, or a closed session it resolves, so the worst case
48604
+ // is today's immediate injection. The gate can never regress below the
48605
+ // status-quo race; it can only close it.
48606
+ // - Provider-path validation is deferred deliberately: the failure is the
48607
+ // intermittent live-prod race on the chief-of-staff singleton (a real
48608
+ // runtime restart dispatching turn-1 while real mcp__* servers reconnect),
48609
+ // which cannot be reproduced deterministically without live credentials
48610
+ // and a forced restart racing reconnect.
48611
+ // - Live confirmation comes from the agent_bridge_claude_mcp_gate_started/
48612
+ // _ready/_timeout diagnostics on the next real restart, not a CI fake.
48613
+ //
48614
+ // Block until the freshly-warmed query's MCP servers have left `pending`, so
48615
+ // the first turn's prompt is built with the mcp__* tools registered. Memoized
48616
+ // per query: the first send pays the wait, concurrent first sends share it,
48617
+ // and a later turn on the same query returns immediately. Sessions without
48618
+ // configured MCP servers, or whose query failed to attach, skip the gate and
48619
+ // fall through to the existing enqueue/error path unchanged.
48620
+ async awaitMcpRegistration() {
48621
+ if (Object.keys(this.options.mcpServers ?? {}).length === 0) {
48622
+ return;
48623
+ }
48624
+ let query;
48625
+ try {
48626
+ query = await this.ensureRunningQuery();
48627
+ } catch {
48628
+ return;
48629
+ }
48630
+ if (this.mcpRegistrationGate?.query !== query) {
48631
+ this.mcpRegistrationGate = {
48632
+ query,
48633
+ promise: this.pollMcpRegistration(query)
48634
+ };
48635
+ }
48636
+ await this.mcpRegistrationGate.promise;
48637
+ }
48638
+ // Poll the live query's MCP server status until no configured server is still
48639
+ // `pending`, bounded by CLAUDE_MCP_REGISTRATION_TIMEOUT_MS. Terminal states
48640
+ // (connected/failed/needs-auth/disabled) all count as settled, so a server
48641
+ // that cannot connect in this runtime (e.g. an interactively-authed server in
48642
+ // a headless sandbox) never holds the gate to the timeout. Never rejects:
48643
+ // status-read failure, timeout, or a closed session all resolve so the caller
48644
+ // degrades to immediate injection.
48645
+ async pollMcpRegistration(query) {
48646
+ const configuredNames = Object.keys(this.options.mcpServers ?? {});
48647
+ const startedAt = Date.now();
48648
+ const deadline = startedAt + CLAUDE_MCP_REGISTRATION_TIMEOUT_MS;
48649
+ this.input.writeOutput?.(
48650
+ `agent_bridge_claude_mcp_gate_started servers=${configuredNames.length}`
48651
+ );
48652
+ while (true) {
48653
+ if (this.state.kind === "closed") {
48654
+ return;
48655
+ }
48656
+ let statuses;
48657
+ try {
48658
+ statuses = await query.mcpServerStatus();
48659
+ } catch (error51) {
48660
+ this.input.writeOutput?.(
48661
+ `agent_bridge_claude_mcp_gate_status_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
48662
+ );
48663
+ return;
48664
+ }
48665
+ if (mcpServersSettled(statuses, configuredNames)) {
48666
+ this.input.writeOutput?.(
48667
+ `agent_bridge_claude_mcp_gate_ready duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
48668
+ );
48669
+ return;
48670
+ }
48671
+ if (Date.now() >= deadline) {
48672
+ this.input.writeOutput?.(
48673
+ `agent_bridge_claude_mcp_gate_timeout duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
48674
+ );
48675
+ return;
48676
+ }
48677
+ await delay(CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS);
48678
+ }
48679
+ }
48591
48680
  messageProbeContext() {
48592
48681
  return {
48593
48682
  configuredMcpServerNames: Object.keys(this.options.mcpServers ?? {}),
@@ -48717,6 +48806,24 @@ var AsyncMessageQueue = class {
48717
48806
  });
48718
48807
  }
48719
48808
  };
48809
+ function mcpServersSettled(statuses, configuredNames) {
48810
+ const statusByName = new Map(
48811
+ statuses.map((server) => [server.name, server.status])
48812
+ );
48813
+ return configuredNames.every((name) => {
48814
+ const status = statusByName.get(name);
48815
+ return status !== void 0 && status !== "pending";
48816
+ });
48817
+ }
48818
+ function mcpStatusList(statuses) {
48819
+ return statuses.map((server) => `${server.name}:${server.status}`).join(",");
48820
+ }
48821
+ function delay(ms) {
48822
+ return new Promise((resolve) => {
48823
+ const timer = setTimeout(resolve, ms);
48824
+ timer.unref?.();
48825
+ });
48826
+ }
48720
48827
  function claudeAgentUserMessage(message) {
48721
48828
  return {
48722
48829
  type: "user",
package/dist/index.js CHANGED
@@ -22782,7 +22782,7 @@ var init_package = __esm({
22782
22782
  "package.json"() {
22783
22783
  package_default = {
22784
22784
  name: "@autohq/cli",
22785
- version: "0.1.259",
22785
+ version: "0.1.260",
22786
22786
  license: "SEE LICENSE IN README.md",
22787
22787
  publishConfig: {
22788
22788
  access: "public"
@@ -33655,6 +33655,8 @@ function withClaudeStderrDiagnosticsPointer(error51, capturedStderr) {
33655
33655
  // src/commands/agent-bridge/harness/claude-code/session.ts
33656
33656
  var CLAUDE_AGENT_STARTUP_TIMEOUT_MS = 3e4;
33657
33657
  var CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS = 1e4;
33658
+ var CLAUDE_MCP_REGISTRATION_TIMEOUT_MS = 3e3;
33659
+ var CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS = 100;
33658
33660
  var CLAUDE_STARTUP_PROFILE_HOOK_EVENTS = [
33659
33661
  "Setup",
33660
33662
  "SessionStart",
@@ -33702,6 +33704,12 @@ var ClaudeAgentBridgeSessionImpl = class {
33702
33704
  // message lands only once the interrupted turn has settled its tool_use.
33703
33705
  turnResultCount = 0;
33704
33706
  turnSettlementWaiters = /* @__PURE__ */ new Set();
33707
+ // Memoized MCP-registration gate, keyed by the attached query. A runtime
33708
+ // restart warms a brand-new session + query, so keying off the query object
33709
+ // (rather than a session-lifetime flag) re-arms the gate on every restart:
33710
+ // each fresh warm gates its own first turn, while later turns on the same
33711
+ // query share the resolved promise and never re-poll.
33712
+ mcpRegistrationGate = null;
33705
33713
  constructor(input) {
33706
33714
  const optionsStartedAt = Date.now();
33707
33715
  this.input = input;
@@ -33764,6 +33772,7 @@ var ClaudeAgentBridgeSessionImpl = class {
33764
33772
  return;
33765
33773
  }
33766
33774
  await this.interruptActiveTurnBeforeMessage();
33775
+ await this.awaitMcpRegistration();
33767
33776
  this.enqueueUserMessage(message);
33768
33777
  this.input.runtimeLogger?.info("agent_bridge_claude_send_message_queued", {
33769
33778
  delivery_mode: mode,
@@ -34051,6 +34060,86 @@ var ClaudeAgentBridgeSessionImpl = class {
34051
34060
  }
34052
34061
  }
34053
34062
  }
34063
+ // Why this gate is correct without a provider-backed test (it relies on real
34064
+ // SDK mcpServerStatus() timing that fakes cannot prove):
34065
+ // - Safe-degrade: pollMcpRegistration never rejects — on timeout, a
34066
+ // status-read failure, or a closed session it resolves, so the worst case
34067
+ // is today's immediate injection. The gate can never regress below the
34068
+ // status-quo race; it can only close it.
34069
+ // - Provider-path validation is deferred deliberately: the failure is the
34070
+ // intermittent live-prod race on the chief-of-staff singleton (a real
34071
+ // runtime restart dispatching turn-1 while real mcp__* servers reconnect),
34072
+ // which cannot be reproduced deterministically without live credentials
34073
+ // and a forced restart racing reconnect.
34074
+ // - Live confirmation comes from the agent_bridge_claude_mcp_gate_started/
34075
+ // _ready/_timeout diagnostics on the next real restart, not a CI fake.
34076
+ //
34077
+ // Block until the freshly-warmed query's MCP servers have left `pending`, so
34078
+ // the first turn's prompt is built with the mcp__* tools registered. Memoized
34079
+ // per query: the first send pays the wait, concurrent first sends share it,
34080
+ // and a later turn on the same query returns immediately. Sessions without
34081
+ // configured MCP servers, or whose query failed to attach, skip the gate and
34082
+ // fall through to the existing enqueue/error path unchanged.
34083
+ async awaitMcpRegistration() {
34084
+ if (Object.keys(this.options.mcpServers ?? {}).length === 0) {
34085
+ return;
34086
+ }
34087
+ let query;
34088
+ try {
34089
+ query = await this.ensureRunningQuery();
34090
+ } catch {
34091
+ return;
34092
+ }
34093
+ if (this.mcpRegistrationGate?.query !== query) {
34094
+ this.mcpRegistrationGate = {
34095
+ query,
34096
+ promise: this.pollMcpRegistration(query)
34097
+ };
34098
+ }
34099
+ await this.mcpRegistrationGate.promise;
34100
+ }
34101
+ // Poll the live query's MCP server status until no configured server is still
34102
+ // `pending`, bounded by CLAUDE_MCP_REGISTRATION_TIMEOUT_MS. Terminal states
34103
+ // (connected/failed/needs-auth/disabled) all count as settled, so a server
34104
+ // that cannot connect in this runtime (e.g. an interactively-authed server in
34105
+ // a headless sandbox) never holds the gate to the timeout. Never rejects:
34106
+ // status-read failure, timeout, or a closed session all resolve so the caller
34107
+ // degrades to immediate injection.
34108
+ async pollMcpRegistration(query) {
34109
+ const configuredNames = Object.keys(this.options.mcpServers ?? {});
34110
+ const startedAt = Date.now();
34111
+ const deadline = startedAt + CLAUDE_MCP_REGISTRATION_TIMEOUT_MS;
34112
+ this.input.writeOutput?.(
34113
+ `agent_bridge_claude_mcp_gate_started servers=${configuredNames.length}`
34114
+ );
34115
+ while (true) {
34116
+ if (this.state.kind === "closed") {
34117
+ return;
34118
+ }
34119
+ let statuses;
34120
+ try {
34121
+ statuses = await query.mcpServerStatus();
34122
+ } catch (error51) {
34123
+ this.input.writeOutput?.(
34124
+ `agent_bridge_claude_mcp_gate_status_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
34125
+ );
34126
+ return;
34127
+ }
34128
+ if (mcpServersSettled(statuses, configuredNames)) {
34129
+ this.input.writeOutput?.(
34130
+ `agent_bridge_claude_mcp_gate_ready duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
34131
+ );
34132
+ return;
34133
+ }
34134
+ if (Date.now() >= deadline) {
34135
+ this.input.writeOutput?.(
34136
+ `agent_bridge_claude_mcp_gate_timeout duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
34137
+ );
34138
+ return;
34139
+ }
34140
+ await delay(CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS);
34141
+ }
34142
+ }
34054
34143
  messageProbeContext() {
34055
34144
  return {
34056
34145
  configuredMcpServerNames: Object.keys(this.options.mcpServers ?? {}),
@@ -34180,6 +34269,24 @@ var AsyncMessageQueue = class {
34180
34269
  });
34181
34270
  }
34182
34271
  };
34272
+ function mcpServersSettled(statuses, configuredNames) {
34273
+ const statusByName = new Map(
34274
+ statuses.map((server) => [server.name, server.status])
34275
+ );
34276
+ return configuredNames.every((name) => {
34277
+ const status = statusByName.get(name);
34278
+ return status !== void 0 && status !== "pending";
34279
+ });
34280
+ }
34281
+ function mcpStatusList(statuses) {
34282
+ return statuses.map((server) => `${server.name}:${server.status}`).join(",");
34283
+ }
34284
+ function delay(ms) {
34285
+ return new Promise((resolve4) => {
34286
+ const timer = setTimeout(resolve4, ms);
34287
+ timer.unref?.();
34288
+ });
34289
+ }
34183
34290
  function claudeAgentUserMessage(message) {
34184
34291
  return {
34185
34292
  type: "user",
@@ -37819,7 +37926,7 @@ function selectLastLines(content, count) {
37819
37926
  async function followRuntimeLog(input) {
37820
37927
  let offset = input.fromByte;
37821
37928
  while (!input.signal?.aborted) {
37822
- await delay(input.pollIntervalMs, input.signal);
37929
+ await delay2(input.pollIntervalMs, input.signal);
37823
37930
  if (input.signal?.aborted) {
37824
37931
  return;
37825
37932
  }
@@ -37854,7 +37961,7 @@ function readByteRange(path2, offset, length) {
37854
37961
  }
37855
37962
  return buffer.toString("utf8");
37856
37963
  }
37857
- function delay(ms, signal) {
37964
+ function delay2(ms, signal) {
37858
37965
  return new Promise((resolve4) => {
37859
37966
  const timer = setTimeout(resolve4, ms);
37860
37967
  signal?.addEventListener(
@@ -38800,10 +38907,10 @@ async function runConsole(input) {
38800
38907
  input.writeOutput(
38801
38908
  `stream interrupted: ${error51 instanceof Error ? error51.message : String(error51)}`
38802
38909
  );
38803
- await delay2(1e3, abort.signal);
38910
+ await delay3(1e3, abort.signal);
38804
38911
  continue;
38805
38912
  }
38806
- await delay2(250, abort.signal);
38913
+ await delay3(250, abort.signal);
38807
38914
  }
38808
38915
  })();
38809
38916
  const consoleInput = createInteractiveInputController({
@@ -38883,7 +38990,7 @@ function createInteractiveInputController(input) {
38883
38990
  })();
38884
38991
  return { done, close };
38885
38992
  }
38886
- function delay2(ms, signal) {
38993
+ function delay3(ms, signal) {
38887
38994
  if (signal.aborted) {
38888
38995
  return Promise.resolve();
38889
38996
  }
@@ -39204,7 +39311,7 @@ async function pollUntilAwaiting(input) {
39204
39311
  if (firstPoll) {
39205
39312
  firstPoll = false;
39206
39313
  } else {
39207
- await delay3(
39314
+ await delay4(
39208
39315
  Math.min(input.pollIntervalMs, Math.max(deadline - Date.now(), 0))
39209
39316
  );
39210
39317
  }
@@ -39218,7 +39325,7 @@ async function pollUntilAwaiting(input) {
39218
39325
  status = session.status;
39219
39326
  }
39220
39327
  }
39221
- function delay3(ms) {
39328
+ function delay4(ms) {
39222
39329
  if (ms <= 0) {
39223
39330
  return Promise.resolve();
39224
39331
  }
@@ -39278,7 +39385,7 @@ async function streamDiagnosticsBestEffort(input) {
39278
39385
  }
39279
39386
  }
39280
39387
  async function settleDiagnostics(done) {
39281
- await Promise.race([done, delay3(DIAGNOSTICS_SHUTDOWN_WAIT_MS)]);
39388
+ await Promise.race([done, delay4(DIAGNOSTICS_SHUTDOWN_WAIT_MS)]);
39282
39389
  }
39283
39390
  function writeBenchmarkResult(context, result, json2) {
39284
39391
  if (json2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autohq/cli",
3
- "version": "0.1.259",
3
+ "version": "0.1.260",
4
4
  "license": "SEE LICENSE IN README.md",
5
5
  "publishConfig": {
6
6
  "access": "public"