@autohq/cli 0.1.399 → 0.1.401

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.
@@ -30819,7 +30819,7 @@ Object.assign(lookup, {
30819
30819
  // package.json
30820
30820
  var package_default = {
30821
30821
  name: "@autohq/cli",
30822
- version: "0.1.399",
30822
+ version: "0.1.401",
30823
30823
  license: "SEE LICENSE IN README.md",
30824
30824
  publishConfig: {
30825
30825
  access: "public"
@@ -30912,6 +30912,7 @@ var AgentBridgeOutputAckTimeoutError = class extends Error {
30912
30912
  this.name = "AgentBridgeOutputAckTimeoutError";
30913
30913
  }
30914
30914
  };
30915
+ var AGENT_BRIDGE_TERMINAL_AUTH_DRAIN_TIMEOUT_MS = 6e4;
30915
30916
  async function runAgentBridgeSocket(options) {
30916
30917
  const socket = lookup(`${options.bridgeUrl}${RUNTIME_BRIDGE_NAMESPACE}`, {
30917
30918
  auth: {
@@ -30923,6 +30924,13 @@ async function runAgentBridgeSocket(options) {
30923
30924
  });
30924
30925
  let handler = null;
30925
30926
  const reconnectLoop = createReconnectLoop(socket);
30927
+ const terminalAuthDrain = createTerminalAuthDrainController({
30928
+ socket,
30929
+ token: options.token,
30930
+ getHandler: () => handler,
30931
+ writeOutput: options.writeOutput,
30932
+ runtimeLogger: options.runtimeLogger
30933
+ });
30926
30934
  let hasConnected = false;
30927
30935
  await new Promise((resolve2, reject) => {
30928
30936
  const shutdown = () => {
@@ -30934,15 +30942,23 @@ async function runAgentBridgeSocket(options) {
30934
30942
  process.once("SIGINT", shutdown);
30935
30943
  process.once("SIGTERM", shutdown);
30936
30944
  socket.on("connect_error", (error51) => {
30945
+ if (terminalAuthDrain.handleConnectError(error51)) {
30946
+ return;
30947
+ }
30937
30948
  if (!hasConnected) {
30938
30949
  reject(error51);
30939
30950
  return;
30940
30951
  }
30941
30952
  if (isTerminalAuthError(error51)) {
30942
30953
  reconnectLoop.stop();
30943
- handler?.shutdown?.();
30944
- socket.disconnect();
30945
- reject(new AgentBridgeTerminalAuthError(error51.message));
30954
+ void terminalAuthDrain.begin().catch((drainError) => {
30955
+ options.writeOutput?.(
30956
+ `agent_bridge_terminal_auth_drain_failed error=${errorMessage(drainError)}`
30957
+ );
30958
+ }).finally(() => {
30959
+ socket.disconnect();
30960
+ reject(new AgentBridgeTerminalAuthError(error51.message));
30961
+ });
30946
30962
  return;
30947
30963
  }
30948
30964
  options.writeOutput?.(
@@ -30955,6 +30971,9 @@ async function runAgentBridgeSocket(options) {
30955
30971
  return;
30956
30972
  }
30957
30973
  options.writeOutput?.(`agent_bridge_disconnected reason=${reason}`);
30974
+ if (terminalAuthDrain.suppressesReconnect()) {
30975
+ return;
30976
+ }
30958
30977
  reconnectLoop.start();
30959
30978
  });
30960
30979
  socket.on(
@@ -30993,6 +31012,9 @@ async function runAgentBridgeSocket(options) {
30993
31012
  })
30994
31013
  );
30995
31014
  socket.on(RUNTIME_BRIDGE_CONNECTED_EVENT, (rawContext) => {
31015
+ if (terminalAuthDrain.handleConnected(rawContext)) {
31016
+ return;
31017
+ }
30996
31018
  const context = RuntimeBridgeConnectedPayloadSchema.parse(rawContext);
30997
31019
  if (!handler) {
30998
31020
  reject(new Error("Bridge connected before bootstrap completed"));
@@ -31237,6 +31259,113 @@ function createReconnectLoop(socket) {
31237
31259
  };
31238
31260
  return { start, stop };
31239
31261
  }
31262
+ function createTerminalAuthDrainController(input) {
31263
+ let terminalAuthExit = false;
31264
+ let state = null;
31265
+ const settle = (callback) => {
31266
+ const current = state;
31267
+ if (!current) {
31268
+ return;
31269
+ }
31270
+ clearTimeout(current.timeout);
31271
+ state = null;
31272
+ callback();
31273
+ };
31274
+ const begin = () => {
31275
+ terminalAuthExit = true;
31276
+ if (state) {
31277
+ return state.promise;
31278
+ }
31279
+ const handler = input.getHandler();
31280
+ handler?.shutdown?.();
31281
+ if (!handler) {
31282
+ input.writeOutput?.(
31283
+ "agent_bridge_terminal_auth_drain_skipped reason=no_handler"
31284
+ );
31285
+ return Promise.resolve();
31286
+ }
31287
+ const startedAt = Date.now();
31288
+ let resolveDrain = () => {
31289
+ };
31290
+ let rejectDrain = () => {
31291
+ };
31292
+ const promise2 = new Promise((resolve2, reject) => {
31293
+ resolveDrain = resolve2;
31294
+ rejectDrain = reject;
31295
+ });
31296
+ const timeout = setTimeout(() => {
31297
+ settle(() => {
31298
+ rejectDrain(
31299
+ new Error(
31300
+ `Timed out draining pending bridge output after terminal auth in ${AGENT_BRIDGE_TERMINAL_AUTH_DRAIN_TIMEOUT_MS}ms`
31301
+ )
31302
+ );
31303
+ });
31304
+ }, AGENT_BRIDGE_TERMINAL_AUTH_DRAIN_TIMEOUT_MS);
31305
+ timeout.unref?.();
31306
+ state = {
31307
+ promise: promise2,
31308
+ reject: rejectDrain,
31309
+ resolve: resolveDrain,
31310
+ startedAt,
31311
+ timeout
31312
+ };
31313
+ input.writeOutput?.("agent_bridge_terminal_auth_drain_started");
31314
+ input.runtimeLogger?.warn("agent_bridge_terminal_auth_drain_started");
31315
+ input.socket.auth = { token: input.token, terminalDrain: true };
31316
+ input.socket.connect();
31317
+ return promise2;
31318
+ };
31319
+ const handleConnected = (rawContext) => {
31320
+ const current = state;
31321
+ if (!current) {
31322
+ return false;
31323
+ }
31324
+ void (async () => {
31325
+ const context = RuntimeBridgeConnectedPayloadSchema.parse(rawContext);
31326
+ input.writeOutput?.(
31327
+ `agent_bridge_terminal_auth_drain_connected session_id=${context.sessionId} runtime_id=${context.runtimeId} bridge_lease_id=${context.bridgeLeaseId}`
31328
+ );
31329
+ input.runtimeLogger?.warn("agent_bridge_terminal_auth_drain_connected", {
31330
+ session_id: context.sessionId,
31331
+ runtime_id: context.runtimeId,
31332
+ bridge_lease_id: context.bridgeLeaseId
31333
+ });
31334
+ const handler = input.getHandler();
31335
+ if (!handler) {
31336
+ throw new Error("Terminal auth drain connected without a handler");
31337
+ }
31338
+ await handler.replayPendingOutputs();
31339
+ settle(() => {
31340
+ input.writeOutput?.(
31341
+ `agent_bridge_terminal_auth_drain_completed duration_ms=${Date.now() - current.startedAt}`
31342
+ );
31343
+ input.runtimeLogger?.warn(
31344
+ "agent_bridge_terminal_auth_drain_completed",
31345
+ { duration_ms: Date.now() - current.startedAt }
31346
+ );
31347
+ current.resolve();
31348
+ });
31349
+ })().catch((error51) => {
31350
+ settle(() => current.reject(error51));
31351
+ });
31352
+ return true;
31353
+ };
31354
+ const handleConnectError = (error51) => {
31355
+ const current = state;
31356
+ if (!current) {
31357
+ return false;
31358
+ }
31359
+ settle(() => current.reject(error51));
31360
+ return true;
31361
+ };
31362
+ return {
31363
+ begin,
31364
+ handleConnected,
31365
+ handleConnectError,
31366
+ suppressesReconnect: () => terminalAuthExit || state !== null
31367
+ };
31368
+ }
31240
31369
  function bridgeForwardingHeaders(url3) {
31241
31370
  if (!isNgrokFreeUrl(url3)) {
31242
31371
  return {};
@@ -33809,6 +33938,7 @@ var SESSION_BINDING_SOURCES = [
33809
33938
  ];
33810
33939
  var SESSION_BINDING_STATUSES = ["active", "released"];
33811
33940
  var SESSION_BINDING_RELEASE_POLICIES = ["manual", "held"];
33941
+ var SESSION_BINDING_CONTINUITIES = ["session", "agent"];
33812
33942
  var SESSION_BINDING_RELEASED_BY = [
33813
33943
  "agent_tool",
33814
33944
  "archive",
@@ -33825,6 +33955,9 @@ var SessionBindingStatusSchema = external_exports.enum(SESSION_BINDING_STATUSES)
33825
33955
  var SessionBindingReleasePolicySchema = external_exports.enum(
33826
33956
  SESSION_BINDING_RELEASE_POLICIES
33827
33957
  );
33958
+ var SessionBindingContinuitySchema = external_exports.enum(
33959
+ SESSION_BINDING_CONTINUITIES
33960
+ );
33828
33961
  var SessionBindingReleasedBySchema = external_exports.enum(
33829
33962
  SESSION_BINDING_RELEASED_BY
33830
33963
  );
@@ -34496,6 +34629,7 @@ var AgentIdentitySchema = external_exports.object({
34496
34629
  var BINDING_LIFECYCLE_VALUES = ["manual", "held"];
34497
34630
  var BindingLifecycleSpecSchema = external_exports.object({
34498
34631
  lifecycle: external_exports.enum(BINDING_LIFECYCLE_VALUES).default("manual"),
34632
+ continuity: external_exports.enum(["session", "agent"]).default("session"),
34499
34633
  bind: BindingAutoBindSchema.optional()
34500
34634
  }).strict();
34501
34635
  var AgentBindingsSchema = external_exports.record(external_exports.string(), BindingLifecycleSpecSchema).superRefine((bindings, context) => {
@@ -35083,6 +35217,13 @@ var SessionUploadAttachmentRefSchema = external_exports.object({
35083
35217
  mimeType: external_exports.string().trim().min(1),
35084
35218
  sizeBytes: external_exports.number().int().nonnegative()
35085
35219
  });
35220
+ var StagedSessionUploadRefSchema = external_exports.object({
35221
+ id: external_exports.string().trim().min(1),
35222
+ stagedStorageKey: external_exports.string().trim().min(1),
35223
+ filename: external_exports.string().trim().min(1),
35224
+ mimeType: external_exports.string().trim().min(1),
35225
+ sizeBytes: external_exports.number().int().nonnegative()
35226
+ });
35086
35227
  var SessionUploadDownloadUrlResponseSchema = external_exports.object({
35087
35228
  downloadUrl: external_exports.string().url(),
35088
35229
  expiresAt: external_exports.string().datetime(),
@@ -35306,7 +35447,13 @@ var RunStartWithMessageCommandPayloadSchema = external_exports.object({
35306
35447
  kind: external_exports.literal("startWithMessage"),
35307
35448
  message: external_exports.string().trim().min(1),
35308
35449
  model: AgentModelSelectionSchema.optional(),
35309
- reasoningEffort: AgentReasoningEffortSchema.optional()
35450
+ reasoningEffort: AgentReasoningEffortSchema.optional(),
35451
+ // File uploads claimed from the pre-session staging area (FAB composer).
35452
+ // The session-creation endpoint copies each staged blob to a session-
35453
+ // scoped key and passes the claimed refs here so the start command's
35454
+ // delivery appends attachment download lines — the same path as a normal
35455
+ // message command. Optional so existing callers stay valid.
35456
+ attachments: external_exports.array(SessionUploadAttachmentRefSchema).optional()
35310
35457
  }).strict();
35311
35458
  var SessionPersistedCommandPayloadSchema = external_exports.union([
35312
35459
  RunMessageCommandPayloadSchema,
@@ -35483,7 +35630,17 @@ var ManualSessionRequestSchema = external_exports.object({
35483
35630
  * session's one idempotent start command and never renders as a user chat
35484
35631
  * bubble; the agent's greeting is the first visible content.
35485
35632
  */
35486
- kickoff: external_exports.boolean().optional()
35633
+ kickoff: external_exports.boolean().optional(),
35634
+ /**
35635
+ * Staged (pre-session) file uploads to claim for the session's initial
35636
+ * message. Each ref's `stagedStorageKey` points at a blob uploaded before
35637
+ * the session existed (the FAB composer flow); the session-creation
35638
+ * endpoint copies each to a session-scoped key and attaches the resulting
35639
+ * `SessionUploadAttachmentRef` to the start command so the initial message
35640
+ * carries the files end-to-end — the same delivery path as a normal
35641
+ * session-composer attachment.
35642
+ */
35643
+ attachments: external_exports.array(StagedSessionUploadRefSchema).optional()
35487
35644
  });
35488
35645
  var SessionArchiveRequestSchema = external_exports.object({
35489
35646
  archived: external_exports.boolean()
@@ -36387,6 +36544,7 @@ var RunBindingRecordSchema = external_exports.object({
36387
36544
  source: SessionBindingSourceSchema,
36388
36545
  status: SessionBindingStatusSchema,
36389
36546
  releasePolicy: SessionBindingReleasePolicySchema,
36547
+ continuity: SessionBindingContinuitySchema,
36390
36548
  releasedBy: SessionBindingReleasedBySchema.nullable(),
36391
36549
  payload: JsonValueSchema2.nullable(),
36392
36550
  createdAt: external_exports.string().datetime(),
package/dist/index.js CHANGED
@@ -17895,7 +17895,7 @@ var init_secrets = __esm({
17895
17895
  });
17896
17896
 
17897
17897
  // ../../packages/schemas/src/session-bindings.ts
17898
- var BINDING_TARGET_TYPES, TRIGGER_BINDING_TARGET_TYPES, SESSION_BINDING_SOURCES, SESSION_BINDING_STATUSES, SESSION_BINDING_RELEASE_POLICIES, SESSION_BINDING_RELEASED_BY, BindingTargetTypeSchema, TriggerBindingTargetTypeSchema, SessionBindingSourceSchema, SessionBindingStatusSchema, SessionBindingReleasePolicySchema, SessionBindingReleasedBySchema, BindingTargetSchema, BINDING_AUTO_BIND_VALUES, BindingAutoBindSchema;
17898
+ var BINDING_TARGET_TYPES, TRIGGER_BINDING_TARGET_TYPES, SESSION_BINDING_SOURCES, SESSION_BINDING_STATUSES, SESSION_BINDING_RELEASE_POLICIES, SESSION_BINDING_CONTINUITIES, SESSION_BINDING_RELEASED_BY, BindingTargetTypeSchema, TriggerBindingTargetTypeSchema, SessionBindingSourceSchema, SessionBindingStatusSchema, SessionBindingReleasePolicySchema, SessionBindingContinuitySchema, SessionBindingReleasedBySchema, BindingTargetSchema, BINDING_AUTO_BIND_VALUES, BindingAutoBindSchema;
17899
17899
  var init_session_bindings = __esm({
17900
17900
  "../../packages/schemas/src/session-bindings.ts"() {
17901
17901
  "use strict";
@@ -17921,6 +17921,7 @@ var init_session_bindings = __esm({
17921
17921
  ];
17922
17922
  SESSION_BINDING_STATUSES = ["active", "released"];
17923
17923
  SESSION_BINDING_RELEASE_POLICIES = ["manual", "held"];
17924
+ SESSION_BINDING_CONTINUITIES = ["session", "agent"];
17924
17925
  SESSION_BINDING_RELEASED_BY = [
17925
17926
  "agent_tool",
17926
17927
  "archive",
@@ -17937,6 +17938,9 @@ var init_session_bindings = __esm({
17937
17938
  SessionBindingReleasePolicySchema = external_exports.enum(
17938
17939
  SESSION_BINDING_RELEASE_POLICIES
17939
17940
  );
17941
+ SessionBindingContinuitySchema = external_exports.enum(
17942
+ SESSION_BINDING_CONTINUITIES
17943
+ );
17940
17944
  SessionBindingReleasedBySchema = external_exports.enum(
17941
17945
  SESSION_BINDING_RELEASED_BY
17942
17946
  );
@@ -18921,6 +18925,7 @@ var init_agents = __esm({
18921
18925
  BINDING_LIFECYCLE_VALUES = ["manual", "held"];
18922
18926
  BindingLifecycleSpecSchema = external_exports.object({
18923
18927
  lifecycle: external_exports.enum(BINDING_LIFECYCLE_VALUES).default("manual"),
18928
+ continuity: external_exports.enum(["session", "agent"]).default("session"),
18924
18929
  bind: BindingAutoBindSchema.optional()
18925
18930
  }).strict();
18926
18931
  AgentBindingsSchema = external_exports.record(external_exports.string(), BindingLifecycleSpecSchema).superRefine((bindings, context) => {
@@ -19300,7 +19305,7 @@ var init_pool_replace = __esm({
19300
19305
  });
19301
19306
 
19302
19307
  // ../../packages/schemas/src/session-uploads.ts
19303
- var SESSION_UPLOAD_MAX_SIZE_BYTES, SESSION_UPLOAD_DOWNLOAD_TTL_MS, SessionUploadPresignRequestSchema, SessionUploadPresignResponseSchema, SessionUploadAttachmentRefSchema, SessionUploadDownloadUrlResponseSchema;
19308
+ var SESSION_UPLOAD_MAX_SIZE_BYTES, SESSION_UPLOAD_DOWNLOAD_TTL_MS, SessionUploadPresignRequestSchema, SessionUploadPresignResponseSchema, SessionUploadAttachmentRefSchema, StagedSessionUploadRefSchema, SessionUploadDownloadUrlResponseSchema;
19304
19309
  var init_session_uploads = __esm({
19305
19310
  "../../packages/schemas/src/session-uploads.ts"() {
19306
19311
  "use strict";
@@ -19329,6 +19334,13 @@ var init_session_uploads = __esm({
19329
19334
  mimeType: external_exports.string().trim().min(1),
19330
19335
  sizeBytes: external_exports.number().int().nonnegative()
19331
19336
  });
19337
+ StagedSessionUploadRefSchema = external_exports.object({
19338
+ id: external_exports.string().trim().min(1),
19339
+ stagedStorageKey: external_exports.string().trim().min(1),
19340
+ filename: external_exports.string().trim().min(1),
19341
+ mimeType: external_exports.string().trim().min(1),
19342
+ sizeBytes: external_exports.number().int().nonnegative()
19343
+ });
19332
19344
  SessionUploadDownloadUrlResponseSchema = external_exports.object({
19333
19345
  downloadUrl: external_exports.string().url(),
19334
19346
  expiresAt: external_exports.string().datetime(),
@@ -19566,7 +19578,13 @@ var init_session_commands = __esm({
19566
19578
  kind: external_exports.literal("startWithMessage"),
19567
19579
  message: external_exports.string().trim().min(1),
19568
19580
  model: AgentModelSelectionSchema.optional(),
19569
- reasoningEffort: AgentReasoningEffortSchema.optional()
19581
+ reasoningEffort: AgentReasoningEffortSchema.optional(),
19582
+ // File uploads claimed from the pre-session staging area (FAB composer).
19583
+ // The session-creation endpoint copies each staged blob to a session-
19584
+ // scoped key and passes the claimed refs here so the start command's
19585
+ // delivery appends attachment download lines — the same path as a normal
19586
+ // message command. Optional so existing callers stay valid.
19587
+ attachments: external_exports.array(SessionUploadAttachmentRefSchema).optional()
19570
19588
  }).strict();
19571
19589
  SessionPersistedCommandPayloadSchema = external_exports.union([
19572
19590
  RunMessageCommandPayloadSchema,
@@ -19708,6 +19726,7 @@ var init_sessions = __esm({
19708
19726
  init_primitives();
19709
19727
  init_requester();
19710
19728
  init_session_commands();
19729
+ init_session_uploads();
19711
19730
  init_tools();
19712
19731
  SESSION_STATUSES = [
19713
19732
  "queued",
@@ -19765,7 +19784,17 @@ var init_sessions = __esm({
19765
19784
  * session's one idempotent start command and never renders as a user chat
19766
19785
  * bubble; the agent's greeting is the first visible content.
19767
19786
  */
19768
- kickoff: external_exports.boolean().optional()
19787
+ kickoff: external_exports.boolean().optional(),
19788
+ /**
19789
+ * Staged (pre-session) file uploads to claim for the session's initial
19790
+ * message. Each ref's `stagedStorageKey` points at a blob uploaded before
19791
+ * the session existed (the FAB composer flow); the session-creation
19792
+ * endpoint copies each to a session-scoped key and attaches the resulting
19793
+ * `SessionUploadAttachmentRef` to the start command so the initial message
19794
+ * carries the files end-to-end — the same delivery path as a normal
19795
+ * session-composer attachment.
19796
+ */
19797
+ attachments: external_exports.array(StagedSessionUploadRefSchema).optional()
19769
19798
  });
19770
19799
  SessionArchiveRequestSchema = external_exports.object({
19771
19800
  archived: external_exports.boolean()
@@ -20785,6 +20814,7 @@ var init_session_introspection = __esm({
20785
20814
  source: SessionBindingSourceSchema,
20786
20815
  status: SessionBindingStatusSchema,
20787
20816
  releasePolicy: SessionBindingReleasePolicySchema,
20817
+ continuity: SessionBindingContinuitySchema,
20788
20818
  releasedBy: SessionBindingReleasedBySchema.nullable(),
20789
20819
  payload: JsonValueSchema.nullable(),
20790
20820
  createdAt: external_exports.string().datetime(),
@@ -35042,7 +35072,7 @@ var init_package = __esm({
35042
35072
  "package.json"() {
35043
35073
  package_default = {
35044
35074
  name: "@autohq/cli",
35045
- version: "0.1.399",
35075
+ version: "0.1.401",
35046
35076
  license: "SEE LICENSE IN README.md",
35047
35077
  publishConfig: {
35048
35078
  access: "public"
@@ -36882,7 +36912,8 @@ function readApplyDocument(path2, document, fileIndex, contextVariables) {
36882
36912
  );
36883
36913
  return result.resources.map((resource) => ({
36884
36914
  resource,
36885
- generatedFromAgent: resource !== result.resource
36915
+ generatedFromAgent: resource !== result.resource,
36916
+ sourcePath: normalizeSourcePath(path2)
36886
36917
  }));
36887
36918
  }
36888
36919
  function validateAgentFragmentDocument(document, path2, context) {
@@ -36931,12 +36962,16 @@ function dedupeGeneratedResources(records) {
36931
36962
  }
36932
36963
  if (stableResource(resource) !== stableResource(existing.resource)) {
36933
36964
  throw new Error(
36934
- `Conflicting generated resource "${key}" from agent authoring. Inline generated resources must be identical when they share a name.`
36965
+ conflictingGeneratedResourceMessage(key, existing, record2)
36935
36966
  );
36936
36967
  }
36937
36968
  }
36938
36969
  return deduped.map((record2) => record2.resource);
36939
36970
  }
36971
+ function conflictingGeneratedResourceMessage(key, existing, record2) {
36972
+ const location = existing.sourcePath === record2.sourcePath ? `defined more than once in "${existing.sourcePath}" with different content` : `defined differently in "${existing.sourcePath}" and "${record2.sourcePath}"`;
36973
+ return `Conflicting generated resource "${key}" from agent authoring: ${location}. Inline generated resources must be identical when they share a name.`;
36974
+ }
36940
36975
  function compileAgentDocumentValue(document, path2, fileIndex, contextVariables) {
36941
36976
  return compileAgentDraftResources(
36942
36977
  compileAgentDocument2(document, path2, {
@@ -37298,7 +37333,8 @@ function readProjectApplyDirectorySource(input) {
37298
37333
  resourceRecords.push(
37299
37334
  ...request.resources.map((resource) => ({
37300
37335
  resource,
37301
- generatedFromAgent: resource.kind !== RESOURCE_KIND_AGENT
37336
+ generatedFromAgent: resource.kind !== RESOURCE_KIND_AGENT,
37337
+ sourcePath: file2.path
37302
37338
  }))
37303
37339
  );
37304
37340
  }
@@ -45487,6 +45523,7 @@ var AgentBridgeOutputAckTimeoutError = class extends Error {
45487
45523
  this.name = "AgentBridgeOutputAckTimeoutError";
45488
45524
  }
45489
45525
  };
45526
+ var AGENT_BRIDGE_TERMINAL_AUTH_DRAIN_TIMEOUT_MS = 6e4;
45490
45527
  async function runAgentBridgeSocket(options) {
45491
45528
  const socket = io(`${options.bridgeUrl}${RUNTIME_BRIDGE_NAMESPACE}`, {
45492
45529
  auth: {
@@ -45498,6 +45535,13 @@ async function runAgentBridgeSocket(options) {
45498
45535
  });
45499
45536
  let handler = null;
45500
45537
  const reconnectLoop = createReconnectLoop(socket);
45538
+ const terminalAuthDrain = createTerminalAuthDrainController({
45539
+ socket,
45540
+ token: options.token,
45541
+ getHandler: () => handler,
45542
+ writeOutput: options.writeOutput,
45543
+ runtimeLogger: options.runtimeLogger
45544
+ });
45501
45545
  let hasConnected = false;
45502
45546
  await new Promise((resolve4, reject) => {
45503
45547
  const shutdown = () => {
@@ -45509,15 +45553,23 @@ async function runAgentBridgeSocket(options) {
45509
45553
  process.once("SIGINT", shutdown);
45510
45554
  process.once("SIGTERM", shutdown);
45511
45555
  socket.on("connect_error", (error51) => {
45556
+ if (terminalAuthDrain.handleConnectError(error51)) {
45557
+ return;
45558
+ }
45512
45559
  if (!hasConnected) {
45513
45560
  reject(error51);
45514
45561
  return;
45515
45562
  }
45516
45563
  if (isTerminalAuthError(error51)) {
45517
45564
  reconnectLoop.stop();
45518
- handler?.shutdown?.();
45519
- socket.disconnect();
45520
- reject(new AgentBridgeTerminalAuthError(error51.message));
45565
+ void terminalAuthDrain.begin().catch((drainError) => {
45566
+ options.writeOutput?.(
45567
+ `agent_bridge_terminal_auth_drain_failed error=${errorMessage(drainError)}`
45568
+ );
45569
+ }).finally(() => {
45570
+ socket.disconnect();
45571
+ reject(new AgentBridgeTerminalAuthError(error51.message));
45572
+ });
45521
45573
  return;
45522
45574
  }
45523
45575
  options.writeOutput?.(
@@ -45530,6 +45582,9 @@ async function runAgentBridgeSocket(options) {
45530
45582
  return;
45531
45583
  }
45532
45584
  options.writeOutput?.(`agent_bridge_disconnected reason=${reason}`);
45585
+ if (terminalAuthDrain.suppressesReconnect()) {
45586
+ return;
45587
+ }
45533
45588
  reconnectLoop.start();
45534
45589
  });
45535
45590
  socket.on(
@@ -45568,6 +45623,9 @@ async function runAgentBridgeSocket(options) {
45568
45623
  })
45569
45624
  );
45570
45625
  socket.on(RUNTIME_BRIDGE_CONNECTED_EVENT, (rawContext) => {
45626
+ if (terminalAuthDrain.handleConnected(rawContext)) {
45627
+ return;
45628
+ }
45571
45629
  const context = RuntimeBridgeConnectedPayloadSchema.parse(rawContext);
45572
45630
  if (!handler) {
45573
45631
  reject(new Error("Bridge connected before bootstrap completed"));
@@ -45812,6 +45870,113 @@ function createReconnectLoop(socket) {
45812
45870
  };
45813
45871
  return { start, stop };
45814
45872
  }
45873
+ function createTerminalAuthDrainController(input) {
45874
+ let terminalAuthExit = false;
45875
+ let state = null;
45876
+ const settle = (callback) => {
45877
+ const current = state;
45878
+ if (!current) {
45879
+ return;
45880
+ }
45881
+ clearTimeout(current.timeout);
45882
+ state = null;
45883
+ callback();
45884
+ };
45885
+ const begin = () => {
45886
+ terminalAuthExit = true;
45887
+ if (state) {
45888
+ return state.promise;
45889
+ }
45890
+ const handler = input.getHandler();
45891
+ handler?.shutdown?.();
45892
+ if (!handler) {
45893
+ input.writeOutput?.(
45894
+ "agent_bridge_terminal_auth_drain_skipped reason=no_handler"
45895
+ );
45896
+ return Promise.resolve();
45897
+ }
45898
+ const startedAt = Date.now();
45899
+ let resolveDrain = () => {
45900
+ };
45901
+ let rejectDrain = () => {
45902
+ };
45903
+ const promise2 = new Promise((resolve4, reject) => {
45904
+ resolveDrain = resolve4;
45905
+ rejectDrain = reject;
45906
+ });
45907
+ const timeout = setTimeout(() => {
45908
+ settle(() => {
45909
+ rejectDrain(
45910
+ new Error(
45911
+ `Timed out draining pending bridge output after terminal auth in ${AGENT_BRIDGE_TERMINAL_AUTH_DRAIN_TIMEOUT_MS}ms`
45912
+ )
45913
+ );
45914
+ });
45915
+ }, AGENT_BRIDGE_TERMINAL_AUTH_DRAIN_TIMEOUT_MS);
45916
+ timeout.unref?.();
45917
+ state = {
45918
+ promise: promise2,
45919
+ reject: rejectDrain,
45920
+ resolve: resolveDrain,
45921
+ startedAt,
45922
+ timeout
45923
+ };
45924
+ input.writeOutput?.("agent_bridge_terminal_auth_drain_started");
45925
+ input.runtimeLogger?.warn("agent_bridge_terminal_auth_drain_started");
45926
+ input.socket.auth = { token: input.token, terminalDrain: true };
45927
+ input.socket.connect();
45928
+ return promise2;
45929
+ };
45930
+ const handleConnected = (rawContext) => {
45931
+ const current = state;
45932
+ if (!current) {
45933
+ return false;
45934
+ }
45935
+ void (async () => {
45936
+ const context = RuntimeBridgeConnectedPayloadSchema.parse(rawContext);
45937
+ input.writeOutput?.(
45938
+ `agent_bridge_terminal_auth_drain_connected session_id=${context.sessionId} runtime_id=${context.runtimeId} bridge_lease_id=${context.bridgeLeaseId}`
45939
+ );
45940
+ input.runtimeLogger?.warn("agent_bridge_terminal_auth_drain_connected", {
45941
+ session_id: context.sessionId,
45942
+ runtime_id: context.runtimeId,
45943
+ bridge_lease_id: context.bridgeLeaseId
45944
+ });
45945
+ const handler = input.getHandler();
45946
+ if (!handler) {
45947
+ throw new Error("Terminal auth drain connected without a handler");
45948
+ }
45949
+ await handler.replayPendingOutputs();
45950
+ settle(() => {
45951
+ input.writeOutput?.(
45952
+ `agent_bridge_terminal_auth_drain_completed duration_ms=${Date.now() - current.startedAt}`
45953
+ );
45954
+ input.runtimeLogger?.warn(
45955
+ "agent_bridge_terminal_auth_drain_completed",
45956
+ { duration_ms: Date.now() - current.startedAt }
45957
+ );
45958
+ current.resolve();
45959
+ });
45960
+ })().catch((error51) => {
45961
+ settle(() => current.reject(error51));
45962
+ });
45963
+ return true;
45964
+ };
45965
+ const handleConnectError = (error51) => {
45966
+ const current = state;
45967
+ if (!current) {
45968
+ return false;
45969
+ }
45970
+ settle(() => current.reject(error51));
45971
+ return true;
45972
+ };
45973
+ return {
45974
+ begin,
45975
+ handleConnected,
45976
+ handleConnectError,
45977
+ suppressesReconnect: () => terminalAuthExit || state !== null
45978
+ };
45979
+ }
45815
45980
  function bridgeForwardingHeaders(url2) {
45816
45981
  if (!isNgrokFreeUrl(url2)) {
45817
45982
  return {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autohq/cli",
3
- "version": "0.1.399",
3
+ "version": "0.1.401",
4
4
  "license": "SEE LICENSE IN README.md",
5
5
  "publishConfig": {
6
6
  "access": "public"