@indigoai-us/hq-cli 5.312.2-beta.0 → 5.313.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 (81) hide show
  1. package/CHANGELOG.md +9 -2
  2. package/assets/scaffold/core/scripts/lib/work-mesh-live-hook.sh +378 -0
  3. package/assets/scaffold/core/scripts/lib/work-mesh-live-rebind.sh +141 -0
  4. package/assets/scaffold/core/scripts/migrate-policy-triggers.sh +373 -0
  5. package/assets/scaffold/core/scripts/register-project.sh +262 -0
  6. package/assets/scaffold/core/scripts/work-mesh-live-rebind.sh +59 -0
  7. package/dist/command-catalog.generated.d.ts +80 -0
  8. package/dist/command-catalog.generated.js +99 -0
  9. package/dist/commands/cloud.js +0 -1
  10. package/dist/commands/company-rename-flag.d.ts +4 -0
  11. package/dist/commands/company-rename-flag.js +7 -0
  12. package/dist/commands/company-rename.d.ts +4 -0
  13. package/dist/commands/company-rename.js +90 -0
  14. package/dist/commands/company.js +2 -0
  15. package/dist/commands/core-eval-trigger-native.d.ts +4 -0
  16. package/dist/commands/core-eval-trigger-native.js +12 -0
  17. package/dist/commands/core-knowledge-prefs.d.ts +0 -1
  18. package/dist/commands/core-knowledge-prefs.js +1 -1
  19. package/dist/commands/core-migrate-policy-triggers-native.d.ts +21 -0
  20. package/dist/commands/core-migrate-policy-triggers-native.js +378 -0
  21. package/dist/commands/core-native-hook-helper-commands.d.ts +4 -0
  22. package/dist/commands/core-native-hook-helper-commands.js +56 -0
  23. package/dist/commands/core-native-hook-helpers-flag.d.ts +4 -0
  24. package/dist/commands/core-native-hook-helpers-flag.js +7 -0
  25. package/dist/commands/core-register-project-native.d.ts +30 -0
  26. package/dist/commands/core-register-project-native.js +347 -0
  27. package/dist/commands/core-work-mesh-live-rebind-native.d.ts +18 -0
  28. package/dist/commands/core-work-mesh-live-rebind-native.js +262 -0
  29. package/dist/commands/core-worker-share-native.d.ts +0 -1
  30. package/dist/commands/core-worker-share-native.js +1 -1
  31. package/dist/commands/core.js +7 -0
  32. package/dist/commands/lanes-message.js +3 -0
  33. package/dist/commands/lanes-questions.js +2 -1
  34. package/dist/commands/lanes-resume.js +3 -0
  35. package/dist/commands/lanes.js +151 -21
  36. package/dist/fast-core.js +10 -3
  37. package/dist/fast-scaffold.d.ts +2 -0
  38. package/dist/fast-scaffold.js +11 -0
  39. package/dist/lib/doctor/checks/work-context.js +9 -1
  40. package/dist/lib/lanes/config.d.ts +3 -0
  41. package/dist/lib/lanes/config.js +14 -0
  42. package/dist/lib/lanes/disk-guard.d.ts +66 -0
  43. package/dist/lib/lanes/disk-guard.js +172 -0
  44. package/dist/lib/lanes/flag-keys.d.ts +2 -0
  45. package/dist/lib/lanes/flag-keys.js +2 -0
  46. package/dist/lib/lanes/grants.d.ts +4 -3
  47. package/dist/lib/lanes/grants.js +12 -7
  48. package/dist/lib/lanes/hire-grant-dispatch.js +2 -3
  49. package/dist/lib/lanes/index.d.ts +1 -1
  50. package/dist/lib/lanes/index.js +1 -1
  51. package/dist/lib/lanes/reconcile.d.ts +7 -1
  52. package/dist/lib/lanes/reconcile.js +142 -11
  53. package/dist/lib/lanes/registry-flag.d.ts +6 -0
  54. package/dist/lib/lanes/registry-flag.js +25 -0
  55. package/dist/lib/lanes/spawn.d.ts +5 -0
  56. package/dist/lib/lanes/spawn.js +26 -1
  57. package/dist/lib/lanes/store.js +11 -0
  58. package/dist/lib/lanes/types.d.ts +11 -1
  59. package/dist/lib/lanes/types.js +1 -0
  60. package/dist/lib/mesh/client.js +23 -5
  61. package/dist/lib/mesh/denial-codes.d.ts +5 -0
  62. package/dist/lib/mesh/denial-codes.js +14 -0
  63. package/dist/lib/mesh/live/daemon/doctor.d.ts +2 -0
  64. package/dist/lib/mesh/live/daemon/doctor.js +11 -1
  65. package/dist/lib/search-index/embed-lock.d.ts +2 -0
  66. package/dist/lib/search-index/embed-lock.js +10 -1
  67. package/dist/lib/work-context/contract.d.ts +3 -0
  68. package/dist/lib/work-context/contract.schema.json +3 -1
  69. package/dist/lib/work-context/outbox.d.ts +14 -5
  70. package/dist/lib/work-context/outbox.js +48 -5
  71. package/dist/lib/work-context/reconcile.js +26 -5
  72. package/dist/main.d.ts +20 -0
  73. package/dist/main.js +8 -1
  74. package/dist/utils/core-eval-trigger.d.ts +4 -0
  75. package/dist/utils/core-eval-trigger.js +88 -0
  76. package/dist/utils/pack-contributions.d.ts +0 -5
  77. package/dist/utils/pack-contributions.js +1 -1
  78. package/dist/utils/redact-error-text.d.ts +0 -2
  79. package/dist/utils/redact-error-text.js +1 -1
  80. package/dist/utils/sandbox-runner-client.d.ts +2 -1
  81. package/package.json +1 -1
@@ -12,6 +12,7 @@ import { artifactIdentityKeys, rollbackStorySwitchAttribution, storySwitchNotice
12
12
  import { laneRunDir, probePidRecord, readPidRecord, readProcStarttime, spawnLaneWorker } from "../lib/lanes/spawn.js";
13
13
  import { keepAliveExclusion, keepAliveSupersededBy, LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
14
14
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
15
+ import { lowDiskGuardEnabledForLane } from "../lib/lanes/disk-guard.js";
15
16
  import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
16
17
  import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, callerMatchesSessionSenior, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
17
18
  export function createLaneMessageHandlers(deps) {
@@ -666,6 +667,7 @@ export function createLaneMessageHandlers(deps) {
666
667
  const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
667
668
  if (admission.action === "refuse" && admission.error)
668
669
  throw admission.error;
670
+ const guardEnabled = admission.action === "queue" ? false : await lowDiskGuardEnabledForLane(hqRoot, lane);
669
671
  return withResumeHireAdmission(ctx, lane, () => {
670
672
  if (admission.action === "queue") {
671
673
  const sessionId = lane.provider_session_id;
@@ -709,6 +711,7 @@ export function createLaneMessageHandlers(deps) {
709
711
  return queued;
710
712
  }
711
713
  const started = spawnLaneWorker(hqRoot, lane, {
714
+ lowDiskGuardEnabled: guardEnabled,
712
715
  env: ctx.env,
713
716
  modelDeps: {
714
717
  readFileSync: ctx.readFileSync,
@@ -134,7 +134,8 @@ export function createLaneQuestionsHandlers(deps) {
134
134
  const awaitingWakeConfirmed = lane.state !== "awaiting_input" ||
135
135
  (remote?.wake_status === "resumed" && remote.lane_state === "running") ||
136
136
  (remote?.wake_status === "queued" && remote.lane_state === "queued") ||
137
- (remote?.wake_status === "awaiting_answers" && remote.lane_state === "awaiting_input");
137
+ ((remote?.wake_status === "awaiting_answers" || remote?.wake_status === "deferred") &&
138
+ remote.lane_state === "awaiting_input");
138
139
  if (remote?.ok !== true ||
139
140
  remote.action !== "questions.answer" ||
140
141
  remoteQuestion?.question_id !== questionId ||
@@ -7,6 +7,7 @@ import { assertNoSecretText } from "../lib/lanes/text-scan.js";
7
7
  import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
8
8
  import { artifactIdentityKeys, rollbackStorySwitchAttribution, storySwitchNotice } from "../lib/lanes/story-switch.js";
9
9
  import { spawnLaneWorker } from "../lib/lanes/spawn.js";
10
+ import { lowDiskGuardEnabledForLane } from "../lib/lanes/disk-guard.js";
10
11
  import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, hasKeepAliveProgress, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
11
12
  export function createLaneResumeHandlers(deps) {
12
13
  const { archiveResumeMessage, assertLaneRunsHereForAdmission, assertLaneRunsHereForOperatorMutation, assessLaneAdmission, automaticBalanceMoveHistoryReason, authorizeFailedResume, authorizeResumeProfileRepin, casUpdateLane, dispatchOutpostLaneCommand, laneIsHostedHere, laneWithRecoveredProviderSession, mintMessageId, persistClaudeAccountSelection, prepareAccountMove, printJson, recordResumeSetup, resolveResumeProfile, selectAutomaticClaudeResumeAccount, validateResumeProfileModel, withResumeHireAdmission } = deps;
@@ -396,6 +397,7 @@ export function createLaneResumeHandlers(deps) {
396
397
  const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
397
398
  if (admission.action === "refuse" && admission.error)
398
399
  throw admission.error;
400
+ const guardEnabled = admission.action === "queue" ? false : await lowDiskGuardEnabledForLane(hqRoot, lane);
399
401
  return withResumeHireAdmission(ctx, lane, () => {
400
402
  if (admission.action === "queue") {
401
403
  const sessionId = lane.provider_session_id;
@@ -439,6 +441,7 @@ export function createLaneResumeHandlers(deps) {
439
441
  });
440
442
  }
441
443
  const started = spawnLaneWorker(hqRoot, lane, {
444
+ lowDiskGuardEnabled: guardEnabled,
442
445
  env: ctx.env,
443
446
  modelDeps: {
444
447
  readFileSync: ctx.readFileSync,
@@ -32,8 +32,9 @@ 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
34
  import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
35
- import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
36
- import { LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG } from "../lib/lanes/flag-keys.js";
35
+ import { readLanesRegistryFlag, readLanesRegistryFlagFailOff } from "../lib/lanes/registry-flag.js";
36
+ import { LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG } from "../lib/lanes/flag-keys.js";
37
+ import { CODEX_ROLLOUT_RETENTION_FLAG, configuredCodexRolloutHomes, lowDiskGuardEnabledForLane, retainCodexRollouts } from "../lib/lanes/disk-guard.js";
37
38
  import { isControllerAliasOutpostId } from "../lib/lanes/admission-host.js";
38
39
  import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
39
40
  import { syncOutpostLeadRegistries } from "../lib/lanes/autopilot-registry.js";
@@ -58,10 +59,10 @@ import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, lan
58
59
  import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
59
60
  import { listLaneReviews } from "../lib/lanes/reviews.js";
60
61
  import { effectiveAccountHome } from "../lib/lanes/account-home.js";
61
- 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, DEAD_HOST_MS, 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";
62
+ 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, isFinishedResumableHireChild, 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, DEAD_HOST_MS, 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";
62
63
  import { laneStories, storyWarning, } from "../lib/lanes/stories.js";
63
64
  import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
64
- import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
65
+ import { laneCompanyUidSource, resolveLaneCompany } from "../lib/lanes/lane-company.js";
65
66
  import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
66
67
  import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
67
68
  import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
@@ -475,10 +476,10 @@ function readQuestionBody(hqRoot, question) {
475
476
  function loadAllLanes(hqRoot) {
476
477
  return readAllLanes(hqRoot);
477
478
  }
478
- function laneHireFields(lane, lanes, aliveChildrenByParent) {
479
+ function laneHireFields(lane, lanes, aliveChildrenByParent, finishedChildrenDoNotCount = false) {
479
480
  const aliveChildCount = aliveChildrenByParent
480
481
  ? aliveChildrenByParent.get(lane.lane_id) ?? 0
481
- : aliveDirectChildren(lanes, lane.lane_id).length;
482
+ : aliveDirectChildren(lanes, lane.lane_id, finishedChildrenDoNotCount).length;
482
483
  const hireGrant = lane.hire_grant ?? 0;
483
484
  const hireSummary = `hire ${aliveChildCount}/${hireGrant}`;
484
485
  return {
@@ -488,6 +489,15 @@ function laneHireFields(lane, lanes, aliveChildrenByParent) {
488
489
  hire_summary: hireSummary,
489
490
  };
490
491
  }
492
+ function hireGrantFinishedChildrenDoNotCount(ctx, lane) {
493
+ const resolution = resolveLaneCompany(ctx.hqRoot(), lane);
494
+ const company = resolution.company ?? undefined;
495
+ return readLanesRegistryFlagFailOff(LANE_HIRE_GRANT_FINISHED_LANES_FLAG, {
496
+ company,
497
+ ...(company ? { companyUidSource: laneCompanyUidSource(resolution.source) } : {}),
498
+ env: ctx.env,
499
+ }, readLanesRegistryFlag);
500
+ }
491
501
  function mergeOwnerLaneHierarchy(supplied, ownerLanes) {
492
502
  const byId = new Map();
493
503
  for (const lane of supplied)
@@ -861,6 +871,18 @@ async function fetchOutpostListHireSummaries(ctx, outpostId, lanes, localLanes)
861
871
  }
862
872
  }
863
873
  const hierarchy = mergeOwnerLaneHierarchy(localLanes, ownerLanes);
874
+ const finishedChildrenDoNotCountByCompany = new Map();
875
+ const companyByLaneId = new Map();
876
+ for (const lane of lanes) {
877
+ const company = resolveLaneCompany(ctx.hqRoot(), lane).company;
878
+ companyByLaneId.set(lane.lane_id, company);
879
+ if (!company)
880
+ continue;
881
+ const key = `${company.uid}\0${company.slug}`;
882
+ if (!finishedChildrenDoNotCountByCompany.has(key)) {
883
+ finishedChildrenDoNotCountByCompany.set(key, await hireGrantFinishedChildrenDoNotCount(ctx, lane));
884
+ }
885
+ }
864
886
  const outcomes = new Map();
865
887
  for (const lane of lanes) {
866
888
  const matchingLanes = ownerLanes.filter((ownerLane) => ownerLane.lane_id === lane.lane_id);
@@ -878,7 +900,13 @@ async function fetchOutpostListHireSummaries(ctx, outpostId, lanes, localLanes)
878
900
  outcomes.set(lane.lane_id, { error: new LanesError("outpost_lane_hire_summary_invalid", `Outpost ${outpostId} returned a lane owned by a different host`, { outpost_id: outpostId, lane_id: lane.lane_id }) });
879
901
  continue;
880
902
  }
881
- outcomes.set(lane.lane_id, { fields: laneHireFields(ownerLane, hierarchy) });
903
+ const company = companyByLaneId.get(lane.lane_id) ?? resolveLaneCompany(ctx.hqRoot(), ownerLane).company;
904
+ const companyKey = company
905
+ ? `${company.uid}\0${company.slug}`
906
+ : undefined;
907
+ outcomes.set(lane.lane_id, {
908
+ fields: laneHireFields(ownerLane, hierarchy, undefined, companyKey === undefined ? false : finishedChildrenDoNotCountByCompany.get(companyKey) ?? false),
909
+ });
882
910
  }
883
911
  return outcomes;
884
912
  }
@@ -925,7 +953,14 @@ async function batchOwnerLaneHireFields(ctx, lanes, localLanes, options = {}) {
925
953
  }
926
954
  const OUTPOST_HIRE_SUMMARY_CONCURRENCY = 4;
927
955
  async function laneHirePresentation(ctx, lane, lanes) {
928
- const local = laneHireFields(lane, lanes);
956
+ // The flag only changes the count when a direct child is finished but resumable.
957
+ const hasFinishedChild = lanes.some((child) => child.senior.kind === "lane" &&
958
+ child.senior.id === lane.lane_id &&
959
+ isFinishedResumableHireChild(child.state));
960
+ const finishedChildrenDoNotCount = hasFinishedChild
961
+ ? await hireGrantFinishedChildrenDoNotCount(ctx, lane)
962
+ : false;
963
+ const local = laneHireFields(lane, lanes, undefined, finishedChildrenDoNotCount);
929
964
  if (lane.host.kind !== "outpost" ||
930
965
  isTerminalLaneState(lane.state) ||
931
966
  laneRunsHere(lane, reconcileHostFromCtx(ctx))) {
@@ -966,7 +1001,12 @@ async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
966
1001
  !sameHireGrantSenior(lane.senior, grantContext.expected_senior))) {
967
1002
  throw new LanesError("hire_grant_senior_changed", `Lane ${lane.lane_id}'s senior changed after the primary authorized its Outpost dispatch`, { lane_id: lane.lane_id, expected_senior: grantContext.expected_senior, current_senior: lane.senior });
968
1003
  }
969
- if (!isTerminalLaneState(lane.state) ||
1004
+ const finishedChildrenDoNotCount = isFinishedResumableHireChild(lane.state)
1005
+ ? await hireGrantFinishedChildrenDoNotCount(ctx, lane)
1006
+ : false;
1007
+ const shouldCheckGrant = isTerminalLaneState(lane.state) ||
1008
+ (finishedChildrenDoNotCount && isFinishedResumableHireChild(lane.state));
1009
+ if (!shouldCheckGrant ||
970
1010
  lane.senior.kind !== "lane" ||
971
1011
  !lane.senior.id) {
972
1012
  return fn();
@@ -1000,13 +1040,15 @@ async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
1000
1040
  }
1001
1041
  // A different resume already reactivated this lane; it occupies the slot
1002
1042
  // in the shared snapshot and is not a second hire.
1003
- if (!isTerminalLaneState(current.state))
1043
+ const currentNeedsGrantCheck = isTerminalLaneState(current.state) ||
1044
+ (finishedChildrenDoNotCount && isFinishedResumableHireChild(current.state));
1045
+ if (!currentNeedsGrantCheck)
1004
1046
  return fn();
1005
- const refusal = terminalChildResumeHireGrantError(current, snapshot);
1047
+ const refusal = terminalChildResumeHireGrantError(current, snapshot, finishedChildrenDoNotCount);
1006
1048
  if (refusal !== null)
1007
1049
  throw refusal;
1008
1050
  return fn();
1009
- }, suppliedHierarchy, remoteSenior === undefined ? undefined : new Set([remoteSenior.lane_id]));
1051
+ }, suppliedHierarchy, remoteSenior === undefined ? undefined : new Set([remoteSenior.lane_id]), finishedChildrenDoNotCount);
1010
1052
  }
1011
1053
  const CAPACITY_PROBE_TIMEOUT_MS = 15_000;
1012
1054
  const MAX_PLACEMENT_OUTPOSTS = 16;
@@ -3520,6 +3562,10 @@ async function runCreate(ctx, opts) {
3520
3562
  if (senior.kind === "lane" && senior.id) {
3521
3563
  await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot), new Set([senior.id]));
3522
3564
  }
3565
+ const seniorLane = senior.kind === "lane" ? readLane(hqRoot, senior.id) : undefined;
3566
+ const finishedChildrenDoNotCount = seniorLane
3567
+ ? await hireGrantFinishedChildrenDoNotCount(ctx, seniorLane)
3568
+ : false;
3523
3569
  const lane = senior.kind === "lane"
3524
3570
  ? withHireGrantAdmission(hqRoot, senior.id, ({ parent, children, parentGrant, availableChildGrant }) => {
3525
3571
  if (isTerminalLaneState(parent.state)) {
@@ -3540,7 +3586,7 @@ async function runCreate(ctx, opts) {
3540
3586
  });
3541
3587
  }
3542
3588
  return createLane(hqRoot, laneInit);
3543
- })
3589
+ }, undefined, new Set(), finishedChildrenDoNotCount)
3544
3590
  : createLane(hqRoot, laneInit);
3545
3591
  return { lane, admission, validation };
3546
3592
  };
@@ -3606,6 +3652,7 @@ async function runCreate(ctx, opts) {
3606
3652
  let started = lane;
3607
3653
  if (admission.action === "start") {
3608
3654
  started = spawnLaneWorker(hqRoot, lane, {
3655
+ lowDiskGuardEnabled: await lowDiskGuardEnabledForLane(hqRoot, lane),
3609
3656
  env: ctx.env,
3610
3657
  modelDeps: {
3611
3658
  readFileSync: ctx.readFileSync,
@@ -4360,11 +4407,12 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4360
4407
  }
4361
4408
  if (action === "grant") {
4362
4409
  const ownerLane = dispatchedOwnerLane(ctx, lane.lane_id, outpostId, remote.lane, behavior);
4410
+ const finishedChildrenDoNotCount = await hireGrantFinishedChildrenDoNotCount(ctx, ownerLane);
4363
4411
  printJson({
4364
4412
  ...remote,
4365
4413
  lane_id: ownerLane.lane_id,
4366
4414
  lane: ownerLane,
4367
- ...laneHireFields(ownerLane, grantHierarchy ?? []),
4415
+ ...laneHireFields(ownerLane, grantHierarchy ?? [], undefined, finishedChildrenDoNotCount),
4368
4416
  outpost_id: outpostId,
4369
4417
  outpost_command_id: dispatched.commandId,
4370
4418
  outpost_transport: "outposts_exec",
@@ -4729,6 +4777,7 @@ async function runPickup(ctx, opts) {
4729
4777
  }
4730
4778
  try {
4731
4779
  return spawnLaneWorker(hqRoot, current, {
4780
+ lowDiskGuardEnabled: await lowDiskGuardEnabledForLane(hqRoot, current),
4732
4781
  env: ctx.env,
4733
4782
  modelDeps: {
4734
4783
  readFileSync: ctx.readFileSync,
@@ -4874,10 +4923,28 @@ async function runList(ctx, opts) {
4874
4923
  const now = ctx.now();
4875
4924
  const host = reconcileHostFromCtx(ctx);
4876
4925
  const allLanes = laneSource;
4926
+ const lanesById = new Map(allLanes.map((lane) => [lane.lane_id, lane]));
4927
+ const companiesForHireFlags = new Map();
4928
+ const resolvedCompanyByLaneId = new Map();
4929
+ for (const lane of allLanes) {
4930
+ const company = resolveLaneCompany(hqRoot, lane).company;
4931
+ resolvedCompanyByLaneId.set(lane.lane_id, company);
4932
+ if (company)
4933
+ companiesForHireFlags.set(`${company.uid}\0${company.slug}`, lane);
4934
+ }
4935
+ const finishedChildrenFlagByCompany = new Map();
4936
+ await Promise.all([...companiesForHireFlags].map(async ([key, lane]) => {
4937
+ finishedChildrenFlagByCompany.set(key, await hireGrantFinishedChildrenDoNotCount(ctx, lane));
4938
+ }));
4877
4939
  const aliveChildrenByParent = new Map();
4878
4940
  for (const child of allLanes) {
4879
4941
  if (child.senior.kind !== "lane" || !child.senior.id || isTerminalLaneState(child.state))
4880
4942
  continue;
4943
+ const parent = lanesById.get(child.senior.id);
4944
+ const parentCompany = parent ? resolvedCompanyByLaneId.get(parent.lane_id) : undefined;
4945
+ const flagKey = parentCompany ? `${parentCompany.uid}\0${parentCompany.slug}` : undefined;
4946
+ if (flagKey !== undefined && finishedChildrenFlagByCompany.get(flagKey) && isFinishedResumableHireChild(child.state))
4947
+ continue;
4881
4948
  aliveChildrenByParent.set(child.senior.id, (aliveChildrenByParent.get(child.senior.id) ?? 0) + 1);
4882
4949
  }
4883
4950
  const ownerEligibleLaneIds = new Set();
@@ -4933,7 +5000,12 @@ async function runList(ctx, opts) {
4933
5000
  story_ids: laneStories(lane).map((story) => story.story_id),
4934
5001
  no_story_reason: lane.no_story_reason ?? null,
4935
5002
  ...(remoteQueued
4936
- ? { state: "remote", remote_state: lane.state, queued_reason: undefined, queued_details: undefined }
5003
+ ? {
5004
+ state: "remote",
5005
+ remote_state: lane.state,
5006
+ queued_reason: lane.queued_reason === "low_disk" ? lane.queued_reason : undefined,
5007
+ queued_details: lane.queued_reason === "low_disk" ? lane.queued_details : undefined,
5008
+ }
4937
5009
  : {}),
4938
5010
  overdue: Boolean(lane.overdue) || isOverdue(hqRoot, lane.lane_id, now),
4939
5011
  stalled: Boolean(lane.stalled),
@@ -4982,8 +5054,11 @@ function formatLaneListHuman(lanes) {
4982
5054
  const storyLabel = ids.length > 0
4983
5055
  ? "stories: " + ids.join(", ")
4984
5056
  : "no story: " + (lane.no_story_reason ?? "reason not recorded");
5057
+ const queueLabel = (lane.state === "queued" || lane.remote_state === "queued") && lane.queued_reason === "low_disk"
5058
+ ? ` queued: low_disk (HQ ${lane.queued_details?.reason === "low_disk" ? lane.queued_details.hq_free_bytes : "?"} bytes; provider ${lane.queued_details?.reason === "low_disk" ? lane.queued_details.provider_free_bytes : "?"} bytes free)`
5059
+ : "";
4985
5060
  return lane.lane_id + " " + lane.state + " " + lane.provider + " " + lane.project_id +
4986
- " " + storyLabel;
5061
+ " " + storyLabel + queueLabel;
4987
5062
  });
4988
5063
  if (rows.length === 0)
4989
5064
  rows.push("No lanes.");
@@ -5050,6 +5125,45 @@ async function runCapacity(ctx) {
5050
5125
  });
5051
5126
  printJson(snapshot);
5052
5127
  }
5128
+ async function runGcRollouts(ctx, opts) {
5129
+ const hqRoot = ctx.hqRoot();
5130
+ const lanes = readAllLanes(hqRoot);
5131
+ const companies = new Map();
5132
+ for (const lane of lanes) {
5133
+ const company = resolveLaneCompany(hqRoot, lane).company;
5134
+ if (company)
5135
+ companies.set(company.uid ?? company.slug, company);
5136
+ }
5137
+ if (companies.size === 0) {
5138
+ const company = await ctx.resolveCompany();
5139
+ companies.set(company.uid ?? company.slug, company);
5140
+ }
5141
+ let enabled = false;
5142
+ for (const company of companies.values()) {
5143
+ enabled ||= await readLanesRegistryFlag(CODEX_ROLLOUT_RETENTION_FLAG, {
5144
+ company,
5145
+ companyUidSource: "resolved_lane_company",
5146
+ env: ctx.env,
5147
+ });
5148
+ if (enabled)
5149
+ break;
5150
+ }
5151
+ if (!enabled) {
5152
+ printJson({ ok: true, action: "gc-rollouts", enabled: false, dry_run: Boolean(opts.dryRun), scanned: 0, compressed: [], deleted: [], bytes_freed: 0 });
5153
+ return;
5154
+ }
5155
+ const config = loadLanesConfig(hqRoot);
5156
+ const homes = configuredCodexRolloutHomes(hqRoot, config, ctx.hostname());
5157
+ const result = await retainCodexRollouts({
5158
+ homes,
5159
+ lanes,
5160
+ nowMs: ctx.now().getTime(),
5161
+ compressAfterMs: config.codex_rollout_compress_after_hours * 60 * 60 * 1000,
5162
+ deleteAfterMs: config.codex_rollout_delete_after_days * 24 * 60 * 60 * 1000,
5163
+ dryRun: opts.dryRun,
5164
+ });
5165
+ printJson({ ok: true, action: "gc-rollouts", enabled: true, dry_run: Boolean(opts.dryRun), ...result });
5166
+ }
5053
5167
  async function runOutpostIdentityBackfill(ctx, opts) {
5054
5168
  const id = opts.id?.trim();
5055
5169
  if (!id) {
@@ -5191,7 +5305,13 @@ async function runShow(ctx, laneId, hasPendingQuestionForLane) {
5191
5305
  storedLane.host.kind === "outpost" &&
5192
5306
  !laneRunsHere(storedLane, reconcileHostFromCtx(ctx));
5193
5307
  const lane = remoteQueued
5194
- ? { ...storedLane, state: "remote", remote_state: storedLane.state, queued_reason: undefined, queued_details: undefined }
5308
+ ? {
5309
+ ...storedLane,
5310
+ state: "remote",
5311
+ remote_state: storedLane.state,
5312
+ queued_reason: storedLane.queued_reason === "low_disk" ? storedLane.queued_reason : undefined,
5313
+ queued_details: storedLane.queued_reason === "low_disk" ? storedLane.queued_details : undefined,
5314
+ }
5195
5315
  : storedLane;
5196
5316
  const askDeliveryFailed = readJsonFile(askDeliveryFailedPath(hqRoot, laneId));
5197
5317
  const delivery = storedLane.state === "awaiting_input" &&
@@ -5535,8 +5655,9 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
5535
5655
  });
5536
5656
  return updated;
5537
5657
  }
5538
- function printGrantResult(hqRoot, updated, hierarchy) {
5539
- const fields = laneHireFields(updated, hierarchy ?? loadAllLanes(hqRoot));
5658
+ async function printGrantResult(ctx, hqRoot, updated, hierarchy) {
5659
+ const finishedChildrenDoNotCount = await hireGrantFinishedChildrenDoNotCount(ctx, updated);
5660
+ const fields = laneHireFields(updated, hierarchy ?? loadAllLanes(hqRoot), undefined, finishedChildrenDoNotCount);
5540
5661
  printJson({ ok: true, action: "grant", lane_id: updated.lane_id, lane: updated, ...fields });
5541
5662
  }
5542
5663
  /**
@@ -5739,7 +5860,7 @@ async function runGrant(ctx, laneId, opts) {
5739
5860
  return;
5740
5861
  }
5741
5862
  const updated = await applyHireGrant(ctx, initial.lane_id, hireGrant, "senior");
5742
- printGrantResult(hqRoot, updated);
5863
+ await printGrantResult(ctx, hqRoot, updated);
5743
5864
  }
5744
5865
  async function runApprove(ctx, laneId, opts) {
5745
5866
  const checkpoint = opts.checkpoint?.trim();
@@ -5892,7 +6013,7 @@ async function runGrantOwnerTarget(ctx, laneId, opts) {
5892
6013
  const grantContext = parseOutpostHireGrantContext(opts.grantContext, laneId);
5893
6014
  const hqRoot = ctx.hqRoot();
5894
6015
  const updated = await applyHireGrant(ctx, laneId, hireGrant, "ssm-owner-dispatch", grantContext);
5895
- printGrantResult(hqRoot, updated, mergeOwnerLaneHierarchy(grantContext.hierarchy, loadAllLanes(hqRoot)));
6016
+ await printGrantResult(ctx, hqRoot, updated, mergeOwnerLaneHierarchy(grantContext.hierarchy, loadAllLanes(hqRoot)));
5896
6017
  }
5897
6018
  async function runBackfillGrants(ctx, opts) {
5898
6019
  if (opts.apply && opts.dryRun) {
@@ -6020,6 +6141,7 @@ async function runAdmit(ctx, laneId, opts) {
6020
6141
  return;
6021
6142
  }
6022
6143
  const started = spawnLaneWorker(hqRoot, lane, {
6144
+ lowDiskGuardEnabled: await lowDiskGuardEnabledForLane(hqRoot, lane),
6023
6145
  env: ctx.env,
6024
6146
  modelDeps: {
6025
6147
  readFileSync: ctx.readFileSync,
@@ -8797,6 +8919,14 @@ export function registerLanesCommand(parent, deps = {}) {
8797
8919
  await messageHandlers.recoverQueuedMessages(ctx, lane.lane_id);
8798
8920
  });
8799
8921
  }));
8922
+ lanes
8923
+ .command("gc-rollouts")
8924
+ .description("Compress or remove unreferenced old Codex session rollouts")
8925
+ .option("--dry-run", "Report changes without modifying rollout files")
8926
+ .option("--json", "Emit JSON on stdout (default)")
8927
+ .action(wrap(async (opts) => {
8928
+ await runGcRollouts(ctx, opts);
8929
+ }));
8800
8930
  lanes
8801
8931
  .command("_keep-alive-terminal-trigger <laneId> <version> <dueAt>", { hidden: true })
8802
8932
  .action(wrap(async (laneId, versionText, dueAt) => {
package/dist/fast-core.js CHANGED
@@ -59,12 +59,13 @@ export async function runFastCore(argv) {
59
59
  if (typeof handler !== "function") {
60
60
  throw new Error(`Native fast handler ${entry.exportName} was not exported`);
61
61
  }
62
+ const readsStdin = entry.readsStdinFor?.(parsed.args) ?? entry.readsStdin ?? false;
62
63
  const chunks = [];
63
- if (entry.readsStdin) {
64
+ if (readsStdin) {
64
65
  for await (const chunk of process.stdin)
65
66
  chunks.push(chunk);
66
67
  }
67
- const code = await handler(parsed.args, entry.readsStdin ? Buffer.concat(chunks).toString("utf8") : "", process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
68
+ const code = await handler(parsed.args, readsStdin ? Buffer.concat(chunks).toString("utf8") : "", process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
68
69
  // Let Node drain handler output before terminating. The timeout guard's
69
70
  // stderr message must survive the native route's process boundary.
70
71
  process.exitCode = code;
@@ -80,12 +81,18 @@ export async function runFastCore(argv) {
80
81
  : process.cwd();
81
82
  const injectRoot = entry.kind === "shell" || entry.kind === "native" &&
82
83
  (entry.shellHqRoot === "live" || entry.shellHqRoot === "reconcile-default" && shouldInjectRoot);
84
+ const childEnv = {
85
+ ...(entry.name === "hq-session" ? { HQ_HQ_SESSION_NO_CLI: "1" } : {}),
86
+ ...(entry.name === "migrate-policy-triggers"
87
+ ? { CLAUDE_PROJECT_DIR: process.env.CLAUDE_PROJECT_DIR || process.cwd() }
88
+ : {}),
89
+ };
83
90
  const { code } = runBundledScript({
84
91
  asset,
85
92
  args: parsed.args,
86
93
  cwd,
87
94
  ...(injectRoot ? { hqRoot } : {}),
88
- env: entry.name === "hq-session" ? { HQ_HQ_SESSION_NO_CLI: "1" } : undefined,
95
+ ...(Object.keys(childEnv).length ? { env: childEnv } : {}),
89
96
  });
90
97
  // Propagate verbatim: these scripts use their exit code as their interface.
91
98
  process.exit(code);
@@ -40,6 +40,8 @@ export type FastScaffoldScript = {
40
40
  shellHqRoot?: "none" | "live" | "reconcile-default";
41
41
  /** Whether this handler consumes stdin. Shell companions inherit stdin directly. */
42
42
  readsStdin?: boolean;
43
+ /** Per-invocation stdin policy for handlers whose shell reads conditionally. */
44
+ readsStdinFor?: (args: readonly string[]) => boolean;
43
45
  /** Whether the bundled fast-route module closure needs Node's network timeout patch. */
44
46
  requiresNodeNetworkCompatibility?: boolean;
45
47
  };
@@ -184,6 +184,17 @@ export const FAST_SCAFFOLD_SCRIPTS = [
184
184
  shellCwd: "live",
185
185
  shellHqRoot: "live",
186
186
  },
187
+ ...[
188
+ { name: "eval-trigger", modulePath: "./commands/core-eval-trigger-native.js", exportName: "runEvalTriggerFast", asset: "core/scripts/eval-trigger.sh", root: "cwd", shellCwd: "caller", shellHqRoot: "none", readsStdinFor: (args) => args[0] === "--check" },
189
+ { name: "migrate-policy-triggers", modulePath: "./commands/core-migrate-policy-triggers-native.js", exportName: "runMigratePolicyTriggersFast", asset: "core/scripts/migrate-policy-triggers.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
190
+ { name: "register-project", modulePath: "./commands/core-register-project-native.js", exportName: "runRegisterProjectFast", asset: "core/scripts/register-project.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
191
+ { name: "work-mesh-live-rebind", modulePath: "./commands/core-work-mesh-live-rebind-native.js", exportName: "runWorkMeshLiveRebindFast", asset: "core/scripts/work-mesh-live-rebind.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
192
+ ].map((entry) => ({
193
+ ...entry,
194
+ kind: "native",
195
+ flagKey: "hq-core-native-fast-route",
196
+ familyFlagKey: "core-native-hook-helpers",
197
+ })),
187
198
  ...["resolve", "pickup", "repo", "secrets", "transfer"].map((name) => ({
188
199
  name: `delegate ${name}`,
189
200
  kind: "native",
@@ -89,12 +89,20 @@ function runChecks(deps) {
89
89
  message: `Outbox queued depth=${stats.depth} (acked=${stats.acked}).`,
90
90
  });
91
91
  const showQuarantineReason = stats.quarantined > 0 && outboxQuarantineStatusEnabled();
92
+ const quarantineDetails = [
93
+ stats.lastQuarantineHttpStatus !== null
94
+ ? `HTTP ${stats.lastQuarantineHttpStatus}`
95
+ : "",
96
+ stats.lastQuarantineServerCode ?? "",
97
+ ]
98
+ .filter(Boolean)
99
+ .join(" ");
92
100
  results.push({
93
101
  status: stats.quarantined > 0 ? "WARN" : "PASS",
94
102
  checkId: "work-context.outbox-quarantined",
95
103
  target: path.join(root, "outbox"),
96
104
  message: `Outbox quarantined count=${stats.quarantined}${showQuarantineReason
97
- ? ` (last reason=${stats.lastQuarantineReason ?? "unknown"})`
105
+ ? ` (last reason=${stats.lastQuarantineReason ?? "unknown"}${quarantineDetails ? `; ${quarantineDetails}` : ""})`
98
106
  : ""}.`,
99
107
  });
100
108
  // Default-company mode + migration capability (US-017B)
@@ -43,6 +43,9 @@ export declare const DEFAULT_SESSION_QUESTION_TIMEOUT_SECS: number;
43
43
  export declare const DEFAULT_BLOCKER_FRESHNESS_HOURS = 4;
44
44
  /** Manager key results must be re-measured within six hours by default. */
45
45
  export declare const DEFAULT_KR_FRESHNESS_HOURS = 6;
46
+ export declare const DEFAULT_LOW_DISK_MIN_FREE_BYTES: number;
47
+ export declare const DEFAULT_CODEX_ROLLOUT_COMPRESS_AFTER_HOURS = 24;
48
+ export declare const DEFAULT_CODEX_ROLLOUT_DELETE_AFTER_DAYS = 7;
46
49
  export declare const DEFAULT_TRACKER_CHECKS: TrackerChecksConfig;
47
50
  export declare const DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG: RunawaySearchGuardConfig;
48
51
  export declare const DEFAULT_LIVENESS_WINDOWS: LivenessWindows;
@@ -47,6 +47,9 @@ export const DEFAULT_SESSION_QUESTION_TIMEOUT_SECS = 10 * 60;
47
47
  export const DEFAULT_BLOCKER_FRESHNESS_HOURS = 4;
48
48
  /** Manager key results must be re-measured within six hours by default. */
49
49
  export const DEFAULT_KR_FRESHNESS_HOURS = 6;
50
+ export const DEFAULT_LOW_DISK_MIN_FREE_BYTES = 5 * 1024 ** 3;
51
+ export const DEFAULT_CODEX_ROLLOUT_COMPRESS_AFTER_HOURS = 24;
52
+ export const DEFAULT_CODEX_ROLLOUT_DELETE_AFTER_DAYS = 7;
50
53
  export const DEFAULT_TRACKER_CHECKS = {
51
54
  owner_question_surface_ms: 10 * 60 * 1000,
52
55
  open_question_age_ms: 2 * 60 * 60 * 1000,
@@ -455,6 +458,9 @@ export function documentedDefaultConfig(deps = defaultDeps()) {
455
458
  liveness: { ...DEFAULT_LIVENESS_WINDOWS },
456
459
  tracker: { checks: { ...DEFAULT_TRACKER_CHECKS } },
457
460
  runaway_search_guard: { ...DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG },
461
+ low_disk_min_free_bytes: DEFAULT_LOW_DISK_MIN_FREE_BYTES,
462
+ codex_rollout_compress_after_hours: DEFAULT_CODEX_ROLLOUT_COMPRESS_AFTER_HOURS,
463
+ codex_rollout_delete_after_days: DEFAULT_CODEX_ROLLOUT_DELETE_AFTER_DAYS,
458
464
  session_question_timeout_secs: DEFAULT_SESSION_QUESTION_TIMEOUT_SECS,
459
465
  blocker_freshness_hours: DEFAULT_BLOCKER_FRESHNESS_HOURS,
460
466
  kr_freshness_hours: DEFAULT_KR_FRESHNESS_HOURS,
@@ -491,6 +497,11 @@ export function parseLanesConfig(raw, deps = defaultDeps()) {
491
497
  ? documentedDefaultConfig(deps).providers[provider]
492
498
  : parseProvider(provider, entry, deps);
493
499
  }
500
+ const compressAfterHours = positiveInt(raw.codex_rollout_compress_after_hours, "codex_rollout_compress_after_hours", DEFAULT_CODEX_ROLLOUT_COMPRESS_AFTER_HOURS);
501
+ const deleteAfterDays = positiveInt(raw.codex_rollout_delete_after_days, "codex_rollout_delete_after_days", DEFAULT_CODEX_ROLLOUT_DELETE_AFTER_DAYS);
502
+ if (deleteAfterDays * 24 < compressAfterHours) {
503
+ throw new LanesConfigError("invalid_config", "codex_rollout_delete_after_days must be at least codex_rollout_compress_after_hours / 24", { codex_rollout_compress_after_hours: compressAfterHours, codex_rollout_delete_after_days: deleteAfterDays });
504
+ }
494
505
  return {
495
506
  schema_version: typeof raw.schema_version === "string"
496
507
  ? raw.schema_version
@@ -500,6 +511,9 @@ export function parseLanesConfig(raw, deps = defaultDeps()) {
500
511
  liveness: parseLiveness(raw.liveness),
501
512
  tracker: { checks: parseTrackerChecks(raw.tracker) },
502
513
  runaway_search_guard: parseRunawaySearchGuard(raw.runaway_search_guard),
514
+ low_disk_min_free_bytes: positiveInt(raw.low_disk_min_free_bytes, "low_disk_min_free_bytes", DEFAULT_LOW_DISK_MIN_FREE_BYTES),
515
+ codex_rollout_compress_after_hours: compressAfterHours,
516
+ codex_rollout_delete_after_days: deleteAfterDays,
503
517
  session_question_timeout_secs: positiveInt(raw.session_question_timeout_secs, "session_question_timeout_secs", DEFAULT_SESSION_QUESTION_TIMEOUT_SECS),
504
518
  blocker_freshness_hours: positiveNumber(raw.blocker_freshness_hours, "blocker_freshness_hours", DEFAULT_BLOCKER_FRESHNESS_HOURS),
505
519
  kr_freshness_hours: positiveNumber(raw.kr_freshness_hours, "kr_freshness_hours", DEFAULT_KR_FRESHNESS_HOURS),
@@ -0,0 +1,66 @@
1
+ import * as fs from "node:fs";
2
+ import { type ReadLanesFlagOptions } from "./registry-flag.js";
3
+ import type { LaneRecord, LanesConfig } from "./types.js";
4
+ export declare const LOW_DISK_GUARD_FLAG = "lanes.low-disk-guard";
5
+ export declare const CODEX_ROLLOUT_RETENTION_FLAG = "lanes.codex-rollout-retention";
6
+ export declare const DEFAULT_LOW_DISK_FREE_BYTES: number;
7
+ export declare const DEFAULT_LOW_DISK_FRACTION = 0.02;
8
+ export declare function lowDiskGuardEnabledForLane(hqRoot: string, lane: LaneRecord, readFlag?: (key: string, options: ReadLanesFlagOptions) => Promise<boolean>): Promise<boolean>;
9
+ export interface DiskStats {
10
+ bsize: number;
11
+ blocks: number;
12
+ bavail: number;
13
+ }
14
+ export type StatFs = (target: string) => DiskStats;
15
+ export interface DiskSpaceMeasurement {
16
+ path: string;
17
+ free_bytes: number;
18
+ total_bytes: number;
19
+ threshold_bytes: number;
20
+ low: boolean;
21
+ }
22
+ export declare function measureDiskSpace(target: string, statfs?: StatFs, minFreeBytes?: number): DiskSpaceMeasurement;
23
+ export declare function checkLaneDiskSpace(hqRoot: string, providerHome: string, statfs?: StatFs, minFreeBytes?: number): {
24
+ low: boolean;
25
+ hq: DiskSpaceMeasurement;
26
+ provider: DiskSpaceMeasurement;
27
+ };
28
+ export interface RolloutLane {
29
+ provider: string;
30
+ state: string;
31
+ provider_session_id?: string;
32
+ last_resume?: {
33
+ session_id?: string;
34
+ };
35
+ }
36
+ export interface RolloutRetentionOptions {
37
+ homes: string[];
38
+ lanes: RolloutLane[];
39
+ nowMs?: number;
40
+ compressAfterMs?: number;
41
+ deleteAfterMs?: number;
42
+ dryRun?: boolean;
43
+ readdirSync?: typeof fs.readdirSync;
44
+ statSync?: typeof fs.statSync;
45
+ readFileSync?: typeof fs.readFileSync;
46
+ writeFileSync?: typeof fs.writeFileSync;
47
+ renameSync?: typeof fs.renameSync;
48
+ unlinkSync?: typeof fs.unlinkSync;
49
+ utimesSync?: typeof fs.utimesSync;
50
+ compressFile?: (source: string, target: string) => Promise<number>;
51
+ }
52
+ export interface RolloutRetentionResult {
53
+ scanned: number;
54
+ compressed: string[];
55
+ deleted: string[];
56
+ bytes_freed: number;
57
+ }
58
+ /**
59
+ * Codex homes that rollout retention may touch: enabled accounts in the
60
+ * operator's lanes config file. Without that file the documented defaults
61
+ * point at the real `~/.codex`, so retention gets no homes at all.
62
+ */
63
+ export declare function configuredCodexRolloutHomes(hqRoot: string, config: LanesConfig, hostname: string): string[];
64
+ /** Operates only on rollout files under configured Codex `sessions/` trees. */
65
+ export declare function retainCodexRollouts(options: RolloutRetentionOptions): Promise<RolloutRetentionResult>;
66
+ //# sourceMappingURL=disk-guard.d.ts.map