@indigoai-us/hq-cli 5.279.0 → 5.280.0

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.280.0] — 2026-09-29
6
+
7
+ ### Fixed
8
+
9
+ - Single-lane commands for Outpost-hosted lanes use a configurable pickup wait,
10
+ defaulting to 120 seconds. `hq lanes list` capacity probes use a separate
11
+ 15-second timeout. A lane ID with a registered host suffix routes to that
12
+ Outpost first.
13
+
5
14
  ## [5.279.0] — 2026-09-29
6
15
 
7
16
  ### Fixed
@@ -195,6 +195,7 @@ export interface Ctx {
195
195
  resolveCompany: (company?: string) => Promise<LaneCompanyRef>;
196
196
  resolveActiveCompany: (company: string) => Promise<LaneCompanyRef | null>;
197
197
  listOutpostIds: () => Promise<string[]>;
198
+ listOutpostRoutes: () => Promise<OutpostRouteIdentity[]>;
198
199
  readOutpostIdentity: () => OutpostIdentity;
199
200
  spawnLogin: (opts: {
200
201
  command: string;
@@ -244,6 +245,10 @@ export interface Ctx {
244
245
  mergeHoldEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
245
246
  mergeReviewEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
246
247
  }
248
+ export interface OutpostRouteIdentity {
249
+ outpostId: string;
250
+ instanceName?: string;
251
+ }
247
252
  export declare const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-balancing";
248
253
  export declare const CLAUDE_ACCOUNT_BALANCE_SIGNALS_FLAG = "lanes.claude-account-balance-signals";
249
254
  export declare function defaultLanePlacementEnabled(company: LaneCompanyRef | undefined): Promise<boolean>;
@@ -6,7 +6,7 @@
6
6
  * `watch` and the monitor reminder are the line-oriented text surfaces.
7
7
  */
8
8
  import { Command } from "commander";
9
- import { type LanesMesh, type SpawnLoginResult } from "./lanes-shared.js";
9
+ import { type LanesMesh, type OutpostRouteIdentity, type SpawnLoginResult } from "./lanes-shared.js";
10
10
  export type { LanesMesh, SpawnLoginResult } from "./lanes-shared.js";
11
11
  export { CLAUDE_ACCOUNT_BALANCE_SIGNALS_FLAG, LANE_OUTPOST_LOAD_BALANCING_FLAG } from "./lanes-shared.js";
12
12
  export { resolveAnswerChoice } from "./lanes-questions.js";
@@ -38,6 +38,7 @@ export interface LanesCommandDeps {
38
38
  /** Membership-backed explicit company selection for account-home writers. */
39
39
  resolveActiveCompany?: (company: string) => Promise<LaneCompanyRef | null>;
40
40
  listOutpostIds?: () => Promise<string[]>;
41
+ listOutpostRoutes?: () => Promise<OutpostRouteIdentity[]>;
41
42
  readOutpostIdentity?: () => OutpostIdentity;
42
43
  spawnLogin?: (opts: {
43
44
  command: string;
@@ -36,7 +36,7 @@ import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } fr
36
36
  import { createOutpostRegistryWorkerApprovalContext, parseOutpostRegistryWorkerApprovalContext, } from "../lib/lanes/outpost-registry-worker-approval.js";
37
37
  import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
38
38
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
39
- import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostQuestionListCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, outpostRejectedEffortOption, parseOutpostPickupPayload, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
39
+ import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostQuestionListCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, outpostRejectedEffortOption, parseOutpostPickupPayload, PICKUP_WAIT_MS, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
40
40
  import { outpostHireSummaryCacheKey, readOutpostHireSummaryCache, writeOutpostHireSummaryCache, } from "../lib/lanes/outpost-hire-cache.js";
41
41
  import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
42
42
  import { BALANCE_MOVABLE_STATES, selectClaudeAccount, } from "../lib/lanes/claude-balance.js";
@@ -159,6 +159,10 @@ async function defaultListOutpostIds() {
159
159
  const rows = await defaultOutpostsClient().list();
160
160
  return rows.map((row) => row.outpostId);
161
161
  }
162
+ async function defaultListOutpostRoutes() {
163
+ const rows = await defaultOutpostsClient().list();
164
+ return rows.map(({ outpostId, instanceName }) => ({ outpostId, instanceName }));
165
+ }
162
166
  async function defaultStageOutpostPayload(outpostId, bytes) {
163
167
  return stageOutpostPayload(defaultOutpostsClient(), outpostId, bytes);
164
168
  }
@@ -218,6 +222,9 @@ function resolveDeps(deps = {}) {
218
222
  };
219
223
  const bindProcessCwd = deps.hqRoot === undefined;
220
224
  const resolveRoot = deps.hqRoot ?? (() => resolveLanesHqRoot({ env, cwd: cwd() }));
225
+ const listOutpostRoutes = deps.listOutpostRoutes ?? (deps.listOutpostIds
226
+ ? async () => (await deps.listOutpostIds()).map((outpostId) => ({ outpostId }))
227
+ : defaultListOutpostRoutes);
221
228
  const hqRoot = () => {
222
229
  callerCwd();
223
230
  const root = resolveRoot();
@@ -236,6 +243,7 @@ function resolveDeps(deps = {}) {
236
243
  resolveCompany: deps.resolveCompany ?? defaultResolveCompany,
237
244
  resolveActiveCompany: deps.resolveActiveCompany ?? defaultResolveActiveCompany,
238
245
  listOutpostIds: deps.listOutpostIds ?? defaultListOutpostIds,
246
+ listOutpostRoutes,
239
247
  readOutpostIdentity: deps.readOutpostIdentity ?? (() => readOutpostIdentity()),
240
248
  spawnLogin: deps.spawnLogin ?? defaultSpawnLogin,
241
249
  throttleDeps: deps.throttleDeps ?? defaultThrottleDeps(env),
@@ -611,6 +619,7 @@ async function fetchOwnerLaneHireFields(ctx, lane) {
611
619
  }
612
620
  const outpostId = lane.host.id;
613
621
  const hqBinary = outpostCliOverride(ctx, outpostId);
622
+ const waitMs = outpostSingleLaneWaitMs(ctx.env);
614
623
  let dispatched;
615
624
  try {
616
625
  dispatched = await ctx.dispatchOutpostExec({
@@ -620,12 +629,15 @@ async function fetchOwnerLaneHireFields(ctx, lane) {
620
629
  laneId: lane.lane_id,
621
630
  hqBinary,
622
631
  }),
623
- timeoutSeconds: OUTPOST_HIRE_SUMMARY_TIMEOUT_SECONDS,
624
- waitMs: OUTPOST_HIRE_SUMMARY_WAIT_MS,
632
+ timeoutSeconds: Math.ceil(waitMs / 1000),
633
+ waitMs,
625
634
  });
626
635
  }
627
636
  catch (err) {
628
- throw new LanesError("outpost_lane_hire_summary_unavailable", `Could not read hire summary for lane ${lane.lane_id} from Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { lane_id: lane.lane_id, outpost_id: outpostId });
637
+ const message = dispatchErrorMessage(err);
638
+ throw new LanesError("outpost_lane_hire_summary_unavailable", /timed out waiting for outpost pickup/i.test(message)
639
+ ? outpostPickupTimeoutMessage(outpostId, waitMs, `reading hire summary for lane ${lane.lane_id}`)
640
+ : `Could not read hire summary for lane ${lane.lane_id} from Outpost ${outpostId}: ${message}`, { lane_id: lane.lane_id, outpost_id: outpostId });
629
641
  }
630
642
  if (dispatched.done !== true || dispatched.status !== "Success" || dispatched.exitCode !== 0) {
631
643
  throw new LanesError("outpost_lane_hire_summary_unavailable", `Outpost ${outpostId} did not confirm the hire summary for lane ${lane.lane_id}`, { lane_id: lane.lane_id, outpost_id: outpostId, command_id: dispatched.commandId });
@@ -808,8 +820,6 @@ async function batchOwnerLaneHireFields(ctx, lanes) {
808
820
  writeOutpostHireSummaryCache(ctx.hqRoot(), cacheUpdates, ctx.now().getTime(), cached);
809
821
  return outcomes;
810
822
  }
811
- const OUTPOST_HIRE_SUMMARY_TIMEOUT_SECONDS = 20;
812
- const OUTPOST_HIRE_SUMMARY_WAIT_MS = 15_000;
813
823
  const OUTPOST_HIRE_SUMMARY_CONCURRENCY = 4;
814
824
  async function laneHirePresentation(ctx, lane, lanes) {
815
825
  const local = laneHireFields(lane, lanes);
@@ -886,6 +896,28 @@ async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
886
896
  }
887
897
  const CAPACITY_PROBE_TIMEOUT_MS = 15_000;
888
898
  const MAX_PLACEMENT_OUTPOSTS = 16;
899
+ const OUTPOST_HIRE_SUMMARY_TIMEOUT_SECONDS = 20;
900
+ const OUTPOST_HIRE_SUMMARY_WAIT_MS = 15_000;
901
+ const OUTPOST_SINGLE_LANE_WAIT_ENV = "HQ_LANES_OUTPOST_SINGLE_LANE_WAIT_MS";
902
+ const OUTPOST_SINGLE_LANE_WAIT_MAX_MS = 600_000;
903
+ function outpostSingleLaneWaitMs(env) {
904
+ const raw = env[OUTPOST_SINGLE_LANE_WAIT_ENV]?.trim() || String(PICKUP_WAIT_MS);
905
+ const value = /^\d+$/.test(raw) ? Number(raw) : Number.NaN;
906
+ if (!Number.isSafeInteger(value) ||
907
+ value < CAPACITY_PROBE_TIMEOUT_MS ||
908
+ value > OUTPOST_SINGLE_LANE_WAIT_MAX_MS) {
909
+ throw new LanesError("outpost_wait_invalid", `${OUTPOST_SINGLE_LANE_WAIT_ENV} must be an integer from ${CAPACITY_PROBE_TIMEOUT_MS} to ${OUTPOST_SINGLE_LANE_WAIT_MAX_MS} milliseconds`, { env: OUTPOST_SINGLE_LANE_WAIT_ENV });
910
+ }
911
+ return value;
912
+ }
913
+ function outpostRouteMatchesHost(route, host) {
914
+ return typeof route.instanceName === "string" &&
915
+ route.instanceName.trim() !== "" &&
916
+ sanitizeHostComponent(route.instanceName.trim()) === sanitizeHostComponent(host.trim());
917
+ }
918
+ function outpostPickupTimeoutMessage(outpostId, waitMs, operation) {
919
+ return `Outpost ${outpostId} pickup timed out after ${waitMs}ms while ${operation}. Raise ${OUTPOST_SINGLE_LANE_WAIT_ENV} (milliseconds) and retry.`;
920
+ }
889
921
  function outpostIdentityUnknownError(message, expectedId, details = {}) {
890
922
  const expected = expectedId ?? "<expected-outpost-id>";
891
923
  const command = `hq lanes outpost-identity backfill --id ${expected}`;
@@ -1025,14 +1057,20 @@ function normalizedLaneListQuery(query) {
1025
1057
  };
1026
1058
  }
1027
1059
  async function readOutpostLaneRecordsUncached(ctx, outpostId, query) {
1060
+ const targetedLookup = query.hierarchy !== undefined || query.laneIds !== undefined;
1061
+ const waitMs = targetedLookup ? outpostSingleLaneWaitMs(ctx.env) : CAPACITY_PROBE_TIMEOUT_MS;
1028
1062
  let result;
1029
1063
  try {
1030
- result = await dispatchOutpostLaneList(ctx, outpostId, query, CAPACITY_PROBE_TIMEOUT_MS / 1000, CAPACITY_PROBE_TIMEOUT_MS);
1064
+ result = await dispatchOutpostLaneList(ctx, outpostId, query, Math.ceil(waitMs / 1000), waitMs);
1031
1065
  }
1032
1066
  catch (err) {
1067
+ const message = dispatchErrorMessage(err);
1068
+ if (/timed out waiting for outpost pickup/i.test(message)) {
1069
+ throw new LanesError("outpost_lane_list_unavailable", outpostPickupTimeoutMessage(outpostId, waitMs, "reading the requested lane"), { outpost_id: outpostId, wait_ms: waitMs, wait_env: OUTPOST_SINGLE_LANE_WAIT_ENV });
1070
+ }
1033
1071
  if (isLanesError(err))
1034
1072
  throw err;
1035
- throw new LanesError("outpost_lane_list_unavailable", `Could not read lanes from Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { outpost_id: outpostId });
1073
+ throw new LanesError("outpost_lane_list_unavailable", `Could not read lanes from Outpost ${outpostId}: ${message}`, { outpost_id: outpostId });
1036
1074
  }
1037
1075
  if (result.done !== true || result.status !== "Success" || result.exitCode !== 0) {
1038
1076
  throw new LanesError("outpost_lane_list_unavailable", `Outpost ${outpostId} did not return a successful lane list`, { outpost_id: outpostId, status: result.status, done: result.done ?? false, exit_code: result.exitCode });
@@ -1119,17 +1157,34 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
1119
1157
  if (!hostSuffix || sanitizeHostComponent(hostSuffix) !== hostSuffix) {
1120
1158
  throw commandLaneNotFound(laneId, options.outpostHint === true);
1121
1159
  }
1122
- let rawOutpostIds;
1160
+ let rawOutpostRoutes;
1123
1161
  try {
1124
- rawOutpostIds = await ctx.listOutpostIds();
1162
+ rawOutpostRoutes = await ctx.listOutpostRoutes();
1125
1163
  }
1126
1164
  catch (err) {
1127
1165
  throw new LanesError("outpost_list_unavailable", `Could not resolve registered Outposts for lane ${laneId}: ${dispatchErrorMessage(err)}`, { lane_id: laneId });
1128
1166
  }
1129
- if (!Array.isArray(rawOutpostIds)) {
1167
+ if (!Array.isArray(rawOutpostRoutes)) {
1130
1168
  throw new LanesError("outpost_list_invalid", "Registered Outpost list is not an array", { lane_id: laneId });
1131
1169
  }
1132
- const outpostIds = rawOutpostIds.map((id) => typeof id === "string" ? id.trim() : "");
1170
+ const outpostRoutes = rawOutpostRoutes.map((raw, index) => {
1171
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw) ||
1172
+ typeof raw.outpostId !== "string" ||
1173
+ !raw.outpostId ||
1174
+ (raw.instanceName !== undefined &&
1175
+ typeof raw.instanceName !== "string")) {
1176
+ throw new LanesError("outpost_list_invalid", `Registered Outpost row ${index} is malformed`, {
1177
+ lane_id: laneId,
1178
+ index,
1179
+ });
1180
+ }
1181
+ const row = raw;
1182
+ return {
1183
+ outpostId: row.outpostId.trim(),
1184
+ ...(typeof row.instanceName === "string" ? { instanceName: row.instanceName.trim() } : {}),
1185
+ };
1186
+ });
1187
+ const outpostIds = outpostRoutes.map(({ outpostId }) => outpostId);
1133
1188
  if (outpostIds.length > MAX_PLACEMENT_OUTPOSTS ||
1134
1189
  outpostIds.some((id) => id === "") ||
1135
1190
  new Set(outpostIds).size !== outpostIds.length) {
@@ -1142,9 +1197,23 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
1142
1197
  if (requestedOutpostId !== undefined && !outpostIds.includes(requestedOutpostId)) {
1143
1198
  throw new LanesError("outpost_not_registered", `Outpost ${requestedOutpostId} is not registered`, { lane_id: laneId, outpost_id: requestedOutpostId });
1144
1199
  }
1200
+ const hostRoutes = outpostRoutes.filter((route) => outpostRouteMatchesHost(route, hostSuffix));
1201
+ const identity = ctx.readOutpostIdentity();
1202
+ const localHostRoutes = outpostRoutes.filter((route) => outpostRouteMatchesHost(route, ctx.hostname()));
1203
+ if (localHostRoutes.length > 1 || (identity.kind === "id" && localHostRoutes.some((route) => route.outpostId !== identity.id))) {
1204
+ throw new LanesError("outpost_host_ambiguous", "Registered Outpost host identity conflicts with this host's identity", { lane_id: laneId, local_outpost_id: identity.kind === "id" ? identity.id : undefined });
1205
+ }
1206
+ const localOutpostId = identity.kind === "id" ? identity.id : localHostRoutes[0]?.outpostId;
1207
+ const hostOutpostIds = hostRoutes.map(({ outpostId }) => outpostId);
1208
+ const hostOutpostIdSet = new Set(hostOutpostIds);
1145
1209
  const matches = [];
1146
1210
  const failedOutpostIds = [];
1147
- const lookupOutpostIds = requestedOutpostId === undefined ? outpostIds : [requestedOutpostId];
1211
+ const lookupOutpostIds = requestedOutpostId !== undefined
1212
+ ? [requestedOutpostId]
1213
+ : [
1214
+ ...hostOutpostIds.filter((id) => id !== localOutpostId),
1215
+ ...outpostIds.filter((id) => id !== localOutpostId && !hostOutpostIdSet.has(id)),
1216
+ ];
1148
1217
  for (const outpostId of lookupOutpostIds) {
1149
1218
  let ownerLanes;
1150
1219
  try {
@@ -1156,6 +1225,8 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
1156
1225
  catch (err) {
1157
1226
  if (requestedOutpostId !== undefined)
1158
1227
  throw err;
1228
+ if (isLanesError(err) && err.error === "outpost_wait_invalid")
1229
+ throw err;
1159
1230
  failedOutpostIds.push(outpostId);
1160
1231
  continue;
1161
1232
  }
@@ -3547,6 +3618,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
3547
3618
  throw new LanesError("outpost_dispatch_failed", `Lane ${lane.lane_id} has no Outpost host to dispatch to`, { lane_id: lane.lane_id });
3548
3619
  }
3549
3620
  const outpostId = lane.host.id;
3621
+ const waitMs = outpostSingleLaneWaitMs(ctx.env);
3550
3622
  const profileRepinContext = action === "resume" &&
3551
3623
  options.worker !== undefined &&
3552
3624
  callerMatchesSenior(ctx, lane.senior)
@@ -3583,6 +3655,8 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
3583
3655
  try {
3584
3656
  const dispatchCommand = (contexts) => ctx.dispatchOutpostExec({
3585
3657
  outpostId,
3658
+ timeoutSeconds: Math.ceil(waitMs / 1000),
3659
+ waitMs,
3586
3660
  command: buildOutpostLaneCommand({
3587
3661
  action: action === "grant"
3588
3662
  ? "grant-target"
@@ -3636,7 +3710,10 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
3636
3710
  }
3637
3711
  }
3638
3712
  catch (err) {
3639
- throw new LanesError("outpost_dispatch_failed", `Could not dispatch ${action} for lane ${lane.lane_id} to Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { lane_id: lane.lane_id, outpost_id: outpostId });
3713
+ const message = dispatchErrorMessage(err);
3714
+ throw new LanesError("outpost_dispatch_failed", /timed out waiting for outpost pickup/i.test(message)
3715
+ ? outpostPickupTimeoutMessage(outpostId, waitMs, `dispatching ${action} for lane ${lane.lane_id}`)
3716
+ : `Could not dispatch ${action} for lane ${lane.lane_id} to Outpost ${outpostId}: ${message}`, { lane_id: lane.lane_id, outpost_id: outpostId, wait_ms: waitMs, wait_env: OUTPOST_SINGLE_LANE_WAIT_ENV });
3640
3717
  }
3641
3718
  if (grantContextRejected) {
3642
3719
  throw new LanesError("outpost_cli_upgrade_required", `Outpost ${outpostId} needs an hq-cli release containing this fix before it can resume lane ${lane.lane_id} under its primary-hosted senior`, {
@@ -3834,6 +3911,7 @@ function parseReparentDescendantIds(value, laneId, outpostId) {
3834
3911
  return ids;
3835
3912
  }
3836
3913
  async function readOutpostLaneSnapshot(ctx, outpostId, laneId) {
3914
+ const waitMs = outpostSingleLaneWaitMs(ctx.env);
3837
3915
  let dispatched;
3838
3916
  try {
3839
3917
  dispatched = await ctx.dispatchOutpostExec({
@@ -3843,12 +3921,15 @@ async function readOutpostLaneSnapshot(ctx, outpostId, laneId) {
3843
3921
  laneId,
3844
3922
  hqBinary: outpostCliOverride(ctx, outpostId),
3845
3923
  }),
3846
- timeoutSeconds: OUTPOST_HIRE_SUMMARY_TIMEOUT_SECONDS,
3847
- waitMs: OUTPOST_HIRE_SUMMARY_WAIT_MS,
3924
+ timeoutSeconds: Math.ceil(waitMs / 1000),
3925
+ waitMs,
3848
3926
  });
3849
3927
  }
3850
3928
  catch (err) {
3851
- throw new LanesError("outpost_lane_sync_unavailable", `Could not refresh descendant lane ${laneId} from Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { lane_id: laneId, outpost_id: outpostId });
3929
+ const message = dispatchErrorMessage(err);
3930
+ throw new LanesError("outpost_lane_sync_unavailable", /timed out waiting for outpost pickup/i.test(message)
3931
+ ? outpostPickupTimeoutMessage(outpostId, waitMs, `reading lane ${laneId}`)
3932
+ : `Could not refresh descendant lane ${laneId} from Outpost ${outpostId}: ${message}`, { lane_id: laneId, outpost_id: outpostId });
3852
3933
  }
3853
3934
  if (dispatched.done !== true || dispatched.status !== "Success" || dispatched.exitCode !== 0) {
3854
3935
  throw new LanesError("outpost_lane_sync_unavailable", `Outpost ${outpostId} did not confirm the snapshot for descendant lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId, command_id: dispatched.commandId });
@@ -6373,22 +6454,27 @@ function parseRemoteCancelledQuestionIds(value, laneId, outpostId, commandId) {
6373
6454
  }
6374
6455
  return value;
6375
6456
  }
6376
- async function readOutpostTerminalLane(ctx, laneId, outpostId, stopCommandId) {
6457
+ async function readOutpostTerminalLane(ctx, laneId, outpostId, stopCommandId, waitMs) {
6377
6458
  let dispatched;
6378
6459
  try {
6379
6460
  dispatched = await ctx.dispatchOutpostExec({
6380
6461
  outpostId,
6381
6462
  command: buildOutpostLaneCommand({ action: "show", laneId }),
6382
- timeoutSeconds: 120,
6383
- waitMs: 120_000,
6463
+ timeoutSeconds: Math.ceil(waitMs / 1000),
6464
+ waitMs,
6384
6465
  });
6385
6466
  }
6386
6467
  catch (err) {
6387
- throw new LanesError("outpost_delivery_unconfirmed", `Could not read terminal lane ${laneId} from Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, {
6468
+ const message = dispatchErrorMessage(err);
6469
+ throw new LanesError("outpost_delivery_unconfirmed", /timed out waiting for outpost pickup/i.test(message)
6470
+ ? outpostPickupTimeoutMessage(outpostId, waitMs, `confirming terminal lane ${laneId}`)
6471
+ : `Could not read terminal lane ${laneId} from Outpost ${outpostId}: ${message}`, {
6388
6472
  lane_id: laneId,
6389
6473
  outpost_id: outpostId,
6390
6474
  command_id: stopCommandId,
6391
6475
  sync_command_id: null,
6476
+ wait_ms: waitMs,
6477
+ wait_env: OUTPOST_SINGLE_LANE_WAIT_ENV,
6392
6478
  });
6393
6479
  }
6394
6480
  const commandId = dispatched.commandId?.trim();
@@ -6457,17 +6543,21 @@ async function dispatchOutpostLaneStop(ctx, lane, reason, quiet = false) {
6457
6543
  throw new LanesError("outpost_dispatch_failed", `Lane ${lane.lane_id} has no Outpost host to dispatch stop to`, { lane_id: lane.lane_id });
6458
6544
  }
6459
6545
  const outpostId = lane.host.id;
6546
+ const waitMs = outpostSingleLaneWaitMs(ctx.env);
6460
6547
  let dispatched;
6461
6548
  try {
6462
6549
  dispatched = await ctx.dispatchOutpostExec({
6463
6550
  outpostId,
6464
6551
  command: buildOutpostLaneCommand({ action: "stop", laneId: lane.lane_id, reason }),
6465
- timeoutSeconds: 120,
6466
- waitMs: 120_000,
6552
+ timeoutSeconds: Math.ceil(waitMs / 1000),
6553
+ waitMs,
6467
6554
  });
6468
6555
  }
6469
6556
  catch (err) {
6470
- throw new LanesError("outpost_dispatch_failed", `Could not dispatch stop for lane ${lane.lane_id} to Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { lane_id: lane.lane_id, outpost_id: outpostId });
6557
+ const message = dispatchErrorMessage(err);
6558
+ throw new LanesError("outpost_dispatch_failed", /timed out waiting for outpost pickup/i.test(message)
6559
+ ? outpostPickupTimeoutMessage(outpostId, waitMs, `stopping lane ${lane.lane_id}`)
6560
+ : `Could not dispatch stop for lane ${lane.lane_id} to Outpost ${outpostId}: ${message}`, { lane_id: lane.lane_id, outpost_id: outpostId, wait_ms: waitMs, wait_env: OUTPOST_SINGLE_LANE_WAIT_ENV });
6471
6561
  }
6472
6562
  if (reason !== undefined && outpostRejectedStopReasonOption(dispatched)) {
6473
6563
  throw new LanesError("outpost_cli_upgrade_required", `Outpost ${outpostId} needs an hq-cli release containing stop reason support before it can stop lane ${lane.lane_id}`, {
@@ -6515,7 +6605,7 @@ async function dispatchOutpostLaneStop(ctx, lane, reason, quiet = false) {
6515
6605
  }
6516
6606
  state = remote.state;
6517
6607
  alreadyTerminal = true;
6518
- const owner = await readOutpostTerminalLane(ctx, lane.lane_id, outpostId, commandId);
6608
+ const owner = await readOutpostTerminalLane(ctx, lane.lane_id, outpostId, commandId, waitMs);
6519
6609
  if (owner.lane.state !== state) {
6520
6610
  throw new LanesError("outpost_delivery_unconfirmed", `Outpost ${outpostId} returned a different terminal state for lane ${lane.lane_id}`, {
6521
6611
  lane_id: lane.lane_id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.279.0",
3
+ "version": "5.280.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {