@autohq/cli 0.1.259 → 0.1.261

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.261",
23344
23344
  license: "SEE LICENSE IN README.md",
23345
23345
  publishConfig: {
23346
23346
  access: "public"
@@ -27877,60 +27877,50 @@ function compareSemver(left, right) {
27877
27877
  return 0;
27878
27878
  }
27879
27879
 
27880
+ // ../../packages/schemas/src/templates/content.generated.ts
27881
+ var GENERATED_TEMPLATE_CONTENT = {
27882
+ "@auto/onboarding": [
27883
+ {
27884
+ version: "1.0.0",
27885
+ files: [
27886
+ {
27887
+ path: "fragments/onboarding.yaml",
27888
+ content: "# Managed onboarding fragment (@auto/onboarding). Tenant onboarding agents\n# import this to inherit Auto's house onboarding guidance. Tenant fields win\n# on merge, so house tweaks can be layered on top of this base.\nsystemPrompt: |\n You are an Auto onboarding guide. Greet the user warmly, explain that Auto\n lets them compose agents and triggers into automated workflows from simple\n `.auto/` YAML, and guide them to their first deployed workflow. Keep replies\n short and conversational, ask one question at a time, and verify each step\n actually worked before telling the user it did."
27889
+ }
27890
+ ]
27891
+ }
27892
+ ],
27893
+ "@auto/smoke-test": [
27894
+ {
27895
+ version: "1.0.0",
27896
+ files: [
27897
+ {
27898
+ path: "fragments/smoke-test.yaml",
27899
+ content: "# Managed smoke-test fragment (@auto/smoke-test). A minimal, self-contained\n# fixture for verifying that managed templates resolve and inject cleanly.\n# Importing agents inherit this base guidance; tenant fields win on merge.\nsystemPrompt: |\n You are the Auto smoke-test agent, a minimal fixture used to confirm that\n managed templates resolve and inject correctly. When asked to run the smoke\n test, reply with a single short sentence confirming it passed, and do\n nothing else."
27900
+ }
27901
+ ]
27902
+ }
27903
+ ]
27904
+ };
27905
+
27880
27906
  // ../../packages/schemas/src/templates/hardcoded.ts
27881
- var ONBOARDING_FRAGMENT_V1 = [
27882
- "# Managed onboarding fragment (@auto/onboarding). Tenant onboarding agents",
27883
- "# import this to inherit Auto's house onboarding guidance. Tenant fields win",
27884
- "# on merge, so house tweaks can be layered on top of this base.",
27885
- "systemPrompt: |",
27886
- " You are an Auto onboarding guide. Greet the user warmly, explain that Auto",
27887
- " lets them compose agents and triggers into automated workflows from simple",
27888
- " `.auto/` YAML, and guide them to their first deployed workflow. Keep replies",
27889
- " short and conversational, ask one question at a time, and verify each step",
27890
- " actually worked before telling the user it did."
27891
- ].join("\n");
27892
- var SMOKE_TEST_FRAGMENT_V1 = [
27893
- "# Managed smoke-test fragment (@auto/smoke-test). A minimal, self-contained",
27894
- "# fixture for verifying that managed templates resolve and inject cleanly.",
27895
- "# Importing agents inherit this base guidance; tenant fields win on merge.",
27896
- "systemPrompt: |",
27897
- " You are the Auto smoke-test agent, a minimal fixture used to confirm that",
27898
- " managed templates resolve and inject correctly. When asked to run the smoke",
27899
- " test, reply with a single short sentence confirming it passed, and do",
27900
- " nothing else."
27901
- ].join("\n");
27902
- var MANAGED_TEMPLATES = [
27903
- {
27904
- name: "@auto/onboarding",
27905
- description: "Auto's house onboarding guidance, importable as a managed template.",
27906
- versions: [
27907
- {
27908
- version: "1.0.0",
27909
- files: [
27910
- {
27911
- path: "fragments/onboarding.yaml",
27912
- content: ONBOARDING_FRAGMENT_V1
27913
- }
27914
- ]
27915
- }
27916
- ]
27917
- },
27918
- {
27919
- name: "@auto/smoke-test",
27920
- description: "A minimal managed-template smoke-test fixture for end-to-end verification.",
27921
- versions: [
27922
- {
27923
- version: "1.0.0",
27924
- files: [
27925
- {
27926
- path: "fragments/smoke-test.yaml",
27927
- content: SMOKE_TEST_FRAGMENT_V1
27928
- }
27929
- ]
27930
- }
27931
- ]
27932
- }
27933
- ];
27907
+ var TEMPLATE_DESCRIPTIONS = {
27908
+ "@auto/onboarding": "Auto's house onboarding guidance, importable as a managed template.",
27909
+ "@auto/smoke-test": "A minimal managed-template smoke-test fixture for end-to-end verification."
27910
+ };
27911
+ var MANAGED_TEMPLATES = Object.entries(
27912
+ GENERATED_TEMPLATE_CONTENT
27913
+ ).map(([name, versions]) => ({
27914
+ name,
27915
+ description: requireDescription(name),
27916
+ versions: versions.map((version2) => ({
27917
+ version: version2.version,
27918
+ files: version2.files.map((file2) => ({
27919
+ path: file2.path,
27920
+ content: file2.content
27921
+ }))
27922
+ }))
27923
+ }));
27934
27924
  var HardcodedTemplateRegistry = class {
27935
27925
  templates;
27936
27926
  constructor(templates = MANAGED_TEMPLATES) {
@@ -27980,6 +27970,13 @@ var HardcodedTemplateRegistry = class {
27980
27970
  }
27981
27971
  };
27982
27972
  var defaultTemplateRegistry = new HardcodedTemplateRegistry();
27973
+ function requireDescription(name) {
27974
+ const description = TEMPLATE_DESCRIPTIONS[name];
27975
+ if (!description) {
27976
+ throw new Error(`Managed template ${name} is missing a description`);
27977
+ }
27978
+ return description;
27979
+ }
27983
27980
  function toBundleFile(file2) {
27984
27981
  return {
27985
27982
  path: file2.path,
@@ -48192,6 +48189,8 @@ function withClaudeStderrDiagnosticsPointer(error51, capturedStderr) {
48192
48189
  // src/commands/agent-bridge/harness/claude-code/session.ts
48193
48190
  var CLAUDE_AGENT_STARTUP_TIMEOUT_MS = 3e4;
48194
48191
  var CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS = 1e4;
48192
+ var CLAUDE_MCP_REGISTRATION_TIMEOUT_MS = 3e3;
48193
+ var CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS = 100;
48195
48194
  var CLAUDE_STARTUP_PROFILE_HOOK_EVENTS = [
48196
48195
  "Setup",
48197
48196
  "SessionStart",
@@ -48239,6 +48238,12 @@ var ClaudeAgentBridgeSessionImpl = class {
48239
48238
  // message lands only once the interrupted turn has settled its tool_use.
48240
48239
  turnResultCount = 0;
48241
48240
  turnSettlementWaiters = /* @__PURE__ */ new Set();
48241
+ // Memoized MCP-registration gate, keyed by the attached query. A runtime
48242
+ // restart warms a brand-new session + query, so keying off the query object
48243
+ // (rather than a session-lifetime flag) re-arms the gate on every restart:
48244
+ // each fresh warm gates its own first turn, while later turns on the same
48245
+ // query share the resolved promise and never re-poll.
48246
+ mcpRegistrationGate = null;
48242
48247
  constructor(input) {
48243
48248
  const optionsStartedAt = Date.now();
48244
48249
  this.input = input;
@@ -48301,6 +48306,7 @@ var ClaudeAgentBridgeSessionImpl = class {
48301
48306
  return;
48302
48307
  }
48303
48308
  await this.interruptActiveTurnBeforeMessage();
48309
+ await this.awaitMcpRegistration();
48304
48310
  this.enqueueUserMessage(message);
48305
48311
  this.input.runtimeLogger?.info("agent_bridge_claude_send_message_queued", {
48306
48312
  delivery_mode: mode,
@@ -48588,6 +48594,86 @@ var ClaudeAgentBridgeSessionImpl = class {
48588
48594
  }
48589
48595
  }
48590
48596
  }
48597
+ // Why this gate is correct without a provider-backed test (it relies on real
48598
+ // SDK mcpServerStatus() timing that fakes cannot prove):
48599
+ // - Safe-degrade: pollMcpRegistration never rejects — on timeout, a
48600
+ // status-read failure, or a closed session it resolves, so the worst case
48601
+ // is today's immediate injection. The gate can never regress below the
48602
+ // status-quo race; it can only close it.
48603
+ // - Provider-path validation is deferred deliberately: the failure is the
48604
+ // intermittent live-prod race on the chief-of-staff singleton (a real
48605
+ // runtime restart dispatching turn-1 while real mcp__* servers reconnect),
48606
+ // which cannot be reproduced deterministically without live credentials
48607
+ // and a forced restart racing reconnect.
48608
+ // - Live confirmation comes from the agent_bridge_claude_mcp_gate_started/
48609
+ // _ready/_timeout diagnostics on the next real restart, not a CI fake.
48610
+ //
48611
+ // Block until the freshly-warmed query's MCP servers have left `pending`, so
48612
+ // the first turn's prompt is built with the mcp__* tools registered. Memoized
48613
+ // per query: the first send pays the wait, concurrent first sends share it,
48614
+ // and a later turn on the same query returns immediately. Sessions without
48615
+ // configured MCP servers, or whose query failed to attach, skip the gate and
48616
+ // fall through to the existing enqueue/error path unchanged.
48617
+ async awaitMcpRegistration() {
48618
+ if (Object.keys(this.options.mcpServers ?? {}).length === 0) {
48619
+ return;
48620
+ }
48621
+ let query;
48622
+ try {
48623
+ query = await this.ensureRunningQuery();
48624
+ } catch {
48625
+ return;
48626
+ }
48627
+ if (this.mcpRegistrationGate?.query !== query) {
48628
+ this.mcpRegistrationGate = {
48629
+ query,
48630
+ promise: this.pollMcpRegistration(query)
48631
+ };
48632
+ }
48633
+ await this.mcpRegistrationGate.promise;
48634
+ }
48635
+ // Poll the live query's MCP server status until no configured server is still
48636
+ // `pending`, bounded by CLAUDE_MCP_REGISTRATION_TIMEOUT_MS. Terminal states
48637
+ // (connected/failed/needs-auth/disabled) all count as settled, so a server
48638
+ // that cannot connect in this runtime (e.g. an interactively-authed server in
48639
+ // a headless sandbox) never holds the gate to the timeout. Never rejects:
48640
+ // status-read failure, timeout, or a closed session all resolve so the caller
48641
+ // degrades to immediate injection.
48642
+ async pollMcpRegistration(query) {
48643
+ const configuredNames = Object.keys(this.options.mcpServers ?? {});
48644
+ const startedAt = Date.now();
48645
+ const deadline = startedAt + CLAUDE_MCP_REGISTRATION_TIMEOUT_MS;
48646
+ this.input.writeOutput?.(
48647
+ `agent_bridge_claude_mcp_gate_started servers=${configuredNames.length}`
48648
+ );
48649
+ while (true) {
48650
+ if (this.state.kind === "closed") {
48651
+ return;
48652
+ }
48653
+ let statuses;
48654
+ try {
48655
+ statuses = await query.mcpServerStatus();
48656
+ } catch (error51) {
48657
+ this.input.writeOutput?.(
48658
+ `agent_bridge_claude_mcp_gate_status_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
48659
+ );
48660
+ return;
48661
+ }
48662
+ if (mcpServersSettled(statuses, configuredNames)) {
48663
+ this.input.writeOutput?.(
48664
+ `agent_bridge_claude_mcp_gate_ready duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
48665
+ );
48666
+ return;
48667
+ }
48668
+ if (Date.now() >= deadline) {
48669
+ this.input.writeOutput?.(
48670
+ `agent_bridge_claude_mcp_gate_timeout duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
48671
+ );
48672
+ return;
48673
+ }
48674
+ await delay(CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS);
48675
+ }
48676
+ }
48591
48677
  messageProbeContext() {
48592
48678
  return {
48593
48679
  configuredMcpServerNames: Object.keys(this.options.mcpServers ?? {}),
@@ -48717,6 +48803,24 @@ var AsyncMessageQueue = class {
48717
48803
  });
48718
48804
  }
48719
48805
  };
48806
+ function mcpServersSettled(statuses, configuredNames) {
48807
+ const statusByName = new Map(
48808
+ statuses.map((server) => [server.name, server.status])
48809
+ );
48810
+ return configuredNames.every((name) => {
48811
+ const status = statusByName.get(name);
48812
+ return status !== void 0 && status !== "pending";
48813
+ });
48814
+ }
48815
+ function mcpStatusList(statuses) {
48816
+ return statuses.map((server) => `${server.name}:${server.status}`).join(",");
48817
+ }
48818
+ function delay(ms) {
48819
+ return new Promise((resolve) => {
48820
+ const timer = setTimeout(resolve, ms);
48821
+ timer.unref?.();
48822
+ });
48823
+ }
48720
48824
  function claudeAgentUserMessage(message) {
48721
48825
  return {
48722
48826
  type: "user",
package/dist/index.js CHANGED
@@ -19771,7 +19771,46 @@ var init_registry = __esm({
19771
19771
  }
19772
19772
  });
19773
19773
 
19774
+ // ../../packages/schemas/src/templates/content.generated.ts
19775
+ var GENERATED_TEMPLATE_CONTENT;
19776
+ var init_content_generated = __esm({
19777
+ "../../packages/schemas/src/templates/content.generated.ts"() {
19778
+ "use strict";
19779
+ GENERATED_TEMPLATE_CONTENT = {
19780
+ "@auto/onboarding": [
19781
+ {
19782
+ version: "1.0.0",
19783
+ files: [
19784
+ {
19785
+ path: "fragments/onboarding.yaml",
19786
+ content: "# Managed onboarding fragment (@auto/onboarding). Tenant onboarding agents\n# import this to inherit Auto's house onboarding guidance. Tenant fields win\n# on merge, so house tweaks can be layered on top of this base.\nsystemPrompt: |\n You are an Auto onboarding guide. Greet the user warmly, explain that Auto\n lets them compose agents and triggers into automated workflows from simple\n `.auto/` YAML, and guide them to their first deployed workflow. Keep replies\n short and conversational, ask one question at a time, and verify each step\n actually worked before telling the user it did."
19787
+ }
19788
+ ]
19789
+ }
19790
+ ],
19791
+ "@auto/smoke-test": [
19792
+ {
19793
+ version: "1.0.0",
19794
+ files: [
19795
+ {
19796
+ path: "fragments/smoke-test.yaml",
19797
+ content: "# Managed smoke-test fragment (@auto/smoke-test). A minimal, self-contained\n# fixture for verifying that managed templates resolve and inject cleanly.\n# Importing agents inherit this base guidance; tenant fields win on merge.\nsystemPrompt: |\n You are the Auto smoke-test agent, a minimal fixture used to confirm that\n managed templates resolve and inject correctly. When asked to run the smoke\n test, reply with a single short sentence confirming it passed, and do\n nothing else."
19798
+ }
19799
+ ]
19800
+ }
19801
+ ]
19802
+ };
19803
+ }
19804
+ });
19805
+
19774
19806
  // ../../packages/schemas/src/templates/hardcoded.ts
19807
+ function requireDescription(name) {
19808
+ const description = TEMPLATE_DESCRIPTIONS[name];
19809
+ if (!description) {
19810
+ throw new Error(`Managed template ${name} is missing a description`);
19811
+ }
19812
+ return description;
19813
+ }
19775
19814
  function toBundleFile(file2) {
19776
19815
  return {
19777
19816
  path: file2.path,
@@ -19788,64 +19827,29 @@ function toManagedTemplate(template) {
19788
19827
  versions: template.versions.map(toTemplateVersion)
19789
19828
  };
19790
19829
  }
19791
- var ONBOARDING_FRAGMENT_V1, SMOKE_TEST_FRAGMENT_V1, MANAGED_TEMPLATES, HardcodedTemplateRegistry, defaultTemplateRegistry;
19830
+ var TEMPLATE_DESCRIPTIONS, MANAGED_TEMPLATES, HardcodedTemplateRegistry, defaultTemplateRegistry;
19792
19831
  var init_hardcoded = __esm({
19793
19832
  "../../packages/schemas/src/templates/hardcoded.ts"() {
19794
19833
  "use strict";
19834
+ init_content_generated();
19795
19835
  init_registry();
19796
- ONBOARDING_FRAGMENT_V1 = [
19797
- "# Managed onboarding fragment (@auto/onboarding). Tenant onboarding agents",
19798
- "# import this to inherit Auto's house onboarding guidance. Tenant fields win",
19799
- "# on merge, so house tweaks can be layered on top of this base.",
19800
- "systemPrompt: |",
19801
- " You are an Auto onboarding guide. Greet the user warmly, explain that Auto",
19802
- " lets them compose agents and triggers into automated workflows from simple",
19803
- " `.auto/` YAML, and guide them to their first deployed workflow. Keep replies",
19804
- " short and conversational, ask one question at a time, and verify each step",
19805
- " actually worked before telling the user it did."
19806
- ].join("\n");
19807
- SMOKE_TEST_FRAGMENT_V1 = [
19808
- "# Managed smoke-test fragment (@auto/smoke-test). A minimal, self-contained",
19809
- "# fixture for verifying that managed templates resolve and inject cleanly.",
19810
- "# Importing agents inherit this base guidance; tenant fields win on merge.",
19811
- "systemPrompt: |",
19812
- " You are the Auto smoke-test agent, a minimal fixture used to confirm that",
19813
- " managed templates resolve and inject correctly. When asked to run the smoke",
19814
- " test, reply with a single short sentence confirming it passed, and do",
19815
- " nothing else."
19816
- ].join("\n");
19817
- MANAGED_TEMPLATES = [
19818
- {
19819
- name: "@auto/onboarding",
19820
- description: "Auto's house onboarding guidance, importable as a managed template.",
19821
- versions: [
19822
- {
19823
- version: "1.0.0",
19824
- files: [
19825
- {
19826
- path: "fragments/onboarding.yaml",
19827
- content: ONBOARDING_FRAGMENT_V1
19828
- }
19829
- ]
19830
- }
19831
- ]
19832
- },
19833
- {
19834
- name: "@auto/smoke-test",
19835
- description: "A minimal managed-template smoke-test fixture for end-to-end verification.",
19836
- versions: [
19837
- {
19838
- version: "1.0.0",
19839
- files: [
19840
- {
19841
- path: "fragments/smoke-test.yaml",
19842
- content: SMOKE_TEST_FRAGMENT_V1
19843
- }
19844
- ]
19845
- }
19846
- ]
19847
- }
19848
- ];
19836
+ TEMPLATE_DESCRIPTIONS = {
19837
+ "@auto/onboarding": "Auto's house onboarding guidance, importable as a managed template.",
19838
+ "@auto/smoke-test": "A minimal managed-template smoke-test fixture for end-to-end verification."
19839
+ };
19840
+ MANAGED_TEMPLATES = Object.entries(
19841
+ GENERATED_TEMPLATE_CONTENT
19842
+ ).map(([name, versions]) => ({
19843
+ name,
19844
+ description: requireDescription(name),
19845
+ versions: versions.map((version2) => ({
19846
+ version: version2.version,
19847
+ files: version2.files.map((file2) => ({
19848
+ path: file2.path,
19849
+ content: file2.content
19850
+ }))
19851
+ }))
19852
+ }));
19849
19853
  HardcodedTemplateRegistry = class {
19850
19854
  templates;
19851
19855
  constructor(templates = MANAGED_TEMPLATES) {
@@ -22782,7 +22786,7 @@ var init_package = __esm({
22782
22786
  "package.json"() {
22783
22787
  package_default = {
22784
22788
  name: "@autohq/cli",
22785
- version: "0.1.259",
22789
+ version: "0.1.261",
22786
22790
  license: "SEE LICENSE IN README.md",
22787
22791
  publishConfig: {
22788
22792
  access: "public"
@@ -33655,6 +33659,8 @@ function withClaudeStderrDiagnosticsPointer(error51, capturedStderr) {
33655
33659
  // src/commands/agent-bridge/harness/claude-code/session.ts
33656
33660
  var CLAUDE_AGENT_STARTUP_TIMEOUT_MS = 3e4;
33657
33661
  var CLAUDE_INTERRUPT_SETTLE_TIMEOUT_MS = 1e4;
33662
+ var CLAUDE_MCP_REGISTRATION_TIMEOUT_MS = 3e3;
33663
+ var CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS = 100;
33658
33664
  var CLAUDE_STARTUP_PROFILE_HOOK_EVENTS = [
33659
33665
  "Setup",
33660
33666
  "SessionStart",
@@ -33702,6 +33708,12 @@ var ClaudeAgentBridgeSessionImpl = class {
33702
33708
  // message lands only once the interrupted turn has settled its tool_use.
33703
33709
  turnResultCount = 0;
33704
33710
  turnSettlementWaiters = /* @__PURE__ */ new Set();
33711
+ // Memoized MCP-registration gate, keyed by the attached query. A runtime
33712
+ // restart warms a brand-new session + query, so keying off the query object
33713
+ // (rather than a session-lifetime flag) re-arms the gate on every restart:
33714
+ // each fresh warm gates its own first turn, while later turns on the same
33715
+ // query share the resolved promise and never re-poll.
33716
+ mcpRegistrationGate = null;
33705
33717
  constructor(input) {
33706
33718
  const optionsStartedAt = Date.now();
33707
33719
  this.input = input;
@@ -33764,6 +33776,7 @@ var ClaudeAgentBridgeSessionImpl = class {
33764
33776
  return;
33765
33777
  }
33766
33778
  await this.interruptActiveTurnBeforeMessage();
33779
+ await this.awaitMcpRegistration();
33767
33780
  this.enqueueUserMessage(message);
33768
33781
  this.input.runtimeLogger?.info("agent_bridge_claude_send_message_queued", {
33769
33782
  delivery_mode: mode,
@@ -34051,6 +34064,86 @@ var ClaudeAgentBridgeSessionImpl = class {
34051
34064
  }
34052
34065
  }
34053
34066
  }
34067
+ // Why this gate is correct without a provider-backed test (it relies on real
34068
+ // SDK mcpServerStatus() timing that fakes cannot prove):
34069
+ // - Safe-degrade: pollMcpRegistration never rejects — on timeout, a
34070
+ // status-read failure, or a closed session it resolves, so the worst case
34071
+ // is today's immediate injection. The gate can never regress below the
34072
+ // status-quo race; it can only close it.
34073
+ // - Provider-path validation is deferred deliberately: the failure is the
34074
+ // intermittent live-prod race on the chief-of-staff singleton (a real
34075
+ // runtime restart dispatching turn-1 while real mcp__* servers reconnect),
34076
+ // which cannot be reproduced deterministically without live credentials
34077
+ // and a forced restart racing reconnect.
34078
+ // - Live confirmation comes from the agent_bridge_claude_mcp_gate_started/
34079
+ // _ready/_timeout diagnostics on the next real restart, not a CI fake.
34080
+ //
34081
+ // Block until the freshly-warmed query's MCP servers have left `pending`, so
34082
+ // the first turn's prompt is built with the mcp__* tools registered. Memoized
34083
+ // per query: the first send pays the wait, concurrent first sends share it,
34084
+ // and a later turn on the same query returns immediately. Sessions without
34085
+ // configured MCP servers, or whose query failed to attach, skip the gate and
34086
+ // fall through to the existing enqueue/error path unchanged.
34087
+ async awaitMcpRegistration() {
34088
+ if (Object.keys(this.options.mcpServers ?? {}).length === 0) {
34089
+ return;
34090
+ }
34091
+ let query;
34092
+ try {
34093
+ query = await this.ensureRunningQuery();
34094
+ } catch {
34095
+ return;
34096
+ }
34097
+ if (this.mcpRegistrationGate?.query !== query) {
34098
+ this.mcpRegistrationGate = {
34099
+ query,
34100
+ promise: this.pollMcpRegistration(query)
34101
+ };
34102
+ }
34103
+ await this.mcpRegistrationGate.promise;
34104
+ }
34105
+ // Poll the live query's MCP server status until no configured server is still
34106
+ // `pending`, bounded by CLAUDE_MCP_REGISTRATION_TIMEOUT_MS. Terminal states
34107
+ // (connected/failed/needs-auth/disabled) all count as settled, so a server
34108
+ // that cannot connect in this runtime (e.g. an interactively-authed server in
34109
+ // a headless sandbox) never holds the gate to the timeout. Never rejects:
34110
+ // status-read failure, timeout, or a closed session all resolve so the caller
34111
+ // degrades to immediate injection.
34112
+ async pollMcpRegistration(query) {
34113
+ const configuredNames = Object.keys(this.options.mcpServers ?? {});
34114
+ const startedAt = Date.now();
34115
+ const deadline = startedAt + CLAUDE_MCP_REGISTRATION_TIMEOUT_MS;
34116
+ this.input.writeOutput?.(
34117
+ `agent_bridge_claude_mcp_gate_started servers=${configuredNames.length}`
34118
+ );
34119
+ while (true) {
34120
+ if (this.state.kind === "closed") {
34121
+ return;
34122
+ }
34123
+ let statuses;
34124
+ try {
34125
+ statuses = await query.mcpServerStatus();
34126
+ } catch (error51) {
34127
+ this.input.writeOutput?.(
34128
+ `agent_bridge_claude_mcp_gate_status_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
34129
+ );
34130
+ return;
34131
+ }
34132
+ if (mcpServersSettled(statuses, configuredNames)) {
34133
+ this.input.writeOutput?.(
34134
+ `agent_bridge_claude_mcp_gate_ready duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
34135
+ );
34136
+ return;
34137
+ }
34138
+ if (Date.now() >= deadline) {
34139
+ this.input.writeOutput?.(
34140
+ `agent_bridge_claude_mcp_gate_timeout duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
34141
+ );
34142
+ return;
34143
+ }
34144
+ await delay(CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS);
34145
+ }
34146
+ }
34054
34147
  messageProbeContext() {
34055
34148
  return {
34056
34149
  configuredMcpServerNames: Object.keys(this.options.mcpServers ?? {}),
@@ -34180,6 +34273,24 @@ var AsyncMessageQueue = class {
34180
34273
  });
34181
34274
  }
34182
34275
  };
34276
+ function mcpServersSettled(statuses, configuredNames) {
34277
+ const statusByName = new Map(
34278
+ statuses.map((server) => [server.name, server.status])
34279
+ );
34280
+ return configuredNames.every((name) => {
34281
+ const status = statusByName.get(name);
34282
+ return status !== void 0 && status !== "pending";
34283
+ });
34284
+ }
34285
+ function mcpStatusList(statuses) {
34286
+ return statuses.map((server) => `${server.name}:${server.status}`).join(",");
34287
+ }
34288
+ function delay(ms) {
34289
+ return new Promise((resolve4) => {
34290
+ const timer = setTimeout(resolve4, ms);
34291
+ timer.unref?.();
34292
+ });
34293
+ }
34183
34294
  function claudeAgentUserMessage(message) {
34184
34295
  return {
34185
34296
  type: "user",
@@ -37819,7 +37930,7 @@ function selectLastLines(content, count) {
37819
37930
  async function followRuntimeLog(input) {
37820
37931
  let offset = input.fromByte;
37821
37932
  while (!input.signal?.aborted) {
37822
- await delay(input.pollIntervalMs, input.signal);
37933
+ await delay2(input.pollIntervalMs, input.signal);
37823
37934
  if (input.signal?.aborted) {
37824
37935
  return;
37825
37936
  }
@@ -37854,7 +37965,7 @@ function readByteRange(path2, offset, length) {
37854
37965
  }
37855
37966
  return buffer.toString("utf8");
37856
37967
  }
37857
- function delay(ms, signal) {
37968
+ function delay2(ms, signal) {
37858
37969
  return new Promise((resolve4) => {
37859
37970
  const timer = setTimeout(resolve4, ms);
37860
37971
  signal?.addEventListener(
@@ -38800,10 +38911,10 @@ async function runConsole(input) {
38800
38911
  input.writeOutput(
38801
38912
  `stream interrupted: ${error51 instanceof Error ? error51.message : String(error51)}`
38802
38913
  );
38803
- await delay2(1e3, abort.signal);
38914
+ await delay3(1e3, abort.signal);
38804
38915
  continue;
38805
38916
  }
38806
- await delay2(250, abort.signal);
38917
+ await delay3(250, abort.signal);
38807
38918
  }
38808
38919
  })();
38809
38920
  const consoleInput = createInteractiveInputController({
@@ -38883,7 +38994,7 @@ function createInteractiveInputController(input) {
38883
38994
  })();
38884
38995
  return { done, close };
38885
38996
  }
38886
- function delay2(ms, signal) {
38997
+ function delay3(ms, signal) {
38887
38998
  if (signal.aborted) {
38888
38999
  return Promise.resolve();
38889
39000
  }
@@ -39204,7 +39315,7 @@ async function pollUntilAwaiting(input) {
39204
39315
  if (firstPoll) {
39205
39316
  firstPoll = false;
39206
39317
  } else {
39207
- await delay3(
39318
+ await delay4(
39208
39319
  Math.min(input.pollIntervalMs, Math.max(deadline - Date.now(), 0))
39209
39320
  );
39210
39321
  }
@@ -39218,7 +39329,7 @@ async function pollUntilAwaiting(input) {
39218
39329
  status = session.status;
39219
39330
  }
39220
39331
  }
39221
- function delay3(ms) {
39332
+ function delay4(ms) {
39222
39333
  if (ms <= 0) {
39223
39334
  return Promise.resolve();
39224
39335
  }
@@ -39278,7 +39389,7 @@ async function streamDiagnosticsBestEffort(input) {
39278
39389
  }
39279
39390
  }
39280
39391
  async function settleDiagnostics(done) {
39281
- await Promise.race([done, delay3(DIAGNOSTICS_SHUTDOWN_WAIT_MS)]);
39392
+ await Promise.race([done, delay4(DIAGNOSTICS_SHUTDOWN_WAIT_MS)]);
39282
39393
  }
39283
39394
  function writeBenchmarkResult(context, result, json2) {
39284
39395
  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.261",
4
4
  "license": "SEE LICENSE IN README.md",
5
5
  "publishConfig": {
6
6
  "access": "public"