@indigoai-us/hq-cli 5.293.0 → 5.295.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 (97) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +5 -1
  3. package/dist/command-catalog.generated.d.ts +54 -33
  4. package/dist/command-catalog.generated.js +68 -42
  5. package/dist/commands/auth.d.ts +1 -0
  6. package/dist/commands/auth.js +76 -1
  7. package/dist/commands/core-command-catalog.js +3 -2
  8. package/dist/commands/core-delegate-w1b.d.ts +2 -0
  9. package/dist/commands/core-delegate-w1b.js +28 -2
  10. package/dist/commands/core-delegate.d.ts +3 -1
  11. package/dist/commands/core-delegate.js +35 -5
  12. package/dist/commands/core-handoff-fast.d.ts +18 -0
  13. package/dist/commands/core-handoff-fast.js +103 -0
  14. package/dist/commands/core-handoff-open-steps.d.ts +21 -0
  15. package/dist/commands/core-handoff-open-steps.js +264 -0
  16. package/dist/commands/core-handoff.d.ts +4 -2
  17. package/dist/commands/core-handoff.js +22 -249
  18. package/dist/commands/core-jobs-native.d.ts +2 -0
  19. package/dist/commands/core-jobs-native.js +22 -11
  20. package/dist/commands/core-knowledge-prefs.d.ts +2 -0
  21. package/dist/commands/core-knowledge-prefs.js +13 -1
  22. package/dist/commands/core-ontology-native.d.ts +2 -0
  23. package/dist/commands/core-ontology-native.js +22 -2
  24. package/dist/commands/core-policy.d.ts +2 -0
  25. package/dist/commands/core-policy.js +18 -1
  26. package/dist/commands/core.js +2 -1
  27. package/dist/commands/daemon.d.ts +2 -1
  28. package/dist/commands/daemon.js +22 -7
  29. package/dist/commands/doctor.js +0 -74
  30. package/dist/commands/lanes-autopilot.d.ts +11 -0
  31. package/dist/commands/lanes-autopilot.js +336 -0
  32. package/dist/commands/lanes-message.js +23 -5
  33. package/dist/commands/lanes-questions.js +8 -5
  34. package/dist/commands/lanes-resume.js +0 -3
  35. package/dist/commands/lanes-shared.d.ts +18 -2
  36. package/dist/commands/lanes-stories.d.ts +14 -0
  37. package/dist/commands/lanes-stories.js +308 -0
  38. package/dist/commands/lanes.d.ts +1 -9
  39. package/dist/commands/lanes.js +216 -481
  40. package/dist/fast-core.js +53 -25
  41. package/dist/fast-scaffold.d.ts +15 -7
  42. package/dist/fast-scaffold.js +167 -17
  43. package/dist/index.js +1 -10
  44. package/dist/lib/core-utils/sentry-report.d.ts +0 -19
  45. package/dist/lib/core-utils/sentry-report.js +8 -61
  46. package/dist/lib/core-utils/worktree-resume.d.ts +2 -0
  47. package/dist/lib/core-utils/worktree-resume.js +16 -0
  48. package/dist/lib/daemon/migrate.d.ts +8 -1
  49. package/dist/lib/daemon/migrate.js +19 -7
  50. package/dist/lib/doctor/checks/claude-wiring.js +9 -0
  51. package/dist/lib/lanes/autopilot-metrics.d.ts +99 -0
  52. package/dist/lib/lanes/autopilot-metrics.js +301 -0
  53. package/dist/lib/lanes/flag-keys.d.ts +2 -0
  54. package/dist/lib/lanes/flag-keys.js +2 -0
  55. package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
  56. package/dist/lib/lanes/manager-status-gate.js +2 -43
  57. package/dist/lib/lanes/outpost-dispatch.d.ts +2 -0
  58. package/dist/lib/lanes/outpost-dispatch.js +7 -1
  59. package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -1
  60. package/dist/lib/lanes/outpost-hire-cache.js +0 -1
  61. package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
  62. package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
  63. package/dist/lib/lanes/reconcile.d.ts +2 -3
  64. package/dist/lib/lanes/reconcile.js +44 -38
  65. package/dist/lib/lanes/spawn.d.ts +0 -2
  66. package/dist/lib/lanes/spawn.js +18 -5
  67. package/dist/lib/lanes/store.d.ts +2 -0
  68. package/dist/lib/lanes/store.js +54 -3
  69. package/dist/lib/lanes/types.d.ts +18 -1
  70. package/dist/lib/lanes/types.js +1 -1
  71. package/dist/lib/lanes/watcher.d.ts +4 -3
  72. package/dist/lib/lanes/watcher.js +102 -34
  73. package/dist/lib/mesh/live/daemon/run.js +18 -1
  74. package/dist/native-fast-route.d.ts +3 -1
  75. package/dist/native-fast-route.js +10 -8
  76. package/dist/utils/cognito-session.d.ts +6 -0
  77. package/dist/utils/cognito-session.js +9 -0
  78. package/dist/utils/contribution-table.d.ts +1 -1
  79. package/dist/utils/fast-stream-errors.d.ts +12 -0
  80. package/dist/utils/fast-stream-errors.js +18 -0
  81. package/dist/utils/scaffold-fast-import-guard.d.ts +2 -2
  82. package/dist/utils/scaffold-fast-import-guard.js +6 -3
  83. package/dist/utils/self-update.js +31 -0
  84. package/dist/utils/version-gate.js +22 -3
  85. package/package.json +1 -1
  86. package/dist/fast-lanes-list-route.d.ts +0 -8
  87. package/dist/fast-lanes-list-route.js +0 -32
  88. package/dist/fast-lanes-list.d.ts +0 -3
  89. package/dist/fast-lanes-list.js +0 -224
  90. package/dist/lib/doctor/client-health-sentry-flag.d.ts +0 -23
  91. package/dist/lib/doctor/client-health-sentry-flag.js +0 -99
  92. package/dist/lib/doctor/client-health-sentry.d.ts +0 -71
  93. package/dist/lib/doctor/client-health-sentry.js +0 -286
  94. package/dist/lib/lanes/outpost-snapshot-cache.d.ts +0 -26
  95. package/dist/lib/lanes/outpost-snapshot-cache.js +0 -215
  96. package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +0 -21
  97. package/dist/lib/lanes/outpost-snapshot-refresh.js +0 -329
@@ -17,11 +17,13 @@ import { createLaneMoveHandlers } from "./lanes-move.js";
17
17
  import { createLaneQuestionsHandlers } from "./lanes-questions.js";
18
18
  import { createLaneResumeHandlers } from "./lanes-resume.js";
19
19
  import { createLaneWatchHandlers } from "./lanes-watch.js";
20
+ import { createLaneStoryHandlers } from "./lanes-stories.js";
20
21
  import { callerMatchesSessionSenior, assertPlacementEnabled, defaultLanePlacementEnabled, parsePlacementPolicy, processLanePlacementCompany, readClaudeAccountBalanceSignalsEnabled, readLanePlacementEnabled, } from "./lanes-shared.js";
21
22
  export { CLAUDE_ACCOUNT_BALANCE_SIGNALS_FLAG, LANE_OUTPOST_LOAD_BALANCING_FLAG } from "./lanes-shared.js";
22
23
  export { resolveAnswerChoice } from "./lanes-questions.js";
23
24
  import { registerOutpostPrunerCommand } from "./outpost-pruner.js";
24
25
  import { registerLaneKpisCommand } from "./lanes-kpis.js";
26
+ import { registerLaneAutopilotReportCommand } from "./lanes-autopilot.js";
25
27
  import { registerLaneChecksCommand } from "./lanes-checks.js";
26
28
  import { registerLaneReportCommands } from "./lanes-report.js";
27
29
  import { registerLaneRepoMergeHoldCommand } from "./lanes-hold.js";
@@ -29,18 +31,17 @@ import { registerLaneReviewCommand } from "./lanes-review.js";
29
31
  import { ensureCognitoToken } from "../utils/cognito-session.js";
30
32
  import { withCliFeatureUsed } from "../utils/cli-telemetry.js";
31
33
  import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
32
- import { startOutpostLaneSnapshotRefresh } from "../lib/lanes/outpost-snapshot-refresh.js";
33
34
  import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
34
35
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
35
36
  import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
36
- 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";
37
38
  import { createOutpostHireGrantContext, parseOutpostHireGrantContext, selectOutpostHireGrantHierarchy, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
38
39
  import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } from "../lib/lanes/outpost-profile-repin.js";
39
40
  import { createOutpostRegistryWorkerApprovalContext, parseOutpostRegistryWorkerApprovalContext, } from "../lib/lanes/outpost-registry-worker-approval.js";
40
41
  import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
41
42
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
42
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";
43
- 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";
44
45
  import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
45
46
  import { BALANCE_MOVABLE_STATES, selectClaudeAccount, } from "../lib/lanes/claude-balance.js";
46
47
  import { parseUserYaml } from "../utils/user-yaml-error.js";
@@ -55,7 +56,7 @@ import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
55
56
  import { listLaneReviews } from "../lib/lanes/reviews.js";
56
57
  import { effectiveAccountHome } from "../lib/lanes/account-home.js";
57
58
  import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readAllLanes, readLane, sanitizeHostComponent, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, dropboxDir, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, readWorkerRunResult, resumeCommandRefusal, withLaneHireLocks, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
58
- import { activeLaneStories, laneStories, laneStory, storyWarning, } from "../lib/lanes/stories.js";
59
+ import { laneStories, storyWarning, } from "../lib/lanes/stories.js";
59
60
  import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
60
61
  import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
61
62
  import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
@@ -67,7 +68,7 @@ import { LanesError } from "../lib/lanes/errors.js";
67
68
  import { readLatestEnvelopeRecord } from "../lib/lanes/watcher.js";
68
69
  import { loadQuotaState, quotaBlockFor } from "../lib/lanes/quota.js";
69
70
  import { SESSION_MOVE_REASON } from "../lib/lanes/move-account.js";
70
- import { managerStatusGateEnabledForLane, resolveManagerStatusGateForLane, } from "../lib/lanes/manager-status-gate.js";
71
+ import { resolveManagerStatusTarget, } from "../lib/lanes/manager-status-gate.js";
71
72
  import { blockerFreshnessFingerprint, resolveBlockerFreshnessForLane, } from "../lib/lanes/blocker-freshness.js";
72
73
  import { resolveKrFreshnessForShow } from "../lib/lanes/kr-freshness.js";
73
74
  import { ensureSessionHome } from "../lib/lanes/session-relink.js";
@@ -83,7 +84,6 @@ const REMOTE_MIRROR_REFRESH_BATCH_SIZE = 100;
83
84
  const REMOTE_LANE_NOT_FOUND_GRACE_MS = 15 * 60_000;
84
85
  /** The lane → mesh seam. Defined in lib/lanes/mesh-events.ts; the watcher shares it. */
85
86
  export { emitLaneDecisionEvents };
86
- export { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG };
87
87
  function withoutProviderHome(value) {
88
88
  if (Array.isArray(value))
89
89
  return value.map(withoutProviderHome);
@@ -299,8 +299,6 @@ function resolveDeps(deps = {}) {
299
299
  })),
300
300
  fetchText: deps.fetchText ?? defaultFetchText,
301
301
  lanePlacementEnabled: deps.lanePlacementEnabled ?? defaultLanePlacementEnabled,
302
- outpostSnapshotCacheEnabled: deps.outpostSnapshotCacheEnabled ?? (() => readLanesRegistryFlag(LANE_OUTPOST_SNAPSHOT_CACHE_FLAG, { env })),
303
- startOutpostLaneSnapshotRefresh: deps.startOutpostLaneSnapshotRefresh ?? ((root) => startOutpostLaneSnapshotRefresh(root, env)),
304
302
  storyLedgerEnabled: deps.storyLedgerEnabled ?? laneStoryLedgerEnabled,
305
303
  isSsmOwnerDispatch: deps.isSsmOwnerDispatch ?? (() => isSsmRunCommandProcess()),
306
304
  withGrantHireLocks: deps.withGrantHireLocks ?? withLaneHireLocks,
@@ -512,7 +510,7 @@ async function readRemoteHireGrantSenior(ctx, hqRoot, seniorLaneId) {
512
510
  laneIds: [seniorLaneId],
513
511
  snapshot: true,
514
512
  strictSnapshot: true,
515
- }, {}, { bypassCache: true });
513
+ }, {}, { bypassMemo: true });
516
514
  }
517
515
  catch (error) {
518
516
  throw unavailable(isLanesError(error) ? error.error : "outpost_lane_list_unavailable");
@@ -1051,7 +1049,7 @@ function laneHostFromCtx(ctx, expectedId) {
1051
1049
  return { kind: "local", id: hostname };
1052
1050
  }
1053
1051
  async function readOutpostCapacity(ctx, outpostId) {
1054
- return readOutpostSnapshotWithCache(ctx, JSON.stringify(["capacity", outpostId]), (value) => isOutpostCapacitySnapshot(value, outpostId), () => readOutpostCapacityUncached(ctx, outpostId));
1052
+ return readOutpostCapacityUncached(ctx, outpostId);
1055
1053
  }
1056
1054
  async function readOutpostCapacityUncached(ctx, outpostId) {
1057
1055
  let result;
@@ -1087,39 +1085,6 @@ async function readOutpostCapacityUncached(ctx, outpostId) {
1087
1085
  }
1088
1086
  return snapshot;
1089
1087
  }
1090
- async function readOutpostSnapshotWithCache(ctx, key, isValue, fetch) {
1091
- let enabled = false;
1092
- try {
1093
- enabled = await ctx.outpostSnapshotCacheEnabled();
1094
- }
1095
- catch (err) {
1096
- process.stderr.write(`lanes snapshot cache flag read failed; using uncached Outpost read: ${dispatchErrorMessage(err)}\n`);
1097
- }
1098
- if (!enabled)
1099
- return fetch();
1100
- return getOrFetchOutpostSnapshot({ hqRoot: ctx.hqRoot(), key, isValue, fetch });
1101
- }
1102
- function isLaneRecordArray(value) {
1103
- if (!Array.isArray(value))
1104
- return false;
1105
- try {
1106
- for (const lane of value)
1107
- parseLaneRecord(lane);
1108
- return true;
1109
- }
1110
- catch {
1111
- return false;
1112
- }
1113
- }
1114
- function isOutpostCapacitySnapshot(value, outpostId) {
1115
- try {
1116
- const snapshot = parseHostCapacitySnapshot(value);
1117
- return snapshot.host.kind === "outpost" && snapshot.host.id === outpostId;
1118
- }
1119
- catch {
1120
- return false;
1121
- }
1122
- }
1123
1088
  async function loadHostCapacitySnapshots(ctx, hqRoot, config = loadLanesConfig(hqRoot), localLanes = loadAllLanes(hqRoot)) {
1124
1089
  const localHost = laneHostFromCtx(ctx);
1125
1090
  let outpostIds;
@@ -1278,7 +1243,7 @@ async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOpt
1278
1243
  }
1279
1244
  async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispatchOptions = {}, options = {}) {
1280
1245
  const query = normalizedLaneListQuery(requestedQuery);
1281
- if (options.bypassCache === true) {
1246
+ if (options.bypassMemo === true) {
1282
1247
  return readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions);
1283
1248
  }
1284
1249
  let invocationMemo = outpostLaneListMemo.get(ctx);
@@ -1286,10 +1251,10 @@ async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispa
1286
1251
  invocationMemo = new OutpostSnapshotInvocationMemo();
1287
1252
  outpostLaneListMemo.set(ctx, invocationMemo);
1288
1253
  }
1289
- 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));
1290
1255
  }
1291
- /** Refresh only this host's non-terminal Outpost mirrors in one bounded owner query per Outpost. */
1292
- 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 = {}) {
1293
1258
  const result = {
1294
1259
  updated: new Map(),
1295
1260
  unverifiedRemote: new Map(),
@@ -1314,11 +1279,21 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
1314
1279
  .filter((lane) => lane.host.kind === "outpost" &&
1315
1280
  (host.outpostId === null || lane.host.id !== host.outpostId) &&
1316
1281
  originMatchesThisHost(lane) &&
1317
- (parentLaneIds === undefined || (lane.senior.kind === "lane" &&
1282
+ ((parentLaneIds === undefined || (lane.senior.kind === "lane" &&
1318
1283
  lane.senior.id !== undefined &&
1319
- parentLaneIds.has(lane.senior.id))) &&
1320
- !isTerminalLaneState(lane.state))
1321
- .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
+ });
1322
1297
  if (candidates.length === 0)
1323
1298
  return result;
1324
1299
  const enabledByCompany = new Map();
@@ -1404,7 +1379,13 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
1404
1379
  : undefined;
1405
1380
  const before = readLane(hqRoot, mirror.lane_id);
1406
1381
  if (before.host.kind !== "outpost" || before.host.id !== outpostId ||
1407
- !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");
1408
1389
  result.updated.set(mirror.lane_id, before);
1409
1390
  continue;
1410
1391
  }
@@ -1422,10 +1403,16 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
1422
1403
  result.updated.set(mirror.lane_id, before);
1423
1404
  continue;
1424
1405
  }
1406
+ let ownerSnapshotLostVersionRace = false;
1425
1407
  const updated = retryUpdateLane(hqRoot, mirror.lane_id, "reconcile", (current) => {
1426
1408
  if (current.host.kind !== "outpost" || current.host.id !== outpostId ||
1427
- !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;
1428
1414
  return current;
1415
+ }
1429
1416
  return {
1430
1417
  ...current,
1431
1418
  state: owner.state,
@@ -1443,6 +1430,9 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
1443
1430
  } : {}),
1444
1431
  };
1445
1432
  });
1433
+ if (ownerSnapshotLostVersionRace) {
1434
+ result.unverifiedRemote.set(mirror.lane_id, "owner_snapshot_behind_mirror");
1435
+ }
1446
1436
  result.updated.set(mirror.lane_id, updated);
1447
1437
  }
1448
1438
  }
@@ -1522,6 +1512,7 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
1522
1512
  const hostOutpostIdSet = new Set(hostOutpostIds);
1523
1513
  const matches = [];
1524
1514
  const failedOutpostIds = [];
1515
+ const probedOutpostIds = [];
1525
1516
  const lookupOutpostIds = requestedOutpostId !== undefined
1526
1517
  ? [requestedOutpostId]
1527
1518
  : [
@@ -1535,6 +1526,7 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
1535
1526
  hierarchy: [laneId],
1536
1527
  snapshot: true,
1537
1528
  });
1529
+ probedOutpostIds.push(outpostId);
1538
1530
  }
1539
1531
  catch (err) {
1540
1532
  if (requestedOutpostId !== undefined)
@@ -1553,7 +1545,10 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
1553
1545
  }
1554
1546
  }
1555
1547
  if (matches.length === 0) {
1556
- 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
+ });
1557
1552
  }
1558
1553
  if (matches.length !== 1) {
1559
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) });
@@ -3500,7 +3495,6 @@ async function runCreate(ctx, opts) {
3500
3495
  if (admission.action === "start") {
3501
3496
  started = spawnLaneWorker(hqRoot, lane, {
3502
3497
  env: ctx.env,
3503
- managerStatusGateEnabled: await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env }),
3504
3498
  modelDeps: {
3505
3499
  readFileSync: ctx.readFileSync,
3506
3500
  probe: ctx.probeProvider,
@@ -4274,6 +4268,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4274
4268
  outpost_id: outpostId,
4275
4269
  outpost_command_id: dispatched.commandId,
4276
4270
  outpost_transport: "outposts_exec",
4271
+ delivered_to: { host: outpostId, lane_id: ownerLane.lane_id, mechanism: "outposts_exec" },
4277
4272
  });
4278
4273
  return;
4279
4274
  }
@@ -4304,6 +4299,9 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4304
4299
  outpost_id: outpostId,
4305
4300
  outpost_command_id: dispatched.commandId,
4306
4301
  outpost_transport: "outposts_exec",
4302
+ ...(action === "message"
4303
+ ? { delivered_to: { host: outpostId, lane_id: lane.lane_id, mechanism: "outposts_exec" } }
4304
+ : {}),
4307
4305
  });
4308
4306
  }
4309
4307
  function dispatchedOwnerLane(ctx, laneId, outpostId, rawLane, behavior) {
@@ -4579,7 +4577,6 @@ async function runPickup(ctx, opts) {
4579
4577
  try {
4580
4578
  return spawnLaneWorker(hqRoot, current, {
4581
4579
  env: ctx.env,
4582
- managerStatusGateEnabled: await managerStatusGateEnabledForLane(hqRoot, current, { env: ctx.env }),
4583
4580
  modelDeps: {
4584
4581
  readFileSync: ctx.readFileSync,
4585
4582
  probe: ctx.probeProvider,
@@ -4624,7 +4621,7 @@ async function runPickup(ctx, opts) {
4624
4621
  lane: started,
4625
4622
  });
4626
4623
  }
4627
- async function runList(ctx, opts, fastRouteFlags) {
4624
+ async function runList(ctx, opts) {
4628
4625
  if (opts.json && opts.human) {
4629
4626
  throw new LanesError("conflicting_output_format", "Use either --json or --human for hq lanes list", { json: true, human: true });
4630
4627
  }
@@ -4664,15 +4661,14 @@ async function runList(ctx, opts, fastRouteFlags) {
4664
4661
  const hierarchyIds = opts.hierarchy === undefined
4665
4662
  ? undefined
4666
4663
  : new Set(opts.hierarchy.flatMap((laneId) => selectOutpostHireGrantHierarchy(laneId, laneSource).map((lane) => lane.lane_id)));
4667
- let selectedLanes = laneSource
4668
- .filter((lane) => {
4664
+ const matchesLaneFilters = (lane, includeState) => {
4669
4665
  if (requestedLaneIds.length > 0 && !requestedIds.has(lane.lane_id))
4670
4666
  return false;
4671
- if (opts.active && isTerminalLaneState(lane.state))
4667
+ if (includeState && opts.active && isTerminalLaneState(lane.state))
4672
4668
  return false;
4673
4669
  if (hierarchyIds && !hierarchyIds.has(lane.lane_id))
4674
4670
  return false;
4675
- if (state && lane.state !== state)
4671
+ if (includeState && state && lane.state !== state)
4676
4672
  return false;
4677
4673
  const carriesFilter = laneStories(lane).some((story) => (!opts.project || story.project_id === opts.project) &&
4678
4674
  (!opts.story || story.story_id === opts.story));
@@ -4687,8 +4683,11 @@ async function runList(ctx, opts, fastRouteFlags) {
4687
4683
  if (senior && seniorKey(lane.senior) !== seniorKey(senior))
4688
4684
  return false;
4689
4685
  return true;
4690
- })
4686
+ };
4687
+ const selectLanes = () => laneSource
4688
+ .filter((lane) => matchesLaneFilters(lane, true))
4691
4689
  .sort(compareLaneQueueOrder);
4690
+ let selectedLanes = selectLanes();
4692
4691
  if (opts.snapshot) {
4693
4692
  printJson({
4694
4693
  ok: true,
@@ -4705,15 +4704,20 @@ async function runList(ctx, opts, fastRouteFlags) {
4705
4704
  const remoteRead = ctx.env[REMOTE_LANE_READ_ENV] === "1";
4706
4705
  const remoteRefresh = remoteRead
4707
4706
  ? { updated: new Map(), unverifiedRemote: new Map(), missingOnOwner: new Map() }
4708
- : await refreshRemoteLaneMirrors(ctx, hqRoot, laneSource, new Set(selectedLanes
4709
- .filter((lane) => !isTerminalLaneState(lane.state))
4710
- .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
+ });
4711
4715
  for (const [laneId, updated] of remoteRefresh.updated) {
4712
4716
  const index = laneSource.findIndex((lane) => lane.lane_id === laneId);
4713
4717
  if (index >= 0)
4714
4718
  laneSource[index] = updated;
4715
4719
  }
4716
- selectedLanes = selectedLanes.map((lane) => remoteRefresh.updated.get(lane.lane_id) ?? lane);
4720
+ selectedLanes = selectLanes();
4717
4721
  const now = ctx.now();
4718
4722
  const host = reconcileHostFromCtx(ctx);
4719
4723
  const allLanes = laneSource;
@@ -4734,33 +4738,7 @@ async function runList(ctx, opts, fastRouteFlags) {
4734
4738
  ownerEligibleLaneIds.add(lane.lane_id);
4735
4739
  }
4736
4740
  const ownerLanes = selectedLanes.filter((lane) => ownerEligibleLaneIds.has(lane.lane_id));
4737
- const refreshRemote = ctx.env.HQ_INTERNAL_LANES_REFRESH_REMOTE_SNAPSHOT === "1";
4738
- let snapshotCacheEnabled = fastRouteFlags?.snapshotCacheEnabled ?? false;
4739
- if (fastRouteFlags === undefined &&
4740
- (refreshRemote || (!opts.fresh && ownerLanes.length > 0))) {
4741
- try {
4742
- snapshotCacheEnabled = await ctx.outpostSnapshotCacheEnabled();
4743
- }
4744
- catch (err) {
4745
- process.stderr.write(`lanes snapshot cache flag read failed; using wait-for-remote list: ${dispatchErrorMessage(err)}\n`);
4746
- }
4747
- }
4748
- if (refreshRemote) {
4749
- if (snapshotCacheEnabled && ownerLanes.length > 0) {
4750
- const outcomes = await batchOwnerLaneHireFields(ctx, ownerLanes, allLanes, { forceRefresh: true });
4751
- const failures = [...outcomes.entries()].filter(([, outcome]) => "error" in outcome);
4752
- if (failures.length > 0) {
4753
- const firstOutcome = failures[0][1];
4754
- const firstError = "error" in firstOutcome ? firstOutcome.error : undefined;
4755
- 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`);
4756
- throw firstError ?? new LanesError("outpost_lane_hire_summary_missing", "Background Outpost lane summary refresh returned a missing outcome", { lane_ids: failures.map(([laneId]) => laneId) });
4757
- }
4758
- }
4759
- return;
4760
- }
4761
4741
  const hireByLane = new Map();
4762
- const remoteSnapshotMetadata = new Map();
4763
- let remoteLanesLoading = false;
4764
4742
  for (const lane of selectedLanes) {
4765
4743
  if (!ownerEligibleLaneIds.has(lane.lane_id)) {
4766
4744
  hireByLane.set(lane.lane_id, {
@@ -4770,47 +4748,7 @@ async function runList(ctx, opts, fastRouteFlags) {
4770
4748
  continue;
4771
4749
  }
4772
4750
  }
4773
- if (snapshotCacheEnabled && !opts.fresh && ownerLanes.length > 0) {
4774
- const nowMs = ctx.now().getTime();
4775
- const cached = readOutpostHireSummaryCacheIncludingStale(hqRoot, nowMs);
4776
- let refreshNeeded = false;
4777
- for (const lane of ownerLanes) {
4778
- if (lane.host.kind !== "outpost")
4779
- continue;
4780
- const summary = cached.get(outpostHireSummaryCacheKey(lane.host.id, lane.lane_id));
4781
- if (!summary) {
4782
- hireByLane.set(lane.lane_id, {
4783
- ...laneHireFields(lane, allLanes, aliveChildrenByParent),
4784
- hire_source: "local",
4785
- });
4786
- remoteSnapshotMetadata.set(lane.lane_id, { ageSeconds: null, stale: true, loading: true });
4787
- refreshNeeded = true;
4788
- remoteLanesLoading = true;
4789
- continue;
4790
- }
4791
- const ageMs = Math.max(0, nowMs - Date.parse(summary.cached_at));
4792
- const stale = ageMs >= OUTPOST_SNAPSHOT_CACHE_TTL_MS;
4793
- hireByLane.set(lane.lane_id, {
4794
- hire_grant: summary.hire_grant,
4795
- hire_alive_children: summary.hire_alive_children,
4796
- hire: summary.hire,
4797
- hire_summary: summary.hire_summary,
4798
- hire_source: "owner",
4799
- });
4800
- remoteSnapshotMetadata.set(lane.lane_id, {
4801
- ageSeconds: Math.floor(ageMs / 1_000),
4802
- stale,
4803
- loading: stale,
4804
- });
4805
- if (stale) {
4806
- refreshNeeded = true;
4807
- remoteLanesLoading = true;
4808
- }
4809
- }
4810
- if (refreshNeeded)
4811
- ctx.startOutpostLaneSnapshotRefresh(hqRoot);
4812
- }
4813
- else if (ownerLanes.length > 0) {
4751
+ if (ownerLanes.length > 0) {
4814
4752
  const ownerSummaries = await batchOwnerLaneHireFields(ctx, ownerLanes, allLanes);
4815
4753
  for (const lane of ownerLanes) {
4816
4754
  const outcome = ownerSummaries.get(lane.lane_id);
@@ -4844,15 +4782,6 @@ async function runList(ctx, opts, fastRouteFlags) {
4844
4782
  ...(remoteQueued
4845
4783
  ? { state: "remote", remote_state: lane.state, queued_reason: undefined, queued_details: undefined }
4846
4784
  : {}),
4847
- ...(remoteSnapshotMetadata.has(lane.lane_id)
4848
- ? {
4849
- remote_snapshot_age_s: remoteSnapshotMetadata.get(lane.lane_id).ageSeconds,
4850
- remote_snapshot_stale: remoteSnapshotMetadata.get(lane.lane_id).stale,
4851
- ...(remoteSnapshotMetadata.get(lane.lane_id).loading
4852
- ? { remote_snapshot_loading: true }
4853
- : {}),
4854
- }
4855
- : {}),
4856
4785
  overdue: Boolean(lane.overdue) || isOverdue(hqRoot, lane.lane_id, now),
4857
4786
  stalled: Boolean(lane.stalled),
4858
4787
  // Listing is observational. Reconcile owns durable liveness transitions.
@@ -4876,8 +4805,7 @@ async function runList(ctx, opts, fastRouteFlags) {
4876
4805
  };
4877
4806
  });
4878
4807
  const placementCompany = company ?? processLanePlacementCompany(ctx);
4879
- const placementEnabled = !remoteRead && (fastRouteFlags?.lanePlacementEnabled ??
4880
- await readLanePlacementEnabled(ctx, placementCompany));
4808
+ const placementEnabled = !remoteRead && await readLanePlacementEnabled(ctx, placementCompany);
4881
4809
  const host_capacity = placementEnabled
4882
4810
  ? (await loadHostCapacitySnapshots(ctx, hqRoot, undefined, allLanes)).entries
4883
4811
  : undefined;
@@ -4888,43 +4816,24 @@ async function runList(ctx, opts, fastRouteFlags) {
4888
4816
  ...(remoteRefresh.unverifiedRemote.size > 0
4889
4817
  ? { unverified_remote_lanes: [...remoteRefresh.unverifiedRemote.keys()] }
4890
4818
  : {}),
4891
- ...(remoteLanesLoading ? { remote_lanes_loading: true } : {}),
4892
4819
  ...(host_capacity !== undefined ? { host_capacity } : {}),
4893
4820
  };
4894
4821
  if (opts.human)
4895
- console.log(formatLaneListHuman(lanes, remoteLanesLoading));
4822
+ console.log(formatLaneListHuman(lanes));
4896
4823
  else
4897
4824
  printJson(result);
4898
4825
  }
4899
- /** Keeps the placement-enabled cache-first path on the full lane command graph. */
4900
- export async function runFastCachedLanesListFromEntrypoint() {
4901
- try {
4902
- await runList(resolveDeps(), { json: true }, {
4903
- snapshotCacheEnabled: true,
4904
- lanePlacementEnabled: true,
4905
- });
4906
- }
4907
- catch (err) {
4908
- failJson(err);
4909
- }
4910
- }
4911
- function formatLaneListHuman(lanes, remoteLanesLoading = false) {
4826
+ function formatLaneListHuman(lanes) {
4912
4827
  const rows = lanes.map((lane) => {
4913
4828
  const ids = lane.story_ids;
4914
4829
  const storyLabel = ids.length > 0
4915
4830
  ? "stories: " + ids.join(", ")
4916
4831
  : "no story: " + (lane.no_story_reason ?? "reason not recorded");
4917
- const snapshotNote = lane.remote_snapshot_stale && lane.remote_snapshot_age_s !== null &&
4918
- lane.remote_snapshot_age_s !== undefined
4919
- ? ` remote snapshot stale (${lane.remote_snapshot_age_s}s)`
4920
- : "";
4921
4832
  return lane.lane_id + " " + lane.state + " " + lane.provider + " " + lane.project_id +
4922
- " " + storyLabel + snapshotNote;
4833
+ " " + storyLabel;
4923
4834
  });
4924
4835
  if (rows.length === 0)
4925
4836
  rows.push("No lanes.");
4926
- if (remoteLanesLoading)
4927
- rows.push("Remote lanes loading.");
4928
4837
  return rows.join("\n");
4929
4838
  }
4930
4839
  async function runStatus(ctx, opts) {
@@ -5119,8 +5028,17 @@ async function runShow(ctx, laneId) {
5119
5028
  const doneStateVerdict = storedLane.state === "done" && storedLane.keep_alive === true
5120
5029
  ? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete }, storedLane.keep_alive_standing === true && hasPendingKeepAliveWork(hqRoot, storedLane))
5121
5030
  : null;
5122
- const managerStatusGate = await resolveManagerStatusGateForLane(hqRoot, storedLane, { env: ctx.env });
5123
- 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
+ }
5124
5042
  const blockerFreshness = await resolveBlockerFreshnessForLane(hqRoot, storedLane, {
5125
5043
  env: ctx.env,
5126
5044
  now: ctx.now().getTime(),
@@ -5161,6 +5079,7 @@ async function runShow(ctx, laneId) {
5161
5079
  envelope_repair_mode: lane.envelope_repair?.mode ?? null,
5162
5080
  envelope_validation_errors: lane.envelope_validation_errors ?? [],
5163
5081
  last_resume: lane.last_resume ?? null,
5082
+ keep_alive_history: lane.keep_alive_history ?? [],
5164
5083
  account: lane.account ?? null,
5165
5084
  account_history: lane.account_history ?? [],
5166
5085
  failed_resume_history: lane.failed_resume_history ?? [],
@@ -5184,15 +5103,16 @@ async function runShow(ctx, laneId) {
5184
5103
  (storedLane.keep_alive === true ? null : "not_opted_in"),
5185
5104
  consecutive_auto_resumes: lane.keep_alive_resume_count ?? 0,
5186
5105
  last_auto_resume_at: lane.keep_alive_last_resume_at ?? null,
5106
+ keep_alive_history: lane.keep_alive_history ?? [],
5187
5107
  next_eligible_at: lane.keep_alive_next_eligible_at ?? null,
5188
5108
  exhausted_at: lane.keep_alive_exhausted_at ?? null,
5189
5109
  },
5190
- manager_status_gate: managerStatusGate.error !== undefined
5110
+ manager_status_gate: managerStatusGateError !== undefined
5191
5111
  ? {
5192
5112
  status_path: null,
5193
5113
  skipped: null,
5194
5114
  consecutive_misses: storedLane.status_gate?.consecutive_misses ?? 0,
5195
- error: managerStatusGate.error,
5115
+ error: managerStatusGateError,
5196
5116
  }
5197
5117
  : managerStatusTarget?.manager
5198
5118
  ? {
@@ -5295,6 +5215,13 @@ function appendCallerSeniorReason(message, authorization) {
5295
5215
  ? message
5296
5216
  : `${message}; authorization refused: ${callerSeniorReason(authorization)}`;
5297
5217
  }
5218
+ function assertQuestionAnswerAuthorized(ctx, lane) {
5219
+ const authorization = callerSeniorAuthorization(ctx, lane.senior);
5220
+ if (authorization.authorized)
5221
+ return;
5222
+ const reason = callerSeniorReason(authorization);
5223
+ throw new LanesError("question_answer_unauthorized", appendCallerSeniorReason(`Only senior ${seniorKey(lane.senior)} may answer lane ${lane.lane_id}'s Outpost question`, authorization), { lane_id: lane.lane_id, senior: lane.senior, reason });
5224
+ }
5298
5225
  function laneGrantTargetHostedHere(ctx, lane) {
5299
5226
  if (lane.host.kind === "local") {
5300
5227
  return laneRunsHere(lane, reconcileHostFromCtx(ctx));
@@ -5901,7 +5828,6 @@ async function runAdmit(ctx, laneId, opts) {
5901
5828
  }
5902
5829
  const started = spawnLaneWorker(hqRoot, lane, {
5903
5830
  env: ctx.env,
5904
- managerStatusGateEnabled: await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env }),
5905
5831
  modelDeps: {
5906
5832
  readFileSync: ctx.readFileSync,
5907
5833
  probe: ctx.probeProvider,
@@ -6447,84 +6373,6 @@ function laneWithRecoveredProviderSession(hqRoot, lane) {
6447
6373
  : {}),
6448
6374
  };
6449
6375
  }
6450
- function storyCommandTarget(lane, storyId, project) {
6451
- const projectId = project?.trim() || lane.project_id;
6452
- const normalizedStoryId = storyId.trim();
6453
- if (!projectId) {
6454
- throw new LanesError("missing_project", "--project is required", {});
6455
- }
6456
- if (!normalizedStoryId) {
6457
- throw new LanesError("missing_story", "<story-id> is required", {});
6458
- }
6459
- const story = laneStory(lane, projectId, normalizedStoryId);
6460
- if (!story) {
6461
- throw new LanesError("story_not_on_lane", `Story ${projectId}/${normalizedStoryId} is not carried by lane ${lane.lane_id}`, { lane_id: lane.lane_id, project_id: projectId, story_id: normalizedStoryId });
6462
- }
6463
- return { projectId, storyId: normalizedStoryId, story };
6464
- }
6465
- function updateCarriedStory(lane, projectId, storyId, status, at, extras = {}) {
6466
- const stories = laneStories(lane).map((story) => story.project_id === projectId && story.story_id === storyId
6467
- ? {
6468
- ...story,
6469
- status,
6470
- updated_at: at,
6471
- blocked_reason: extras.blocked_reason,
6472
- verified_at: extras.verified_at,
6473
- verified_by: extras.verified_by,
6474
- }
6475
- : story);
6476
- return {
6477
- ...lane,
6478
- project_id: projectId,
6479
- story_id: storyId,
6480
- stories,
6481
- };
6482
- }
6483
- function addCarriedStory(lane, projectId, storyId, at) {
6484
- if (laneStory(lane, projectId, storyId)) {
6485
- throw new LanesError("story_already_on_lane", `Story ${projectId}/${storyId} is already carried by lane ${lane.lane_id}`, {
6486
- lane_id: lane.lane_id,
6487
- project_id: projectId,
6488
- story_id: storyId,
6489
- });
6490
- }
6491
- return {
6492
- ...lane,
6493
- ...(lane.story_id.trim() ? {} : { project_id: projectId, story_id: storyId }),
6494
- stories: [
6495
- ...laneStories(lane),
6496
- { project_id: projectId, story_id: storyId, status: "in_progress", added_at: at, updated_at: at },
6497
- ],
6498
- };
6499
- }
6500
- function callerMayVerifyStory(ctx, lane) {
6501
- const callerLaneId = ctx.env.HQ_LANE_ID?.trim();
6502
- if (callerLaneId) {
6503
- return lane.senior.kind === "lane" && lane.senior.id === callerLaneId;
6504
- }
6505
- if (lane.senior.kind === "user")
6506
- return true;
6507
- if (lane.senior.kind === "lane")
6508
- return false;
6509
- return sessionIdFromEnv(ctx.env) === lane.senior.id;
6510
- }
6511
- async function ensureStoryThread(ctx, lane, projectId, storyId = lane.story_id) {
6512
- const company = lane.mesh_company ?? lane.company;
6513
- if (!company)
6514
- return lane;
6515
- return ensureLaneThreadRecord(ctx, lane, projectId, storyId);
6516
- }
6517
- async function ensureStoryThreadForTransition(ctx, lane, projectId, storyId) {
6518
- try {
6519
- return { lane: await ensureStoryThread(ctx, lane, projectId, storyId) };
6520
- }
6521
- catch (err) {
6522
- const warning = storyNotOnBoardWarning(err);
6523
- if (warning === undefined)
6524
- throw err;
6525
- return { lane, warning };
6526
- }
6527
- }
6528
6376
  async function ensureLaneThreadRecord(ctx, lane, projectId = lane.project_id, storyId = lane.story_id, laneStatus = lane.state) {
6529
6377
  const company = lane.mesh_company ?? lane.company;
6530
6378
  if (!company)
@@ -6546,228 +6394,6 @@ async function ensureLaneThreadRecord(ctx, lane, projectId = lane.project_id, st
6546
6394
  }
6547
6395
  return updated;
6548
6396
  }
6549
- async function emitStoryTransition(ctx, lane, projectId, storyId, event, status, payload = {}, skipStoryStatusPatch = false) {
6550
- const company = lane.mesh_company ?? lane.company;
6551
- if (!company)
6552
- return;
6553
- const ready = await ensureStoryThread(ctx, lane, projectId, storyId);
6554
- if (!ready.thread_id)
6555
- return;
6556
- await emitLaneMeshEvent(ctx, company, ready.thread_id, event, {
6557
- lane_id: ready.lane_id,
6558
- project_id: projectId,
6559
- story_id: storyId,
6560
- ...payload,
6561
- lane_status: lane.state,
6562
- });
6563
- if (!skipStoryStatusPatch && projectId?.trim() && storyId?.trim()) {
6564
- await ctx.mesh.patchStoryStatus(company, projectId, storyId, status === "blocked" ? "in_progress" : status);
6565
- }
6566
- }
6567
- function sameCommittedStory(current, expected, projectId, storyId) {
6568
- const currentStory = laneStory(current, projectId, storyId);
6569
- const expectedStory = laneStory(expected, projectId, storyId);
6570
- return (current.project_id === expected.project_id &&
6571
- current.story_id === expected.story_id &&
6572
- JSON.stringify(currentStory) === JSON.stringify(expectedStory));
6573
- }
6574
- async function commitStoryTransition(ctx, before, next, projectId, storyId, event, status, payload = {}, skipStoryStatusPatch = false) {
6575
- try {
6576
- await emitStoryTransition(ctx, next, projectId, storyId, event, status, payload, skipStoryStatusPatch);
6577
- return undefined;
6578
- }
6579
- catch (err) {
6580
- const warning = storyNotOnBoardWarning(err);
6581
- if (warning !== undefined)
6582
- return warning;
6583
- try {
6584
- const current = readLane(ctx.hqRoot(), next.lane_id);
6585
- if (!sameCommittedStory(current, next, projectId, storyId)) {
6586
- throw new LanesError("story_transition_rollback_conflict", `Story ${projectId}/${storyId} changed while mesh publication failed`, { lane_id: next.lane_id, project_id: projectId, story_id: storyId });
6587
- }
6588
- updateLane(ctx.hqRoot(), next.lane_id, current.version, "command", (latest) => {
6589
- if (!sameCommittedStory(latest, next, projectId, storyId)) {
6590
- throw new LanesError("story_transition_rollback_conflict", `Story ${projectId}/${storyId} changed before rollback`, { lane_id: next.lane_id, project_id: projectId, story_id: storyId });
6591
- }
6592
- return {
6593
- ...latest,
6594
- project_id: before.project_id,
6595
- story_id: before.story_id,
6596
- stories: before.stories,
6597
- };
6598
- });
6599
- }
6600
- catch (rollbackErr) {
6601
- process.stderr.write(`hq lanes: story transition rollback failed for ${next.lane_id}/${projectId}/${storyId}: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}\n`);
6602
- }
6603
- throw err;
6604
- }
6605
- }
6606
- async function runStoryList(ctx, laneId) {
6607
- const lane = readLane(ctx.hqRoot(), laneId);
6608
- printJson({
6609
- ok: true,
6610
- action: "story.list",
6611
- lane_id: laneId,
6612
- stories: laneStories(lane),
6613
- active_stories: activeLaneStories(lane),
6614
- story_warning: storyWarning(lane) ?? null,
6615
- });
6616
- }
6617
- async function runStoryAdd(ctx, laneId, storyId, opts) {
6618
- const hqRoot = ctx.hqRoot();
6619
- let lane = readLane(hqRoot, laneId);
6620
- assertLaneRunsHereForOperatorMutation(ctx, lane, "update story status for");
6621
- const projectId = opts.project?.trim() || lane.project_id;
6622
- if (!projectId)
6623
- throw new LanesError("missing_project", "--project is required", {});
6624
- const normalizedStoryId = storyId.trim();
6625
- if (!normalizedStoryId)
6626
- throw new LanesError("missing_story", "<story-id> is required", {});
6627
- if (ctx.storyLedgerEnabled()) {
6628
- validateProjectStories(hqRoot, projectId, [normalizedStoryId], lane.company?.slug);
6629
- }
6630
- const company = lane.mesh_company ?? lane.company;
6631
- const warnings = [];
6632
- let storyKnownAbsent = false;
6633
- if (company) {
6634
- const boardCheck = await storyBoardCheck(ctx, company, projectId, [normalizedStoryId]);
6635
- if (boardCheck.warning !== undefined)
6636
- warnings.push(boardCheck.warning);
6637
- storyKnownAbsent = boardCheck.absentStoryIds.has(normalizedStoryId);
6638
- }
6639
- const at = ctx.now().toISOString();
6640
- const thread = await ensureStoryThreadForTransition(ctx, lane, projectId, normalizedStoryId);
6641
- lane = thread.lane;
6642
- if (thread.warning !== undefined)
6643
- warnings.push(thread.warning);
6644
- const before = lane;
6645
- const next = casUpdateLane(hqRoot, laneId, (current) => {
6646
- assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
6647
- const added = addCarriedStory(current, projectId, normalizedStoryId, at);
6648
- return {
6649
- ...added,
6650
- no_story_reason: undefined,
6651
- };
6652
- });
6653
- const transitionWarning = await commitStoryTransition(ctx, before, next, projectId, normalizedStoryId, "lane_started", "in_progress", {}, storyKnownAbsent);
6654
- if (transitionWarning !== undefined)
6655
- warnings.push(transitionWarning);
6656
- const finalLane = readLane(hqRoot, laneId);
6657
- printJson({
6658
- ok: true,
6659
- action: "story.add",
6660
- lane_id: laneId,
6661
- project_id: projectId,
6662
- story_id: normalizedStoryId,
6663
- status: "in_progress",
6664
- story_warning: combinedStoryWarning(finalLane, warnings),
6665
- lane: finalLane,
6666
- });
6667
- }
6668
- async function runStoryStart(ctx, laneId, storyId, opts) {
6669
- const hqRoot = ctx.hqRoot();
6670
- let lane = readLane(hqRoot, laneId);
6671
- assertLaneRunsHereForOperatorMutation(ctx, lane, "update story status for");
6672
- const target = storyCommandTarget(lane, storyId, opts.project);
6673
- if (target.story.status === "done") {
6674
- throw new LanesError("story_already_done", `Story ${target.projectId}/${target.storyId} is done and cannot be started again`, {
6675
- lane_id: laneId,
6676
- project_id: target.projectId,
6677
- story_id: target.storyId,
6678
- });
6679
- }
6680
- const at = ctx.now().toISOString();
6681
- const warnings = [];
6682
- const thread = await ensureStoryThreadForTransition(ctx, lane, target.projectId, target.storyId);
6683
- lane = thread.lane;
6684
- if (thread.warning !== undefined)
6685
- warnings.push(thread.warning);
6686
- const before = lane;
6687
- const next = casUpdateLane(hqRoot, laneId, (current) => {
6688
- assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
6689
- return updateCarriedStory(current, target.projectId, target.storyId, "in_progress", at);
6690
- });
6691
- const transitionWarning = await commitStoryTransition(ctx, before, next, target.projectId, target.storyId, "lane_started", "in_progress");
6692
- if (transitionWarning !== undefined)
6693
- warnings.push(transitionWarning);
6694
- const finalLane = readLane(hqRoot, laneId);
6695
- printJson({ ok: true, action: "story.start", lane_id: laneId, project_id: target.projectId, story_id: target.storyId, status: "in_progress", story_warning: combinedStoryWarning(finalLane, warnings), lane: finalLane });
6696
- }
6697
- async function runStoryBlock(ctx, laneId, storyId, opts) {
6698
- const hqRoot = ctx.hqRoot();
6699
- let lane = readLane(hqRoot, laneId);
6700
- assertLaneRunsHereForOperatorMutation(ctx, lane, "update story status for");
6701
- const target = storyCommandTarget(lane, storyId, opts.project);
6702
- if (target.story.status === "done") {
6703
- throw new LanesError("story_already_done", `Story ${target.projectId}/${target.storyId} is done and cannot be blocked again`, {
6704
- lane_id: laneId,
6705
- project_id: target.projectId,
6706
- story_id: target.storyId,
6707
- });
6708
- }
6709
- const reason = opts.reason?.trim();
6710
- if (!reason)
6711
- throw new LanesError("missing_block_reason", "--reason is required", {});
6712
- const at = ctx.now().toISOString();
6713
- const warnings = [];
6714
- const thread = await ensureStoryThreadForTransition(ctx, lane, target.projectId, target.storyId);
6715
- lane = thread.lane;
6716
- if (thread.warning !== undefined)
6717
- warnings.push(thread.warning);
6718
- const before = lane;
6719
- const next = casUpdateLane(hqRoot, laneId, (current) => {
6720
- assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
6721
- return updateCarriedStory(current, target.projectId, target.storyId, "blocked", at, { blocked_reason: reason });
6722
- });
6723
- const transitionWarning = await commitStoryTransition(ctx, before, next, target.projectId, target.storyId, "lane_blocked", "blocked", { blocked_reason: reason });
6724
- if (transitionWarning !== undefined)
6725
- warnings.push(transitionWarning);
6726
- const finalLane = readLane(hqRoot, laneId);
6727
- printJson({ ok: true, action: "story.block", lane_id: laneId, project_id: target.projectId, story_id: target.storyId, status: "blocked", blocked_reason: reason, story_warning: combinedStoryWarning(finalLane, warnings), lane: finalLane });
6728
- }
6729
- async function runStoryDone(ctx, laneId, storyId, opts) {
6730
- const hqRoot = ctx.hqRoot();
6731
- let lane = readLane(hqRoot, laneId);
6732
- assertLaneRunsHereForOperatorMutation(ctx, lane, "update story status for");
6733
- if (!opts.verified) {
6734
- throw new LanesError("senior_verification_required", "Only a senior may mark a story done; pass --verified after reviewing or merging it", { lane_id: laneId });
6735
- }
6736
- if (!callerMayVerifyStory(ctx, lane)) {
6737
- throw new LanesError("senior_verification_required", `Caller is not the senior for lane ${laneId}`, {
6738
- lane_id: laneId,
6739
- senior: lane.senior,
6740
- });
6741
- }
6742
- const target = storyCommandTarget(lane, storyId, opts.project);
6743
- if (target.story.status !== "review") {
6744
- throw new LanesError("story_not_ready_for_verification", `Story ${target.projectId}/${target.storyId} must be in review before senior verification`, {
6745
- lane_id: laneId,
6746
- project_id: target.projectId,
6747
- story_id: target.storyId,
6748
- status: target.story.status,
6749
- });
6750
- }
6751
- const at = ctx.now().toISOString();
6752
- const warnings = [];
6753
- const thread = await ensureStoryThreadForTransition(ctx, lane, target.projectId, target.storyId);
6754
- lane = thread.lane;
6755
- if (thread.warning !== undefined)
6756
- warnings.push(thread.warning);
6757
- const before = lane;
6758
- const next = casUpdateLane(hqRoot, laneId, (current) => {
6759
- assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
6760
- return updateCarriedStory(current, target.projectId, target.storyId, "done", at, {
6761
- verified_at: at,
6762
- verified_by: current.senior,
6763
- });
6764
- });
6765
- const transitionWarning = await commitStoryTransition(ctx, before, next, target.projectId, target.storyId, "lane_done", "done", { summary: "Senior verification complete" });
6766
- if (transitionWarning !== undefined)
6767
- warnings.push(transitionWarning);
6768
- const finalLane = readLane(hqRoot, laneId);
6769
- printJson({ ok: true, action: "story.done", lane_id: laneId, project_id: target.projectId, story_id: target.storyId, status: "done", verified: true, story_warning: combinedStoryWarning(finalLane, warnings), lane: finalLane });
6770
- }
6771
6397
  /**
6772
6398
  * Registration state of the drain hook in the lane's account home, read the
6773
6399
  * way the provider reads it. Null when the lane has no bound account yet
@@ -7208,6 +6834,89 @@ function isDefiniteOutpostLaneNotFound(dispatched, laneId, ownerError) {
7208
6834
  ownerError?.error === "lane_not_found" &&
7209
6835
  ownerError.details.lane_id === laneId;
7210
6836
  }
6837
+ async function dispatchPrimaryLaneMessage(ctx, laneId, options) {
6838
+ const outpostId = "primary";
6839
+ const waitMs = outpostSingleLaneWaitMs(ctx.env);
6840
+ let dispatched;
6841
+ try {
6842
+ dispatched = await ctx.dispatchOutpostExec({
6843
+ outpostId,
6844
+ command: buildOutpostLaneCommand({
6845
+ action: "message",
6846
+ laneId,
6847
+ text: options.text,
6848
+ project: options.project,
6849
+ story: options.story,
6850
+ account: options.account,
6851
+ model: options.model,
6852
+ effort: options.effort,
6853
+ worker: options.worker,
6854
+ compact: options.compact,
6855
+ callerLaneId: ctx.env.HQ_LANE_ID?.trim() || undefined,
6856
+ callerSessionId: sessionIdFromEnv(ctx.env),
6857
+ }),
6858
+ timeoutSeconds: Math.ceil(waitMs / 1000),
6859
+ waitMs,
6860
+ });
6861
+ }
6862
+ catch (err) {
6863
+ const message = dispatchErrorMessage(err);
6864
+ 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 });
6865
+ }
6866
+ const confirmedCommandId = dispatched.commandId?.trim();
6867
+ const ownerError = ownerErrorFromOutput(dispatched.stdout);
6868
+ if (isDefiniteOutpostLaneNotFound(dispatched, laneId, ownerError)) {
6869
+ throw new LanesError("lane_not_found", ownerError.message, {
6870
+ ...ownerError.details,
6871
+ lane_id: laneId,
6872
+ outpost_id: outpostId,
6873
+ command_id: confirmedCommandId,
6874
+ });
6875
+ }
6876
+ if (dispatched.done === true &&
6877
+ Boolean(confirmedCommandId) &&
6878
+ dispatched.status === "Failed" &&
6879
+ dispatched.exitCode !== null &&
6880
+ dispatched.exitCode !== 0 &&
6881
+ ownerError) {
6882
+ throw new LanesError(ownerError.error, ownerError.message, {
6883
+ ...ownerError.details,
6884
+ lane_id: laneId,
6885
+ outpost_id: outpostId,
6886
+ command_id: confirmedCommandId,
6887
+ });
6888
+ }
6889
+ if (dispatched.done !== true ||
6890
+ dispatched.status !== "Success" ||
6891
+ dispatched.exitCode !== 0 ||
6892
+ !confirmedCommandId) {
6893
+ throw new LanesError("outpost_delivery_unconfirmed", `Primary did not confirm message delivery for lane ${laneId}`, {
6894
+ lane_id: laneId,
6895
+ outpost_id: outpostId,
6896
+ command_id: confirmedCommandId ?? null,
6897
+ status: dispatched.status,
6898
+ done: dispatched.done ?? false,
6899
+ exit_code: dispatched.exitCode,
6900
+ });
6901
+ }
6902
+ const remote = parseRemoteJson(dispatched.stdout, "Primary lane message");
6903
+ if (remote === null ||
6904
+ remote.ok !== true ||
6905
+ remote.action !== "message" ||
6906
+ remote.lane_id !== laneId ||
6907
+ remote.accepted !== true) {
6908
+ 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 });
6909
+ }
6910
+ if (!options.silent) {
6911
+ printJson({
6912
+ ...remote,
6913
+ outpost_id: outpostId,
6914
+ outpost_command_id: confirmedCommandId,
6915
+ outpost_transport: "outposts_exec",
6916
+ delivered_to: { host: outpostId, lane_id: laneId, mechanism: "outposts_exec" },
6917
+ });
6918
+ }
6919
+ }
7211
6920
  async function dispatchOutpostLaneStop(ctx, lane, reason, quiet = false) {
7212
6921
  if (lane.host.kind !== "outpost") {
7213
6922
  throw new LanesError("outpost_dispatch_failed", `Lane ${lane.lane_id} has no Outpost host to dispatch stop to`, { lane_id: lane.lane_id });
@@ -7882,6 +7591,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
7882
7591
  ? { model: automatic.preservedModel, model_source: "preserved" }
7883
7592
  : {}),
7884
7593
  },
7594
+ accountSelection: selection,
7885
7595
  };
7886
7596
  }
7887
7597
  let latest;
@@ -7899,7 +7609,10 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
7899
7609
  latest.last_resume?.session_id !== lane.last_resume?.session_id)
7900
7610
  return { lane: latest };
7901
7611
  if (selection.account === lane.account) {
7902
- return { lane: persistClaudeAccountSelection(hqRoot, recoveredLane, selection) };
7612
+ return {
7613
+ lane: persistClaudeAccountSelection(hqRoot, recoveredLane, selection),
7614
+ accountSelection: selection,
7615
+ };
7903
7616
  }
7904
7617
  try {
7905
7618
  const moved = await prepareAccountMove(ctx, recoveredLane, {
@@ -7913,7 +7626,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
7913
7626
  admissionLocksHeld: true,
7914
7627
  accountSelection: selection,
7915
7628
  });
7916
- return { lane: moved.lane };
7629
+ return { lane: moved.lane, accountSelection: selection };
7917
7630
  }
7918
7631
  catch (err) {
7919
7632
  let latest;
@@ -8052,6 +7765,19 @@ function attachJsonNoop(cmd) {
8052
7765
  }
8053
7766
  export function registerLanesCommand(parent, deps = {}) {
8054
7767
  const ctx = resolveDeps(deps);
7768
+ const storyHandlers = createLaneStoryHandlers({
7769
+ assertLaneRunsHereForOperatorMutation,
7770
+ casUpdateLane,
7771
+ combinedStoryWarning,
7772
+ emitLaneMeshEvent,
7773
+ ensureLaneThreadRecord,
7774
+ printJson,
7775
+ readLane,
7776
+ storyBoardCheck,
7777
+ storyNotOnBoardWarning,
7778
+ updateLane,
7779
+ validateProjectStories,
7780
+ });
8055
7781
  const accountHandlers = createLaneAccountsHandlers({
8056
7782
  automaticBalanceMoveHistoryReason,
8057
7783
  ensureDir,
@@ -8069,6 +7795,7 @@ export function registerLanesCommand(parent, deps = {}) {
8069
7795
  MAX_PLACEMENT_OUTPOSTS,
8070
7796
  OUTPOST_QUESTION_LOOKUP_CONCURRENCY,
8071
7797
  assertLaneRunsHereForOperatorMutation,
7798
+ assertQuestionAnswerAuthorized,
8072
7799
  assertQuestionCompanyMatchesLane,
8073
7800
  casUpdateQuestion,
8074
7801
  effectiveLaneQuestionCompany,
@@ -8107,6 +7834,7 @@ export function registerLanesCommand(parent, deps = {}) {
8107
7834
  assessLaneAdmission,
8108
7835
  casUpdateLane,
8109
7836
  dispatchOutpostLaneCommand,
7837
+ dispatchPrimaryLaneMessage,
8110
7838
  drainHookStatusFor,
8111
7839
  laneIsHostedHere,
8112
7840
  laneWithRecoveredProviderSession,
@@ -8207,6 +7935,11 @@ export function registerLanesCommand(parent, deps = {}) {
8207
7935
  }, wrap);
8208
7936
  registerOutpostPrunerCommand(lanes);
8209
7937
  registerLaneKpisCommand(lanes, { hqRoot: ctx.hqRoot, now: ctx.now, wrap });
7938
+ registerLaneAutopilotReportCommand(lanes, {
7939
+ hqRoot: ctx.hqRoot,
7940
+ now: ctx.now,
7941
+ currentHost: () => resolveAdmissionHost({ hostname: ctx.hostname, readOutpostIdentity: ctx.readOutpostIdentity }),
7942
+ }, wrap);
8210
7943
  const noStoryOption = new Option("--no-story <reason>", "Create a lane without a story and record why");
8211
7944
  noStoryOption.attributeName = () => "noStory";
8212
7945
  lanes
@@ -8418,7 +8151,7 @@ export function registerLanesCommand(parent, deps = {}) {
8418
8151
  .description("List the stories carried by a lane")
8419
8152
  .argument("<lane-id>", "Lane id")
8420
8153
  .action(wrap(async (laneId) => {
8421
- await runStoryList(ctx, laneId);
8154
+ await storyHandlers.runStoryList(ctx, laneId);
8422
8155
  }));
8423
8156
  stories
8424
8157
  .command("add")
@@ -8427,7 +8160,7 @@ export function registerLanesCommand(parent, deps = {}) {
8427
8160
  .argument("<story-id>", "Story id")
8428
8161
  .option("--project <slug>", "Project id; defaults to the lane's current project")
8429
8162
  .action(wrap(async (laneId, storyId, opts) => {
8430
- await runStoryAdd(ctx, laneId, storyId, opts);
8163
+ await storyHandlers.runStoryAdd(ctx, laneId, storyId, opts);
8431
8164
  }));
8432
8165
  stories
8433
8166
  .command("start")
@@ -8436,7 +8169,7 @@ export function registerLanesCommand(parent, deps = {}) {
8436
8169
  .argument("<story-id>", "Story id")
8437
8170
  .option("--project <slug>", "Project id; defaults to the lane's current project")
8438
8171
  .action(wrap(async (laneId, storyId, opts) => {
8439
- await runStoryStart(ctx, laneId, storyId, opts);
8172
+ await storyHandlers.runStoryStart(ctx, laneId, storyId, opts);
8440
8173
  }));
8441
8174
  stories
8442
8175
  .command("block")
@@ -8446,7 +8179,7 @@ export function registerLanesCommand(parent, deps = {}) {
8446
8179
  .requiredOption("--reason <text>", "Why the story is blocked")
8447
8180
  .option("--project <slug>", "Project id; defaults to the lane's current project")
8448
8181
  .action(wrap(async (laneId, storyId, opts) => {
8449
- await runStoryBlock(ctx, laneId, storyId, opts);
8182
+ await storyHandlers.runStoryBlock(ctx, laneId, storyId, opts);
8450
8183
  }));
8451
8184
  stories
8452
8185
  .command("done")
@@ -8456,7 +8189,7 @@ export function registerLanesCommand(parent, deps = {}) {
8456
8189
  .requiredOption("--verified", "Confirm senior review or merge")
8457
8190
  .option("--project <slug>", "Project id; defaults to the lane's current project")
8458
8191
  .action(wrap(async (laneId, storyId, opts) => {
8459
- await runStoryDone(ctx, laneId, storyId, opts);
8192
+ await storyHandlers.runStoryDone(ctx, laneId, storyId, opts);
8460
8193
  }));
8461
8194
  lanes
8462
8195
  .command("message")
@@ -8745,9 +8478,11 @@ export function registerLanesCommand(parent, deps = {}) {
8745
8478
  .action(wrap(async (opts) => {
8746
8479
  await runPickup(ctx, opts);
8747
8480
  }));
8748
- lanes
8749
- .command("autopilot")
8750
- .description("Check registered team leads and wake them for ready work")
8481
+ const autopilot = lanes.commands.find((command) => command.name() === "autopilot");
8482
+ if (!autopilot)
8483
+ throw new Error("Lanes autopilot command group was not registered");
8484
+ autopilot
8485
+ .description("Run lead autopilot checks and report follow-through metrics")
8751
8486
  .command("run")
8752
8487
  .description("Run one lead autopilot check pass")
8753
8488
  .option("--dry-run", "Report proposed wakes without writing them")