@indigoai-us/hq-cli 5.294.0 → 5.296.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/command-catalog.generated.d.ts +70 -9
  3. package/dist/command-catalog.generated.js +88 -12
  4. package/dist/commands/auth.d.ts +1 -0
  5. package/dist/commands/auth.js +76 -1
  6. package/dist/commands/core-job-native.d.ts +7 -0
  7. package/dist/commands/core-job-native.js +20 -0
  8. package/dist/commands/core-job-probe-native.d.ts +45 -0
  9. package/dist/commands/core-job-probe-native.js +564 -0
  10. package/dist/commands/core-job-remote-launcher.d.ts +7 -0
  11. package/dist/commands/core-job-remote-launcher.js +51 -0
  12. package/dist/commands/core-job-run-native.d.ts +47 -0
  13. package/dist/commands/core-job-run-native.js +1215 -0
  14. package/dist/commands/core.js +1 -1
  15. package/dist/commands/doctor.js +0 -74
  16. package/dist/commands/lanes-message.js +24 -5
  17. package/dist/commands/lanes-resume.js +1 -3
  18. package/dist/commands/lanes-shared.d.ts +1 -2
  19. package/dist/commands/lanes-stories.d.ts +1 -0
  20. package/dist/commands/lanes-stories.js +40 -1
  21. package/dist/commands/lanes.d.ts +1 -9
  22. package/dist/commands/lanes.js +197 -174
  23. package/dist/commands/run.js +5 -0
  24. package/dist/fast-scaffold.d.ts +0 -5
  25. package/dist/fast-scaffold.js +0 -10
  26. package/dist/index.js +1 -10
  27. package/dist/lib/core-utils/sentry-report.d.ts +0 -19
  28. package/dist/lib/core-utils/sentry-report.js +8 -61
  29. package/dist/lib/doctor/checks/claude-wiring.js +9 -0
  30. package/dist/lib/lanes/flag-keys.d.ts +2 -0
  31. package/dist/lib/lanes/flag-keys.js +2 -0
  32. package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
  33. package/dist/lib/lanes/manager-status-gate.js +2 -43
  34. package/dist/lib/lanes/mesh-events.js +3 -0
  35. package/dist/lib/lanes/outpost-dispatch.d.ts +2 -0
  36. package/dist/lib/lanes/outpost-dispatch.js +7 -1
  37. package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -1
  38. package/dist/lib/lanes/outpost-hire-cache.js +0 -1
  39. package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
  40. package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
  41. package/dist/lib/lanes/reconcile.d.ts +2 -3
  42. package/dist/lib/lanes/reconcile.js +67 -57
  43. package/dist/lib/lanes/spawn.d.ts +0 -2
  44. package/dist/lib/lanes/spawn.js +57 -5
  45. package/dist/lib/lanes/store.js +54 -0
  46. package/dist/lib/lanes/stories.d.ts +7 -1
  47. package/dist/lib/lanes/stories.js +16 -0
  48. package/dist/lib/lanes/types.d.ts +30 -1
  49. package/dist/lib/lanes/types.js +2 -2
  50. package/dist/lib/lanes/watcher.d.ts +4 -3
  51. package/dist/lib/lanes/watcher.js +122 -56
  52. package/dist/lib/mesh/live/daemon/run.js +18 -1
  53. package/dist/run/env-graph-guard.d.ts +2 -2
  54. package/dist/run/env-graph-guard.js +5 -2
  55. package/dist/run/hq-plugin.js +6 -5
  56. package/dist/utils/cognito-session.d.ts +6 -0
  57. package/dist/utils/cognito-session.js +9 -0
  58. package/dist/utils/self-update.js +31 -0
  59. package/dist/utils/version-gate.js +22 -3
  60. package/package.json +2 -2
  61. package/dist/fast-lanes-list-route.d.ts +0 -8
  62. package/dist/fast-lanes-list-route.js +0 -32
  63. package/dist/fast-lanes-list.d.ts +0 -3
  64. package/dist/fast-lanes-list.js +0 -224
  65. package/dist/lib/doctor/client-health-sentry-flag.d.ts +0 -23
  66. package/dist/lib/doctor/client-health-sentry-flag.js +0 -99
  67. package/dist/lib/doctor/client-health-sentry.d.ts +0 -71
  68. package/dist/lib/doctor/client-health-sentry.js +0 -286
  69. package/dist/lib/lanes/outpost-snapshot-cache.d.ts +0 -26
  70. package/dist/lib/lanes/outpost-snapshot-cache.js +0 -215
  71. package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +0 -21
  72. package/dist/lib/lanes/outpost-snapshot-refresh.js +0 -329
@@ -31,18 +31,17 @@ import { registerLaneReviewCommand } from "./lanes-review.js";
31
31
  import { ensureCognitoToken } from "../utils/cognito-session.js";
32
32
  import { withCliFeatureUsed } from "../utils/cli-telemetry.js";
33
33
  import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
34
- import { startOutpostLaneSnapshotRefresh } from "../lib/lanes/outpost-snapshot-refresh.js";
35
34
  import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
36
35
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
37
36
  import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
38
- import { getOrFetchOutpostSnapshot, OUTPOST_SNAPSHOT_CACHE_TTL_MS, outpostLaneListSnapshotKey, OutpostSnapshotInvocationMemo, } from "../lib/lanes/outpost-snapshot-cache.js";
37
+ import { OutpostSnapshotInvocationMemo } from "../lib/lanes/outpost-snapshot-memo.js";
39
38
  import { createOutpostHireGrantContext, parseOutpostHireGrantContext, selectOutpostHireGrantHierarchy, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
40
39
  import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } from "../lib/lanes/outpost-profile-repin.js";
41
40
  import { createOutpostRegistryWorkerApprovalContext, parseOutpostRegistryWorkerApprovalContext, } from "../lib/lanes/outpost-registry-worker-approval.js";
42
41
  import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
43
42
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
44
43
  import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostQuestionListCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, REMOTE_LANE_READ_ENV, outpostRejectedEffortOption, parseOutpostPickupPayload, PICKUP_WAIT_MS, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
45
- import { outpostHireSummaryCacheKey, LANE_OUTPOST_SNAPSHOT_CACHE_FLAG, OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS, readOutpostHireSummaryCache, readOutpostHireSummaryCacheIncludingStale, writeOutpostHireSummaryCache, } from "../lib/lanes/outpost-hire-cache.js";
44
+ import { outpostHireSummaryCacheKey, OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS, readOutpostHireSummaryCache, readOutpostHireSummaryCacheIncludingStale, writeOutpostHireSummaryCache, } from "../lib/lanes/outpost-hire-cache.js";
46
45
  import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
47
46
  import { BALANCE_MOVABLE_STATES, selectClaudeAccount, } from "../lib/lanes/claude-balance.js";
48
47
  import { parseUserYaml } from "../utils/user-yaml-error.js";
@@ -69,7 +68,7 @@ import { LanesError } from "../lib/lanes/errors.js";
69
68
  import { readLatestEnvelopeRecord } from "../lib/lanes/watcher.js";
70
69
  import { loadQuotaState, quotaBlockFor } from "../lib/lanes/quota.js";
71
70
  import { SESSION_MOVE_REASON } from "../lib/lanes/move-account.js";
72
- import { managerStatusGateEnabledForLane, resolveManagerStatusGateForLane, } from "../lib/lanes/manager-status-gate.js";
71
+ import { resolveManagerStatusTarget, } from "../lib/lanes/manager-status-gate.js";
73
72
  import { blockerFreshnessFingerprint, resolveBlockerFreshnessForLane, } from "../lib/lanes/blocker-freshness.js";
74
73
  import { resolveKrFreshnessForShow } from "../lib/lanes/kr-freshness.js";
75
74
  import { ensureSessionHome } from "../lib/lanes/session-relink.js";
@@ -85,7 +84,6 @@ const REMOTE_MIRROR_REFRESH_BATCH_SIZE = 100;
85
84
  const REMOTE_LANE_NOT_FOUND_GRACE_MS = 15 * 60_000;
86
85
  /** The lane → mesh seam. Defined in lib/lanes/mesh-events.ts; the watcher shares it. */
87
86
  export { emitLaneDecisionEvents };
88
- export { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG };
89
87
  function withoutProviderHome(value) {
90
88
  if (Array.isArray(value))
91
89
  return value.map(withoutProviderHome);
@@ -301,8 +299,6 @@ function resolveDeps(deps = {}) {
301
299
  })),
302
300
  fetchText: deps.fetchText ?? defaultFetchText,
303
301
  lanePlacementEnabled: deps.lanePlacementEnabled ?? defaultLanePlacementEnabled,
304
- outpostSnapshotCacheEnabled: deps.outpostSnapshotCacheEnabled ?? (() => readLanesRegistryFlag(LANE_OUTPOST_SNAPSHOT_CACHE_FLAG, { env })),
305
- startOutpostLaneSnapshotRefresh: deps.startOutpostLaneSnapshotRefresh ?? ((root) => startOutpostLaneSnapshotRefresh(root, env)),
306
302
  storyLedgerEnabled: deps.storyLedgerEnabled ?? laneStoryLedgerEnabled,
307
303
  isSsmOwnerDispatch: deps.isSsmOwnerDispatch ?? (() => isSsmRunCommandProcess()),
308
304
  withGrantHireLocks: deps.withGrantHireLocks ?? withLaneHireLocks,
@@ -514,7 +510,7 @@ async function readRemoteHireGrantSenior(ctx, hqRoot, seniorLaneId) {
514
510
  laneIds: [seniorLaneId],
515
511
  snapshot: true,
516
512
  strictSnapshot: true,
517
- }, {}, { bypassCache: true });
513
+ }, {}, { bypassMemo: true });
518
514
  }
519
515
  catch (error) {
520
516
  throw unavailable(isLanesError(error) ? error.error : "outpost_lane_list_unavailable");
@@ -1053,7 +1049,7 @@ function laneHostFromCtx(ctx, expectedId) {
1053
1049
  return { kind: "local", id: hostname };
1054
1050
  }
1055
1051
  async function readOutpostCapacity(ctx, outpostId) {
1056
- return readOutpostSnapshotWithCache(ctx, JSON.stringify(["capacity", outpostId]), (value) => isOutpostCapacitySnapshot(value, outpostId), () => readOutpostCapacityUncached(ctx, outpostId));
1052
+ return readOutpostCapacityUncached(ctx, outpostId);
1057
1053
  }
1058
1054
  async function readOutpostCapacityUncached(ctx, outpostId) {
1059
1055
  let result;
@@ -1089,39 +1085,6 @@ async function readOutpostCapacityUncached(ctx, outpostId) {
1089
1085
  }
1090
1086
  return snapshot;
1091
1087
  }
1092
- async function readOutpostSnapshotWithCache(ctx, key, isValue, fetch) {
1093
- let enabled = false;
1094
- try {
1095
- enabled = await ctx.outpostSnapshotCacheEnabled();
1096
- }
1097
- catch (err) {
1098
- process.stderr.write(`lanes snapshot cache flag read failed; using uncached Outpost read: ${dispatchErrorMessage(err)}\n`);
1099
- }
1100
- if (!enabled)
1101
- return fetch();
1102
- return getOrFetchOutpostSnapshot({ hqRoot: ctx.hqRoot(), key, isValue, fetch });
1103
- }
1104
- function isLaneRecordArray(value) {
1105
- if (!Array.isArray(value))
1106
- return false;
1107
- try {
1108
- for (const lane of value)
1109
- parseLaneRecord(lane);
1110
- return true;
1111
- }
1112
- catch {
1113
- return false;
1114
- }
1115
- }
1116
- function isOutpostCapacitySnapshot(value, outpostId) {
1117
- try {
1118
- const snapshot = parseHostCapacitySnapshot(value);
1119
- return snapshot.host.kind === "outpost" && snapshot.host.id === outpostId;
1120
- }
1121
- catch {
1122
- return false;
1123
- }
1124
- }
1125
1088
  async function loadHostCapacitySnapshots(ctx, hqRoot, config = loadLanesConfig(hqRoot), localLanes = loadAllLanes(hqRoot)) {
1126
1089
  const localHost = laneHostFromCtx(ctx);
1127
1090
  let outpostIds;
@@ -1280,7 +1243,7 @@ async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOpt
1280
1243
  }
1281
1244
  async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispatchOptions = {}, options = {}) {
1282
1245
  const query = normalizedLaneListQuery(requestedQuery);
1283
- if (options.bypassCache === true) {
1246
+ if (options.bypassMemo === true) {
1284
1247
  return readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions);
1285
1248
  }
1286
1249
  let invocationMemo = outpostLaneListMemo.get(ctx);
@@ -1288,10 +1251,10 @@ async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispa
1288
1251
  invocationMemo = new OutpostSnapshotInvocationMemo();
1289
1252
  outpostLaneListMemo.set(ctx, invocationMemo);
1290
1253
  }
1291
- return invocationMemo.getOrCreate(outpostId, query, () => readOutpostSnapshotWithCache(ctx, outpostLaneListSnapshotKey(outpostId, query), isLaneRecordArray, () => readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions)));
1254
+ return invocationMemo.getOrCreate(outpostId, query, () => readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions));
1292
1255
  }
1293
- /** Refresh only this host's non-terminal Outpost mirrors in one bounded owner query per Outpost. */
1294
- async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
1256
+ /** Refresh this host's selected Outpost mirrors in one bounded owner query per Outpost. */
1257
+ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, options = {}) {
1295
1258
  const result = {
1296
1259
  updated: new Map(),
1297
1260
  unverifiedRemote: new Map(),
@@ -1316,11 +1279,21 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
1316
1279
  .filter((lane) => lane.host.kind === "outpost" &&
1317
1280
  (host.outpostId === null || lane.host.id !== host.outpostId) &&
1318
1281
  originMatchesThisHost(lane) &&
1319
- (parentLaneIds === undefined || (lane.senior.kind === "lane" &&
1282
+ ((parentLaneIds === undefined || (lane.senior.kind === "lane" &&
1320
1283
  lane.senior.id !== undefined &&
1321
- parentLaneIds.has(lane.senior.id))) &&
1322
- !isTerminalLaneState(lane.state))
1323
- .sort((a, b) => a.lane_id.localeCompare(b.lane_id));
1284
+ parentLaneIds.has(lane.senior.id))) || options.laneIds?.has(lane.lane_id) === true) &&
1285
+ (options.includeTerminalMirrors === true || !isTerminalLaneState(lane.state)))
1286
+ .sort((a, b) => {
1287
+ const terminalOrder = Number(isTerminalLaneState(a.state)) - Number(isTerminalLaneState(b.state));
1288
+ if (terminalOrder !== 0)
1289
+ return terminalOrder;
1290
+ if (isTerminalLaneState(a.state)) {
1291
+ const updatedAtOrder = Date.parse(b.updated_at) - Date.parse(a.updated_at);
1292
+ if (updatedAtOrder !== 0)
1293
+ return updatedAtOrder;
1294
+ }
1295
+ return a.lane_id.localeCompare(b.lane_id);
1296
+ });
1324
1297
  if (candidates.length === 0)
1325
1298
  return result;
1326
1299
  const enabledByCompany = new Map();
@@ -1406,7 +1379,13 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
1406
1379
  : undefined;
1407
1380
  const before = readLane(hqRoot, mirror.lane_id);
1408
1381
  if (before.host.kind !== "outpost" || before.host.id !== outpostId ||
1409
- !originMatchesThisHost(before) || isTerminalLaneState(before.state)) {
1382
+ !originMatchesThisHost(before) ||
1383
+ (!options.includeTerminalMirrors && isTerminalLaneState(before.state))) {
1384
+ result.updated.set(mirror.lane_id, before);
1385
+ continue;
1386
+ }
1387
+ if (owner.version < before.version) {
1388
+ result.unverifiedRemote.set(mirror.lane_id, "owner_snapshot_behind_mirror");
1410
1389
  result.updated.set(mirror.lane_id, before);
1411
1390
  continue;
1412
1391
  }
@@ -1424,10 +1403,16 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
1424
1403
  result.updated.set(mirror.lane_id, before);
1425
1404
  continue;
1426
1405
  }
1406
+ let ownerSnapshotLostVersionRace = false;
1427
1407
  const updated = retryUpdateLane(hqRoot, mirror.lane_id, "reconcile", (current) => {
1428
1408
  if (current.host.kind !== "outpost" || current.host.id !== outpostId ||
1429
- !originMatchesThisHost(current) || isTerminalLaneState(current.state))
1409
+ !originMatchesThisHost(current) ||
1410
+ (!options.includeTerminalMirrors && isTerminalLaneState(current.state)))
1411
+ return current;
1412
+ if (owner.version < current.version) {
1413
+ ownerSnapshotLostVersionRace = true;
1430
1414
  return current;
1415
+ }
1431
1416
  return {
1432
1417
  ...current,
1433
1418
  state: owner.state,
@@ -1445,6 +1430,9 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
1445
1430
  } : {}),
1446
1431
  };
1447
1432
  });
1433
+ if (ownerSnapshotLostVersionRace) {
1434
+ result.unverifiedRemote.set(mirror.lane_id, "owner_snapshot_behind_mirror");
1435
+ }
1448
1436
  result.updated.set(mirror.lane_id, updated);
1449
1437
  }
1450
1438
  }
@@ -1524,6 +1512,7 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
1524
1512
  const hostOutpostIdSet = new Set(hostOutpostIds);
1525
1513
  const matches = [];
1526
1514
  const failedOutpostIds = [];
1515
+ const probedOutpostIds = [];
1527
1516
  const lookupOutpostIds = requestedOutpostId !== undefined
1528
1517
  ? [requestedOutpostId]
1529
1518
  : [
@@ -1537,6 +1526,7 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
1537
1526
  hierarchy: [laneId],
1538
1527
  snapshot: true,
1539
1528
  });
1529
+ probedOutpostIds.push(outpostId);
1540
1530
  }
1541
1531
  catch (err) {
1542
1532
  if (requestedOutpostId !== undefined)
@@ -1555,7 +1545,10 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
1555
1545
  }
1556
1546
  }
1557
1547
  if (matches.length === 0) {
1558
- throw commandLaneNotFound(laneId, options.outpostHint === true, failedOutpostIds.length > 0 ? { failed_outpost_ids: failedOutpostIds } : undefined);
1548
+ throw commandLaneNotFound(laneId, options.outpostHint === true, {
1549
+ ...(failedOutpostIds.length > 0 ? { failed_outpost_ids: failedOutpostIds } : {}),
1550
+ ...(probedOutpostIds.length > 0 ? { probed_outpost_ids: probedOutpostIds } : {}),
1551
+ });
1559
1552
  }
1560
1553
  if (matches.length !== 1) {
1561
1554
  throw new LanesError("lane_outpost_ambiguous", `More than one registered Outpost reported lane ${laneId} with the same recorded host identity`, { lane_id: laneId, outpost_ids: matches.map((match) => match.outpostId) });
@@ -3502,7 +3495,6 @@ async function runCreate(ctx, opts) {
3502
3495
  if (admission.action === "start") {
3503
3496
  started = spawnLaneWorker(hqRoot, lane, {
3504
3497
  env: ctx.env,
3505
- managerStatusGateEnabled: await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env }),
3506
3498
  modelDeps: {
3507
3499
  readFileSync: ctx.readFileSync,
3508
3500
  probe: ctx.probeProvider,
@@ -4276,6 +4268,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4276
4268
  outpost_id: outpostId,
4277
4269
  outpost_command_id: dispatched.commandId,
4278
4270
  outpost_transport: "outposts_exec",
4271
+ delivered_to: { host: outpostId, lane_id: ownerLane.lane_id, mechanism: "outposts_exec" },
4279
4272
  });
4280
4273
  return;
4281
4274
  }
@@ -4306,6 +4299,9 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4306
4299
  outpost_id: outpostId,
4307
4300
  outpost_command_id: dispatched.commandId,
4308
4301
  outpost_transport: "outposts_exec",
4302
+ ...(action === "message"
4303
+ ? { delivered_to: { host: outpostId, lane_id: lane.lane_id, mechanism: "outposts_exec" } }
4304
+ : {}),
4309
4305
  });
4310
4306
  }
4311
4307
  function dispatchedOwnerLane(ctx, laneId, outpostId, rawLane, behavior) {
@@ -4581,7 +4577,6 @@ async function runPickup(ctx, opts) {
4581
4577
  try {
4582
4578
  return spawnLaneWorker(hqRoot, current, {
4583
4579
  env: ctx.env,
4584
- managerStatusGateEnabled: await managerStatusGateEnabledForLane(hqRoot, current, { env: ctx.env }),
4585
4580
  modelDeps: {
4586
4581
  readFileSync: ctx.readFileSync,
4587
4582
  probe: ctx.probeProvider,
@@ -4626,7 +4621,7 @@ async function runPickup(ctx, opts) {
4626
4621
  lane: started,
4627
4622
  });
4628
4623
  }
4629
- async function runList(ctx, opts, fastRouteFlags) {
4624
+ async function runList(ctx, opts) {
4630
4625
  if (opts.json && opts.human) {
4631
4626
  throw new LanesError("conflicting_output_format", "Use either --json or --human for hq lanes list", { json: true, human: true });
4632
4627
  }
@@ -4666,15 +4661,14 @@ async function runList(ctx, opts, fastRouteFlags) {
4666
4661
  const hierarchyIds = opts.hierarchy === undefined
4667
4662
  ? undefined
4668
4663
  : new Set(opts.hierarchy.flatMap((laneId) => selectOutpostHireGrantHierarchy(laneId, laneSource).map((lane) => lane.lane_id)));
4669
- let selectedLanes = laneSource
4670
- .filter((lane) => {
4664
+ const matchesLaneFilters = (lane, includeState) => {
4671
4665
  if (requestedLaneIds.length > 0 && !requestedIds.has(lane.lane_id))
4672
4666
  return false;
4673
- if (opts.active && isTerminalLaneState(lane.state))
4667
+ if (includeState && opts.active && isTerminalLaneState(lane.state))
4674
4668
  return false;
4675
4669
  if (hierarchyIds && !hierarchyIds.has(lane.lane_id))
4676
4670
  return false;
4677
- if (state && lane.state !== state)
4671
+ if (includeState && state && lane.state !== state)
4678
4672
  return false;
4679
4673
  const carriesFilter = laneStories(lane).some((story) => (!opts.project || story.project_id === opts.project) &&
4680
4674
  (!opts.story || story.story_id === opts.story));
@@ -4689,8 +4683,11 @@ async function runList(ctx, opts, fastRouteFlags) {
4689
4683
  if (senior && seniorKey(lane.senior) !== seniorKey(senior))
4690
4684
  return false;
4691
4685
  return true;
4692
- })
4686
+ };
4687
+ const selectLanes = () => laneSource
4688
+ .filter((lane) => matchesLaneFilters(lane, true))
4693
4689
  .sort(compareLaneQueueOrder);
4690
+ let selectedLanes = selectLanes();
4694
4691
  if (opts.snapshot) {
4695
4692
  printJson({
4696
4693
  ok: true,
@@ -4707,15 +4704,20 @@ async function runList(ctx, opts, fastRouteFlags) {
4707
4704
  const remoteRead = ctx.env[REMOTE_LANE_READ_ENV] === "1";
4708
4705
  const remoteRefresh = remoteRead
4709
4706
  ? { updated: new Map(), unverifiedRemote: new Map(), missingOnOwner: new Map() }
4710
- : await refreshRemoteLaneMirrors(ctx, hqRoot, laneSource, new Set(selectedLanes
4711
- .filter((lane) => !isTerminalLaneState(lane.state))
4712
- .map((lane) => lane.lane_id)));
4707
+ : await refreshRemoteLaneMirrors(ctx, hqRoot, laneSource, new Set(laneSource
4708
+ .filter((lane) => matchesLaneFilters(lane, false))
4709
+ .map((lane) => lane.lane_id)), {
4710
+ includeTerminalMirrors: true,
4711
+ laneIds: new Set(laneSource
4712
+ .filter((lane) => matchesLaneFilters(lane, false))
4713
+ .map((lane) => lane.lane_id)),
4714
+ });
4713
4715
  for (const [laneId, updated] of remoteRefresh.updated) {
4714
4716
  const index = laneSource.findIndex((lane) => lane.lane_id === laneId);
4715
4717
  if (index >= 0)
4716
4718
  laneSource[index] = updated;
4717
4719
  }
4718
- selectedLanes = selectedLanes.map((lane) => remoteRefresh.updated.get(lane.lane_id) ?? lane);
4720
+ selectedLanes = selectLanes();
4719
4721
  const now = ctx.now();
4720
4722
  const host = reconcileHostFromCtx(ctx);
4721
4723
  const allLanes = laneSource;
@@ -4736,33 +4738,7 @@ async function runList(ctx, opts, fastRouteFlags) {
4736
4738
  ownerEligibleLaneIds.add(lane.lane_id);
4737
4739
  }
4738
4740
  const ownerLanes = selectedLanes.filter((lane) => ownerEligibleLaneIds.has(lane.lane_id));
4739
- const refreshRemote = ctx.env.HQ_INTERNAL_LANES_REFRESH_REMOTE_SNAPSHOT === "1";
4740
- let snapshotCacheEnabled = fastRouteFlags?.snapshotCacheEnabled ?? false;
4741
- if (fastRouteFlags === undefined &&
4742
- (refreshRemote || (!opts.fresh && ownerLanes.length > 0))) {
4743
- try {
4744
- snapshotCacheEnabled = await ctx.outpostSnapshotCacheEnabled();
4745
- }
4746
- catch (err) {
4747
- process.stderr.write(`lanes snapshot cache flag read failed; using wait-for-remote list: ${dispatchErrorMessage(err)}\n`);
4748
- }
4749
- }
4750
- if (refreshRemote) {
4751
- if (snapshotCacheEnabled && ownerLanes.length > 0) {
4752
- const outcomes = await batchOwnerLaneHireFields(ctx, ownerLanes, allLanes, { forceRefresh: true });
4753
- const failures = [...outcomes.entries()].filter(([, outcome]) => "error" in outcome);
4754
- if (failures.length > 0) {
4755
- const firstOutcome = failures[0][1];
4756
- const firstError = "error" in firstOutcome ? firstOutcome.error : undefined;
4757
- process.stderr.write(`lanes snapshot refresh failed for ${failures.length} lane(s): ${firstError?.error ?? "outpost_lane_hire_summary_missing"}: ${firstError?.message ?? "owner summary outcome was missing"}\n`);
4758
- throw firstError ?? new LanesError("outpost_lane_hire_summary_missing", "Background Outpost lane summary refresh returned a missing outcome", { lane_ids: failures.map(([laneId]) => laneId) });
4759
- }
4760
- }
4761
- return;
4762
- }
4763
4741
  const hireByLane = new Map();
4764
- const remoteSnapshotMetadata = new Map();
4765
- let remoteLanesLoading = false;
4766
4742
  for (const lane of selectedLanes) {
4767
4743
  if (!ownerEligibleLaneIds.has(lane.lane_id)) {
4768
4744
  hireByLane.set(lane.lane_id, {
@@ -4772,47 +4748,7 @@ async function runList(ctx, opts, fastRouteFlags) {
4772
4748
  continue;
4773
4749
  }
4774
4750
  }
4775
- if (snapshotCacheEnabled && !opts.fresh && ownerLanes.length > 0) {
4776
- const nowMs = ctx.now().getTime();
4777
- const cached = readOutpostHireSummaryCacheIncludingStale(hqRoot, nowMs);
4778
- let refreshNeeded = false;
4779
- for (const lane of ownerLanes) {
4780
- if (lane.host.kind !== "outpost")
4781
- continue;
4782
- const summary = cached.get(outpostHireSummaryCacheKey(lane.host.id, lane.lane_id));
4783
- if (!summary) {
4784
- hireByLane.set(lane.lane_id, {
4785
- ...laneHireFields(lane, allLanes, aliveChildrenByParent),
4786
- hire_source: "local",
4787
- });
4788
- remoteSnapshotMetadata.set(lane.lane_id, { ageSeconds: null, stale: true, loading: true });
4789
- refreshNeeded = true;
4790
- remoteLanesLoading = true;
4791
- continue;
4792
- }
4793
- const ageMs = Math.max(0, nowMs - Date.parse(summary.cached_at));
4794
- const stale = ageMs >= OUTPOST_SNAPSHOT_CACHE_TTL_MS;
4795
- hireByLane.set(lane.lane_id, {
4796
- hire_grant: summary.hire_grant,
4797
- hire_alive_children: summary.hire_alive_children,
4798
- hire: summary.hire,
4799
- hire_summary: summary.hire_summary,
4800
- hire_source: "owner",
4801
- });
4802
- remoteSnapshotMetadata.set(lane.lane_id, {
4803
- ageSeconds: Math.floor(ageMs / 1_000),
4804
- stale,
4805
- loading: stale,
4806
- });
4807
- if (stale) {
4808
- refreshNeeded = true;
4809
- remoteLanesLoading = true;
4810
- }
4811
- }
4812
- if (refreshNeeded)
4813
- ctx.startOutpostLaneSnapshotRefresh(hqRoot);
4814
- }
4815
- else if (ownerLanes.length > 0) {
4751
+ if (ownerLanes.length > 0) {
4816
4752
  const ownerSummaries = await batchOwnerLaneHireFields(ctx, ownerLanes, allLanes);
4817
4753
  for (const lane of ownerLanes) {
4818
4754
  const outcome = ownerSummaries.get(lane.lane_id);
@@ -4846,15 +4782,6 @@ async function runList(ctx, opts, fastRouteFlags) {
4846
4782
  ...(remoteQueued
4847
4783
  ? { state: "remote", remote_state: lane.state, queued_reason: undefined, queued_details: undefined }
4848
4784
  : {}),
4849
- ...(remoteSnapshotMetadata.has(lane.lane_id)
4850
- ? {
4851
- remote_snapshot_age_s: remoteSnapshotMetadata.get(lane.lane_id).ageSeconds,
4852
- remote_snapshot_stale: remoteSnapshotMetadata.get(lane.lane_id).stale,
4853
- ...(remoteSnapshotMetadata.get(lane.lane_id).loading
4854
- ? { remote_snapshot_loading: true }
4855
- : {}),
4856
- }
4857
- : {}),
4858
4785
  overdue: Boolean(lane.overdue) || isOverdue(hqRoot, lane.lane_id, now),
4859
4786
  stalled: Boolean(lane.stalled),
4860
4787
  // Listing is observational. Reconcile owns durable liveness transitions.
@@ -4878,8 +4805,7 @@ async function runList(ctx, opts, fastRouteFlags) {
4878
4805
  };
4879
4806
  });
4880
4807
  const placementCompany = company ?? processLanePlacementCompany(ctx);
4881
- const placementEnabled = !remoteRead && (fastRouteFlags?.lanePlacementEnabled ??
4882
- await readLanePlacementEnabled(ctx, placementCompany));
4808
+ const placementEnabled = !remoteRead && await readLanePlacementEnabled(ctx, placementCompany);
4883
4809
  const host_capacity = placementEnabled
4884
4810
  ? (await loadHostCapacitySnapshots(ctx, hqRoot, undefined, allLanes)).entries
4885
4811
  : undefined;
@@ -4890,43 +4816,24 @@ async function runList(ctx, opts, fastRouteFlags) {
4890
4816
  ...(remoteRefresh.unverifiedRemote.size > 0
4891
4817
  ? { unverified_remote_lanes: [...remoteRefresh.unverifiedRemote.keys()] }
4892
4818
  : {}),
4893
- ...(remoteLanesLoading ? { remote_lanes_loading: true } : {}),
4894
4819
  ...(host_capacity !== undefined ? { host_capacity } : {}),
4895
4820
  };
4896
4821
  if (opts.human)
4897
- console.log(formatLaneListHuman(lanes, remoteLanesLoading));
4822
+ console.log(formatLaneListHuman(lanes));
4898
4823
  else
4899
4824
  printJson(result);
4900
4825
  }
4901
- /** Keeps the placement-enabled cache-first path on the full lane command graph. */
4902
- export async function runFastCachedLanesListFromEntrypoint() {
4903
- try {
4904
- await runList(resolveDeps(), { json: true }, {
4905
- snapshotCacheEnabled: true,
4906
- lanePlacementEnabled: true,
4907
- });
4908
- }
4909
- catch (err) {
4910
- failJson(err);
4911
- }
4912
- }
4913
- function formatLaneListHuman(lanes, remoteLanesLoading = false) {
4826
+ function formatLaneListHuman(lanes) {
4914
4827
  const rows = lanes.map((lane) => {
4915
4828
  const ids = lane.story_ids;
4916
4829
  const storyLabel = ids.length > 0
4917
4830
  ? "stories: " + ids.join(", ")
4918
4831
  : "no story: " + (lane.no_story_reason ?? "reason not recorded");
4919
- const snapshotNote = lane.remote_snapshot_stale && lane.remote_snapshot_age_s !== null &&
4920
- lane.remote_snapshot_age_s !== undefined
4921
- ? ` remote snapshot stale (${lane.remote_snapshot_age_s}s)`
4922
- : "";
4923
4832
  return lane.lane_id + " " + lane.state + " " + lane.provider + " " + lane.project_id +
4924
- " " + storyLabel + snapshotNote;
4833
+ " " + storyLabel;
4925
4834
  });
4926
4835
  if (rows.length === 0)
4927
4836
  rows.push("No lanes.");
4928
- if (remoteLanesLoading)
4929
- rows.push("Remote lanes loading.");
4930
4837
  return rows.join("\n");
4931
4838
  }
4932
4839
  async function runStatus(ctx, opts) {
@@ -5121,8 +5028,17 @@ async function runShow(ctx, laneId) {
5121
5028
  const doneStateVerdict = storedLane.state === "done" && storedLane.keep_alive === true
5122
5029
  ? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete }, storedLane.keep_alive_standing === true && hasPendingKeepAliveWork(hqRoot, storedLane))
5123
5030
  : null;
5124
- const managerStatusGate = await resolveManagerStatusGateForLane(hqRoot, storedLane, { env: ctx.env });
5125
- const managerStatusTarget = managerStatusGate.target;
5031
+ let managerStatusTarget;
5032
+ let managerStatusGateError;
5033
+ try {
5034
+ resolveLaneCompany(hqRoot, storedLane);
5035
+ managerStatusTarget = resolveManagerStatusTarget(hqRoot, storedLane);
5036
+ }
5037
+ catch (err) {
5038
+ managerStatusGateError = err instanceof LanesError ? err.error : "manager_status_gate_unavailable";
5039
+ const message = err instanceof Error ? err.message : String(err);
5040
+ process.stderr.write(`hq lanes: manager status gate disabled for ${storedLane.lane_id}: ${managerStatusGateError}: ${message}\n`);
5041
+ }
5126
5042
  const blockerFreshness = await resolveBlockerFreshnessForLane(hqRoot, storedLane, {
5127
5043
  env: ctx.env,
5128
5044
  now: ctx.now().getTime(),
@@ -5146,10 +5062,12 @@ async function runShow(ctx, laneId) {
5146
5062
  })
5147
5063
  .slice(0, 20)
5148
5064
  .map(({ repo, pr, head_sha, recorded_at }) => ({ repo, pr, head_sha, recorded_at }));
5065
+ const laneForShow = { ...lane };
5066
+ delete laneForShow.resume_history;
5149
5067
  printJson({
5150
5068
  ok: true,
5151
5069
  action: "show",
5152
- lane,
5070
+ lane: laneForShow,
5153
5071
  ...(laneIsSenior ? { reviews: recentReviews, reviews_skipped: reviewListing.skipped } : {}),
5154
5072
  ...hire,
5155
5073
  overdue: Boolean(lane.overdue) || isOverdue(hqRoot, lane.lane_id, ctx.now()),
@@ -5163,6 +5081,9 @@ async function runShow(ctx, laneId) {
5163
5081
  envelope_repair_mode: lane.envelope_repair?.mode ?? null,
5164
5082
  envelope_validation_errors: lane.envelope_validation_errors ?? [],
5165
5083
  last_resume: lane.last_resume ?? null,
5084
+ // Public JSON key for the bounded start audit; `last_resume` remains unchanged.
5085
+ resume_history: lane.resume_history ?? [],
5086
+ keep_alive_history: lane.keep_alive_history ?? [],
5166
5087
  account: lane.account ?? null,
5167
5088
  account_history: lane.account_history ?? [],
5168
5089
  failed_resume_history: lane.failed_resume_history ?? [],
@@ -5186,15 +5107,16 @@ async function runShow(ctx, laneId) {
5186
5107
  (storedLane.keep_alive === true ? null : "not_opted_in"),
5187
5108
  consecutive_auto_resumes: lane.keep_alive_resume_count ?? 0,
5188
5109
  last_auto_resume_at: lane.keep_alive_last_resume_at ?? null,
5110
+ keep_alive_history: lane.keep_alive_history ?? [],
5189
5111
  next_eligible_at: lane.keep_alive_next_eligible_at ?? null,
5190
5112
  exhausted_at: lane.keep_alive_exhausted_at ?? null,
5191
5113
  },
5192
- manager_status_gate: managerStatusGate.error !== undefined
5114
+ manager_status_gate: managerStatusGateError !== undefined
5193
5115
  ? {
5194
5116
  status_path: null,
5195
5117
  skipped: null,
5196
5118
  consecutive_misses: storedLane.status_gate?.consecutive_misses ?? 0,
5197
- error: managerStatusGate.error,
5119
+ error: managerStatusGateError,
5198
5120
  }
5199
5121
  : managerStatusTarget?.manager
5200
5122
  ? {
@@ -5910,7 +5832,6 @@ async function runAdmit(ctx, laneId, opts) {
5910
5832
  }
5911
5833
  const started = spawnLaneWorker(hqRoot, lane, {
5912
5834
  env: ctx.env,
5913
- managerStatusGateEnabled: await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env }),
5914
5835
  modelDeps: {
5915
5836
  readFileSync: ctx.readFileSync,
5916
5837
  probe: ctx.probeProvider,
@@ -6274,7 +6195,12 @@ async function prepareAccountMove(ctx, lane, input) {
6274
6195
  }),
6275
6196
  pending_resume: input.deferTurnStart
6276
6197
  ? undefined
6277
- : { session_id: sessionId, at, ...input.pending },
6198
+ : {
6199
+ session_id: sessionId,
6200
+ at,
6201
+ caller: ctx.env.HQ_LANE_ID?.trim() || "user",
6202
+ ...input.pending,
6203
+ },
6278
6204
  };
6279
6205
  applyResumeProfileAndEffort(current, next, {
6280
6206
  ...(input.workerProfile !== undefined ? { profile: input.workerProfile } : {}),
@@ -6917,6 +6843,89 @@ function isDefiniteOutpostLaneNotFound(dispatched, laneId, ownerError) {
6917
6843
  ownerError?.error === "lane_not_found" &&
6918
6844
  ownerError.details.lane_id === laneId;
6919
6845
  }
6846
+ async function dispatchPrimaryLaneMessage(ctx, laneId, options) {
6847
+ const outpostId = "primary";
6848
+ const waitMs = outpostSingleLaneWaitMs(ctx.env);
6849
+ let dispatched;
6850
+ try {
6851
+ dispatched = await ctx.dispatchOutpostExec({
6852
+ outpostId,
6853
+ command: buildOutpostLaneCommand({
6854
+ action: "message",
6855
+ laneId,
6856
+ text: options.text,
6857
+ project: options.project,
6858
+ story: options.story,
6859
+ account: options.account,
6860
+ model: options.model,
6861
+ effort: options.effort,
6862
+ worker: options.worker,
6863
+ compact: options.compact,
6864
+ callerLaneId: ctx.env.HQ_LANE_ID?.trim() || undefined,
6865
+ callerSessionId: sessionIdFromEnv(ctx.env),
6866
+ }),
6867
+ timeoutSeconds: Math.ceil(waitMs / 1000),
6868
+ waitMs,
6869
+ });
6870
+ }
6871
+ catch (err) {
6872
+ const message = dispatchErrorMessage(err);
6873
+ throw new LanesError("outpost_dispatch_failed", `Could not dispatch message for lane ${laneId} to primary: ${message}`, { lane_id: laneId, outpost_id: outpostId, wait_ms: waitMs, wait_env: OUTPOST_SINGLE_LANE_WAIT_ENV });
6874
+ }
6875
+ const confirmedCommandId = dispatched.commandId?.trim();
6876
+ const ownerError = ownerErrorFromOutput(dispatched.stdout);
6877
+ if (isDefiniteOutpostLaneNotFound(dispatched, laneId, ownerError)) {
6878
+ throw new LanesError("lane_not_found", ownerError.message, {
6879
+ ...ownerError.details,
6880
+ lane_id: laneId,
6881
+ outpost_id: outpostId,
6882
+ command_id: confirmedCommandId,
6883
+ });
6884
+ }
6885
+ if (dispatched.done === true &&
6886
+ Boolean(confirmedCommandId) &&
6887
+ dispatched.status === "Failed" &&
6888
+ dispatched.exitCode !== null &&
6889
+ dispatched.exitCode !== 0 &&
6890
+ ownerError) {
6891
+ throw new LanesError(ownerError.error, ownerError.message, {
6892
+ ...ownerError.details,
6893
+ lane_id: laneId,
6894
+ outpost_id: outpostId,
6895
+ command_id: confirmedCommandId,
6896
+ });
6897
+ }
6898
+ if (dispatched.done !== true ||
6899
+ dispatched.status !== "Success" ||
6900
+ dispatched.exitCode !== 0 ||
6901
+ !confirmedCommandId) {
6902
+ throw new LanesError("outpost_delivery_unconfirmed", `Primary did not confirm message delivery for lane ${laneId}`, {
6903
+ lane_id: laneId,
6904
+ outpost_id: outpostId,
6905
+ command_id: confirmedCommandId ?? null,
6906
+ status: dispatched.status,
6907
+ done: dispatched.done ?? false,
6908
+ exit_code: dispatched.exitCode,
6909
+ });
6910
+ }
6911
+ const remote = parseRemoteJson(dispatched.stdout, "Primary lane message");
6912
+ if (remote === null ||
6913
+ remote.ok !== true ||
6914
+ remote.action !== "message" ||
6915
+ remote.lane_id !== laneId ||
6916
+ remote.accepted !== true) {
6917
+ throw new LanesError("outpost_delivery_unconfirmed", `Primary completed exec without a confirmed message result for lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId, command_id: confirmedCommandId });
6918
+ }
6919
+ if (!options.silent) {
6920
+ printJson({
6921
+ ...remote,
6922
+ outpost_id: outpostId,
6923
+ outpost_command_id: confirmedCommandId,
6924
+ outpost_transport: "outposts_exec",
6925
+ delivered_to: { host: outpostId, lane_id: laneId, mechanism: "outposts_exec" },
6926
+ });
6927
+ }
6928
+ }
6920
6929
  async function dispatchOutpostLaneStop(ctx, lane, reason, quiet = false) {
6921
6930
  if (lane.host.kind !== "outpost") {
6922
6931
  throw new LanesError("outpost_dispatch_failed", `Lane ${lane.lane_id} has no Outpost host to dispatch stop to`, { lane_id: lane.lane_id });
@@ -7591,6 +7600,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
7591
7600
  ? { model: automatic.preservedModel, model_source: "preserved" }
7592
7601
  : {}),
7593
7602
  },
7603
+ accountSelection: selection,
7594
7604
  };
7595
7605
  }
7596
7606
  let latest;
@@ -7608,7 +7618,10 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
7608
7618
  latest.last_resume?.session_id !== lane.last_resume?.session_id)
7609
7619
  return { lane: latest };
7610
7620
  if (selection.account === lane.account) {
7611
- return { lane: persistClaudeAccountSelection(hqRoot, recoveredLane, selection) };
7621
+ return {
7622
+ lane: persistClaudeAccountSelection(hqRoot, recoveredLane, selection),
7623
+ accountSelection: selection,
7624
+ };
7612
7625
  }
7613
7626
  try {
7614
7627
  const moved = await prepareAccountMove(ctx, recoveredLane, {
@@ -7622,7 +7635,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
7622
7635
  admissionLocksHeld: true,
7623
7636
  accountSelection: selection,
7624
7637
  });
7625
- return { lane: moved.lane };
7638
+ return { lane: moved.lane, accountSelection: selection };
7626
7639
  }
7627
7640
  catch (err) {
7628
7641
  let latest;
@@ -7830,6 +7843,7 @@ export function registerLanesCommand(parent, deps = {}) {
7830
7843
  assessLaneAdmission,
7831
7844
  casUpdateLane,
7832
7845
  dispatchOutpostLaneCommand,
7846
+ dispatchPrimaryLaneMessage,
7833
7847
  drainHookStatusFor,
7834
7848
  laneIsHostedHere,
7835
7849
  laneWithRecoveredProviderSession,
@@ -8166,6 +8180,15 @@ export function registerLanesCommand(parent, deps = {}) {
8166
8180
  .action(wrap(async (laneId, storyId, opts) => {
8167
8181
  await storyHandlers.runStoryStart(ctx, laneId, storyId, opts);
8168
8182
  }));
8183
+ stories
8184
+ .command("review")
8185
+ .description("Move a carried story to review")
8186
+ .argument("<lane-id>", "Lane id")
8187
+ .argument("<story-id>", "Story id")
8188
+ .option("--project <slug>", "Project id; defaults to the lane's current project")
8189
+ .action(wrap(async (laneId, storyId, opts) => {
8190
+ await storyHandlers.runStoryReview(ctx, laneId, storyId, opts);
8191
+ }));
8169
8192
  stories
8170
8193
  .command("block")
8171
8194
  .description("Mark one carried story blocked")