@indigoai-us/hq-cli 5.227.1 → 5.228.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.228.1] — 2026-09-27
6
+
7
+ ### Fixed
8
+
9
+ - Reparenting an Outpost lane from the primary now dispatches to the owner,
10
+ where the hierarchy, tenant, depth, and enabled hire-grant checks are repeated.
11
+ The primary syncs the changed lane and each descendant whose depth changed.
12
+ `hq lanes show <lane-id>` refreshes that Outpost lane and its descendants
13
+ from the owner, including senior history, depth, and grants. Lane sessions
14
+ now share their lane's depth, and an SSM owner dispatch can change a
15
+ user-senior lane's grant without allowing a lane caller to impersonate the
16
+ user.
17
+
18
+ ## [5.228.0] — 2026-09-27
19
+
20
+ ### Changed
21
+
22
+ - Lane hire grants and Claude account balancing are always active.
23
+ `backfill-grants --apply` refuses to write; dry-run output remains available.
24
+
5
25
  ## [5.227.1] — 2026-09-27
6
26
 
7
27
  ### Fixed
@@ -7480,6 +7480,28 @@ export declare const COMMAND_CATALOG: readonly [{
7480
7480
  readonly description: "JSON on stdout (always)";
7481
7481
  }];
7482
7482
  readonly subcommands: readonly [];
7483
+ }, {
7484
+ readonly name: "reparent-target";
7485
+ readonly description: "";
7486
+ readonly aliases: readonly [];
7487
+ readonly hidden: true;
7488
+ readonly usage: "[options] <lane-id>";
7489
+ readonly arguments: readonly [{
7490
+ readonly name: "lane-id";
7491
+ readonly required: true;
7492
+ readonly variadic: false;
7493
+ }];
7494
+ readonly options: readonly [{
7495
+ readonly flags: "--senior <ref>";
7496
+ readonly description: "New senior ref";
7497
+ }, {
7498
+ readonly flags: "--reason <text>";
7499
+ readonly description: "Why the lane is being re-parented";
7500
+ }, {
7501
+ readonly flags: "--json";
7502
+ readonly description: "JSON on stdout (always)";
7503
+ }];
7504
+ readonly subcommands: readonly [];
7483
7505
  }, {
7484
7506
  readonly name: "grant-target";
7485
7507
  readonly description: "";
@@ -8285,7 +8307,7 @@ export declare const COMMAND_CATALOG: readonly [{
8285
8307
  readonly description: "claude";
8286
8308
  }, {
8287
8309
  readonly flags: "--apply";
8288
- readonly description: "Move eligible between-turn sessions (requires the Claude account balance gate)";
8310
+ readonly description: "Move eligible between-turn sessions to balance Claude accounts";
8289
8311
  }, {
8290
8312
  readonly flags: "--json";
8291
8313
  readonly description: "JSON on stdout (always)";
@@ -9670,6 +9670,35 @@ export const COMMAND_CATALOG = [
9670
9670
  ],
9671
9671
  "subcommands": []
9672
9672
  },
9673
+ {
9674
+ "name": "reparent-target",
9675
+ "description": "",
9676
+ "aliases": [],
9677
+ "hidden": true,
9678
+ "usage": "[options] <lane-id>",
9679
+ "arguments": [
9680
+ {
9681
+ "name": "lane-id",
9682
+ "required": true,
9683
+ "variadic": false
9684
+ }
9685
+ ],
9686
+ "options": [
9687
+ {
9688
+ "flags": "--senior <ref>",
9689
+ "description": "New senior ref"
9690
+ },
9691
+ {
9692
+ "flags": "--reason <text>",
9693
+ "description": "Why the lane is being re-parented"
9694
+ },
9695
+ {
9696
+ "flags": "--json",
9697
+ "description": "JSON on stdout (always)"
9698
+ }
9699
+ ],
9700
+ "subcommands": []
9701
+ },
9673
9702
  {
9674
9703
  "name": "grant-target",
9675
9704
  "description": "",
@@ -10717,7 +10746,7 @@ export const COMMAND_CATALOG = [
10717
10746
  },
10718
10747
  {
10719
10748
  "flags": "--apply",
10720
- "description": "Move eligible between-turn sessions (requires the Claude account balance gate)"
10749
+ "description": "Move eligible between-turn sessions to balance Claude accounts"
10721
10750
  },
10722
10751
  {
10723
10752
  "flags": "--json",
@@ -10,10 +10,9 @@ import { type TrackerMeshRequest } from "../lib/lanes/tracker-mesh.js";
10
10
  import type { OutpostIdentity } from "../lib/lanes/admission-host.js";
11
11
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
12
12
  export type { OutpostIdentity } from "../lib/lanes/admission-host.js";
13
- export { LANE_HIRE_GRANTS_FLAG };
14
13
  import { type OutpostDispatchResult } from "../lib/lanes/outpost-dispatch.js";
15
14
  import { emitLaneDecisionEvents, type LaneMesh } from "../lib/lanes/mesh-events.js";
16
- import { LANE_HIRE_GRANTS_FLAG, MAX_LANE_DEPTH, withLaneHireLocks, type LaneCompanyRef, type QuestionBody, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
15
+ import { MAX_LANE_DEPTH, withLaneHireLocks, type LaneCompanyRef, type QuestionBody, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
17
16
  import { questionBodyAbsPath } from "../lib/lanes/questions.js";
18
17
  import { type RunCompactionFn } from "../lib/lanes/compaction.js";
19
18
  import type { WatcherLoopDeps } from "../lib/lanes/watcher.js";
@@ -87,16 +86,12 @@ export interface LanesCommandDeps {
87
86
  monitorEvidence?: MonitorEvidenceReader;
88
87
  /** Test seam for the default-off outpost placement rollout flag. */
89
88
  lanePlacementEnabled?: () => boolean;
90
- /** Test seam for the default-off lane hire-grants rollout gate. */
91
- laneHireGrantsEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
92
89
  /** Test seam for the default-off story ledger flag. */
93
90
  storyLedgerEnabled?: () => boolean;
94
91
  /** Test seam for the SSM-only Outpost owner grant path. */
95
92
  isSsmOwnerDispatch?: () => boolean;
96
93
  /** Test seam for changing a lane between pre-lock and locked grant reads. */
97
94
  withGrantHireLocks?: typeof withLaneHireLocks;
98
- /** Test seam for the Claude account-balancing gate. */
99
- laneClaudeAccountBalanceEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
100
95
  /** Test seam for the default-off Outpost question visibility gate. */
101
96
  laneOutpostQuestionVisibilityEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
102
97
  /** Test seam for the hq-flags gated tracker commands. */
@@ -27,11 +27,10 @@ import { defaultTrackerActor, LANES_TRACKER_FLAG, LANES_TRACKER_MESH_FLAG, regis
27
27
  import { createOutpostHireGrantContext, parseOutpostHireGrantContext, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
28
28
  import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
29
29
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
30
- export { LANE_HIRE_GRANTS_FLAG };
31
30
  import { buildOutpostCapacityCommand, buildOutpostBalanceMoveCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostMoveTargetCommand, buildOutpostQuestionAnswerCommand, buildOutpostQuestionListCommand, buildOutpostQuestionShowCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, outpostRejectedEffortOption, parseOutpostPickupPayload, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
32
31
  import { outpostHireSummaryCacheKey, readOutpostHireSummaryCache, writeOutpostHireSummaryCache, } from "../lib/lanes/outpost-hire-cache.js";
33
32
  import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
34
- import { BALANCE_MOVABLE_STATES, CLAUDE_ACCOUNT_BALANCE_FLAG, accountLoadsForReport, assertBalanceApplyEnabled, planClaudeBalanceMoves, selectClaudeAccount, } from "../lib/lanes/claude-account-balance.js";
33
+ import { BALANCE_MOVABLE_STATES, accountLoadsForReport, planClaudeBalanceMoves, selectClaudeAccount, } from "../lib/lanes/claude-balance.js";
35
34
  import { parseUserYaml } from "../utils/user-yaml-error.js";
36
35
  import { backfillOutpostIdentityMarker as backfillOutpostIdentityMarkerImpl, createOutpostIdentityMarkerIfMissing, validateOutpostIdentityId, } from "../lib/lanes/outpost-identity.js";
37
36
  import { vaultApiFetch } from "../utils/vault-api.js";
@@ -42,7 +41,7 @@ import { assertNoSecretText } from "../lib/lanes/text-scan.js";
42
41
  import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshEventIdentity, } from "../lib/lanes/mesh-events.js";
43
42
  import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
44
43
  import { effectiveAccountHome } from "../lib/lanes/account-home.js";
45
- import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, LANE_STATES, PROVIDER_HOME_ENV, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, createQuestion, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installDrainHook, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listAccountStatuses, listLaneIds, listQuestionIds, listSessionLiveness, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseLivenessState, parseQuestionRecord, parseQuestionStatus, parseSeniorKind, readDaemonPresenceConnected, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, runReconcilePass, wakeAnsweredLaneNow, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, writeLanesConfig, appendDropboxMessage, answerDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, withLaneHireLocks, laneIdForAnswerDelivery, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "../lib/lanes/index.js";
44
+ import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, LANE_STATES, PROVIDER_HOME_ENV, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, createQuestion, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installDrainHook, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listAccountStatuses, listLaneIds, listQuestionIds, listSessionLiveness, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseLivenessState, parseQuestionRecord, parseQuestionStatus, parseSeniorKind, readDaemonPresenceConnected, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, runReconcilePass, wakeAnsweredLaneNow, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, writeLanesConfig, appendDropboxMessage, answerDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, withLaneHireLocks, laneIdForAnswerDelivery, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "../lib/lanes/index.js";
46
45
  import { activeLaneStories, laneStories, laneStory, storyWarning, } from "../lib/lanes/stories.js";
47
46
  import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
48
47
  import { keepAliveExclusion, LEAD_LANE_KEEP_ALIVE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
@@ -77,20 +76,12 @@ export const LANE_HOST_SCOPED_ACCOUNT_HOMES_FLAG = "lanes.host-scoped-account-ho
77
76
  function defaultLanePlacementEnabled() {
78
77
  return resolveProcessFlagGate(LANE_OUTPOST_LOAD_BALANCING_FLAG, { fallback: false }, () => false);
79
78
  }
80
- function defaultLaneClaudeAccountBalanceEnabled(company) {
81
- // Keep the local HQ_FLAG override and the registry reader's fail-closed
82
- // behavior, matching the lane hire-grants gate.
83
- return readLanesRegistryFlag(CLAUDE_ACCOUNT_BALANCE_FLAG, { company });
84
- }
85
79
  function defaultLaneTrackerEnabled(company) {
86
80
  return readLanesRegistryFlag(LANES_TRACKER_FLAG, { company });
87
81
  }
88
82
  function defaultLaneTrackerMeshEnabled(company) {
89
83
  return readLanesRegistryFlag(LANES_TRACKER_MESH_FLAG, { company });
90
84
  }
91
- function defaultLaneHireGrantsEnabled(company) {
92
- return readLanesRegistryFlag(LANE_HIRE_GRANTS_FLAG, { company });
93
- }
94
85
  function defaultLaneOutpostQuestionVisibilityEnabled(company, env) {
95
86
  return readLanesRegistryFlag(LANE_OUTPOST_QUESTION_VISIBILITY_FLAG, {
96
87
  company,
@@ -320,11 +311,9 @@ function resolveDeps(deps = {}) {
320
311
  })),
321
312
  fetchText: deps.fetchText ?? defaultFetchText,
322
313
  lanePlacementEnabled: deps.lanePlacementEnabled ?? defaultLanePlacementEnabled,
323
- laneHireGrantsEnabled: deps.laneHireGrantsEnabled ?? defaultLaneHireGrantsEnabled,
324
314
  storyLedgerEnabled: deps.storyLedgerEnabled ?? laneStoryLedgerEnabled,
325
315
  isSsmOwnerDispatch: deps.isSsmOwnerDispatch ?? (() => isSsmRunCommandProcess()),
326
316
  withGrantHireLocks: deps.withGrantHireLocks ?? withLaneHireLocks,
327
- laneClaudeAccountBalanceEnabled: deps.laneClaudeAccountBalanceEnabled ?? defaultLaneClaudeAccountBalanceEnabled,
328
317
  laneOutpostQuestionVisibilityEnabled: deps.laneOutpostQuestionVisibilityEnabled ??
329
318
  ((company) => defaultLaneOutpostQuestionVisibilityEnabled(company, env)),
330
319
  laneTrackerEnabled: deps.laneTrackerEnabled ?? defaultLaneTrackerEnabled,
@@ -1022,9 +1011,6 @@ async function laneHirePresentation(ctx, lane, lanes) {
1022
1011
  laneRunsHere(lane, reconcileHostFromCtx(ctx))) {
1023
1012
  return { ...local, hire_source: "local" };
1024
1013
  }
1025
- if (!await ctx.laneHireGrantsEnabled(lane.company)) {
1026
- return { ...local, hire_source: "local" };
1027
- }
1028
1014
  try {
1029
1015
  return { ...(await ownerLaneHireFields(ctx, lane)), hire_source: "owner" };
1030
1016
  }
@@ -1059,8 +1045,6 @@ async function withResumeHireAdmission(ctx, lane, fn) {
1059
1045
  !lane.senior.id) {
1060
1046
  return fn();
1061
1047
  }
1062
- if (!await ctx.laneHireGrantsEnabled(lane.company))
1063
- return fn();
1064
1048
  return withHireGrantAdmission(hqRoot, lane.senior.id, (snapshot) => {
1065
1049
  const current = readLane(hqRoot, lane.lane_id);
1066
1050
  // A different resume already reactivated this lane; it occupies the slot
@@ -1684,7 +1668,11 @@ function depthOfRef(hqRoot, ref, ctx, visited = new Set(), override) {
1684
1668
  senior: ref,
1685
1669
  });
1686
1670
  }
1687
- return depthOfRef(hqRoot, seniorOfSession(hqRoot, ref.id, ctx), ctx, visited, override) + 1;
1671
+ const sessionSenior = seniorOfSession(hqRoot, ref.id, ctx);
1672
+ const seniorDepth = depthOfRef(hqRoot, sessionSenior, ctx, visited, override);
1673
+ // A lane's execution session shares the lane's depth. Other sessions are
1674
+ // their own authority hop, including user-owned and nested sessions.
1675
+ return seniorDepth + (sessionSenior.kind === "lane" ? 0 : 1);
1688
1676
  }
1689
1677
  if (!ref.id) {
1690
1678
  throw new LanesError("invalid_senior_ref", "Lane senior is missing an id", {
@@ -2664,18 +2652,14 @@ async function runCreate(ctx, opts) {
2664
2652
  }
2665
2653
  const senior = opts.senior ? parseSeniorRef(opts.senior) : defaultSenior(ctx);
2666
2654
  assertSeniorExists(hqRoot, senior, ctx);
2667
- // Resolve tenant context before the registry read so the flag evaluator
2668
- // applies the same company scope as this lane.
2669
2655
  const company = companyFlag
2670
2656
  ? await ctx.resolveCompany(companyFlag)
2671
2657
  : null;
2672
- const hireGrantsEnabled = await ctx.laneHireGrantsEnabled(company);
2673
2658
  if (requestedHireGrant !== undefined &&
2674
2659
  !callerMatchesSenior(ctx, senior)) {
2675
2660
  throw new LanesError("hire_grant_unauthorized", `Only senior ${seniorKey(senior)} may set a lane's hire grant`, { senior, hire_grant: requestedHireGrant });
2676
2661
  }
2677
- if (hireGrantsEnabled &&
2678
- ctx.env.HQ_LANE_ID?.trim() &&
2662
+ if (ctx.env.HQ_LANE_ID?.trim() &&
2679
2663
  !callerMatchesSenior(ctx, senior)) {
2680
2664
  throw new LanesError("hire_grant_unauthorized", `Only senior lane ${senior.id} may create a direct child lane`, { senior_lane_id: senior.id });
2681
2665
  }
@@ -3031,7 +3015,7 @@ async function runCreate(ctx, opts) {
3031
3015
  }
3032
3016
  const allowedAccounts = validatedAccounts.size > 0 ? [...validatedAccounts] : profileAccounts;
3033
3017
  let accountSelection;
3034
- if (provider === "claude" && await ctx.laneClaudeAccountBalanceEnabled(company)) {
3018
+ if (provider === "claude") {
3035
3019
  const targetHost = placementHostOverride ?? (outpostId !== undefined
3036
3020
  ? { kind: "outpost", id: outpostId }
3037
3021
  : laneHostFromCtx(ctx));
@@ -3223,13 +3207,13 @@ async function runCreate(ctx, opts) {
3223
3207
  };
3224
3208
  const lane = senior.kind === "lane"
3225
3209
  ? withHireGrantAdmission(hqRoot, senior.id, ({ parent, children, parentGrant, availableChildGrant }) => {
3226
- if (hireGrantsEnabled && isTerminalLaneState(parent.state)) {
3210
+ if (isTerminalLaneState(parent.state)) {
3227
3211
  throw new LanesError("hire_grant_senior_inactive", `Senior lane ${parent.lane_id} is ${parent.state} and cannot hire lanes`, { senior_lane_id: parent.lane_id, state: parent.state });
3228
3212
  }
3229
- if (hireGrantsEnabled && children.length >= parentGrant) {
3213
+ if (children.length >= parentGrant) {
3230
3214
  throw hireGrantCapacityError(parent, children);
3231
3215
  }
3232
- if (hireGrantsEnabled && hireGrant > availableChildGrant) {
3216
+ if (hireGrant > availableChildGrant) {
3233
3217
  throw new LanesError("hire_grant_carve_exceeded", `Lane ${parent.lane_id} has ${availableChildGrant} unassigned hire grant remaining after its other alive children. Ask its senior to raise its grant before assigning ${hireGrant}.`, {
3234
3218
  senior_lane_id: parent.lane_id,
3235
3219
  parent_grant: parentGrant,
@@ -3622,7 +3606,6 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3622
3606
  let grantContext;
3623
3607
  let grantHierarchy;
3624
3608
  if (action === "grant") {
3625
- const enforcementEnabled = await ctx.laneHireGrantsEnabled(lane.company);
3626
3609
  let hierarchy = loadAllLanes(ctx.hqRoot());
3627
3610
  const ownerLanes = await readOutpostLaneRecords(ctx, outpostId);
3628
3611
  const byId = new Map(hierarchy.map((record) => [record.lane_id, record]));
@@ -3631,16 +3614,22 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3631
3614
  }
3632
3615
  hierarchy = [...byId.values()];
3633
3616
  grantHierarchy = hierarchy;
3634
- grantContext = createOutpostHireGrantContext(lane, hierarchy, enforcementEnabled);
3617
+ grantContext = createOutpostHireGrantContext(lane, hierarchy);
3635
3618
  }
3636
3619
  let dispatched;
3637
3620
  try {
3638
3621
  dispatched = await ctx.dispatchOutpostExec({
3639
3622
  outpostId,
3640
3623
  command: buildOutpostLaneCommand({
3641
- action: action === "grant" ? "grant-target" : action,
3624
+ action: action === "grant"
3625
+ ? "grant-target"
3626
+ : action === "reparent"
3627
+ ? "reparent-target"
3628
+ : action,
3642
3629
  laneId: lane.lane_id,
3643
- hqBinary: action === "grant" ? outpostCliOverride(ctx, outpostId) : undefined,
3630
+ hqBinary: action === "grant" || action === "reparent"
3631
+ ? outpostCliOverride(ctx, outpostId)
3632
+ : undefined,
3644
3633
  text: options.text,
3645
3634
  project: options.project,
3646
3635
  story: options.story,
@@ -3650,6 +3639,8 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3650
3639
  worker: options.worker,
3651
3640
  keepAlive: options.keepAlive,
3652
3641
  hire: options.hire,
3642
+ senior: options.senior,
3643
+ reason: options.reason,
3653
3644
  grantContext,
3654
3645
  compact: options.compact,
3655
3646
  }),
@@ -3703,6 +3694,9 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3703
3694
  owner_error: ownerErrorFromOutput(dispatched.stdout),
3704
3695
  });
3705
3696
  }
3697
+ const changedDescendantIds = action === "reparent"
3698
+ ? parseReparentDescendantIds(remote?.changed_descendant_ids, lane.lane_id, outpostId)
3699
+ : [];
3706
3700
  const resultConfirmed = remote !== null &&
3707
3701
  remote.ok === true &&
3708
3702
  remote.action === action &&
@@ -3711,8 +3705,11 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3711
3705
  ? remote.accepted === true
3712
3706
  : action === "grant"
3713
3707
  ? remote.hire_grant === Number(options.hire)
3714
- : remote.delivery_status === "worker_spawned" ||
3715
- remote.delivery_status === "queued_on_target_account");
3708
+ : action === "reparent"
3709
+ ? remote.lane !== null && typeof remote.lane === "object" &&
3710
+ Array.isArray(remote.changed_descendant_ids)
3711
+ : remote.delivery_status === "worker_spawned" ||
3712
+ remote.delivery_status === "queued_on_target_account");
3716
3713
  if (!resultConfirmed) {
3717
3714
  throw new LanesError("outpost_delivery_unconfirmed", `Outpost ${outpostId} completed exec without a confirmed ${action} result for lane ${lane.lane_id}`, {
3718
3715
  lane_id: lane.lane_id,
@@ -3733,6 +3730,25 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3733
3730
  });
3734
3731
  return;
3735
3732
  }
3733
+ if (action === "reparent") {
3734
+ const ownerLane = syncLaneRecordFromOwner(ctx.hqRoot(), lane.lane_id, outpostId, remote.lane, undefined, { allowNonTerminal: true });
3735
+ const descendants = [];
3736
+ for (const descendantId of changedDescendantIds) {
3737
+ const ownerDescendant = await readOutpostLaneSnapshot(ctx, outpostId, descendantId);
3738
+ descendants.push(syncLaneRecordFromOwner(ctx.hqRoot(), descendantId, outpostId, ownerDescendant, undefined, { allowNonTerminal: true }));
3739
+ }
3740
+ printJson({
3741
+ ...remote,
3742
+ lane_id: ownerLane.lane_id,
3743
+ lane: ownerLane,
3744
+ changed_descendant_ids: changedDescendantIds,
3745
+ descendants,
3746
+ outpost_id: outpostId,
3747
+ outpost_command_id: dispatched.commandId,
3748
+ outpost_transport: "outposts_exec",
3749
+ });
3750
+ return;
3751
+ }
3736
3752
  printJson({
3737
3753
  ...remote,
3738
3754
  outpost_id: outpostId,
@@ -3740,6 +3756,42 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3740
3756
  outpost_transport: "outposts_exec",
3741
3757
  });
3742
3758
  }
3759
+ function parseReparentDescendantIds(value, laneId, outpostId) {
3760
+ if (!Array.isArray(value) || value.some((id) => typeof id !== "string" || id.trim() === "")) {
3761
+ throw new LanesError("outpost_reparent_result_invalid", `Outpost ${outpostId} returned an invalid descendant list for lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId });
3762
+ }
3763
+ const ids = value;
3764
+ if (new Set(ids).size !== ids.length || ids.includes(laneId)) {
3765
+ throw new LanesError("outpost_reparent_result_invalid", `Outpost ${outpostId} returned duplicate or self-referential descendants for lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId, descendant_lane_ids: ids });
3766
+ }
3767
+ return ids;
3768
+ }
3769
+ async function readOutpostLaneSnapshot(ctx, outpostId, laneId) {
3770
+ let dispatched;
3771
+ try {
3772
+ dispatched = await ctx.dispatchOutpostExec({
3773
+ outpostId,
3774
+ command: buildOutpostLaneCommand({
3775
+ action: "show",
3776
+ laneId,
3777
+ hqBinary: outpostCliOverride(ctx, outpostId),
3778
+ }),
3779
+ timeoutSeconds: OUTPOST_HIRE_SUMMARY_TIMEOUT_SECONDS,
3780
+ waitMs: OUTPOST_HIRE_SUMMARY_WAIT_MS,
3781
+ });
3782
+ }
3783
+ catch (err) {
3784
+ throw new LanesError("outpost_lane_sync_unavailable", `Could not refresh descendant lane ${laneId} from Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { lane_id: laneId, outpost_id: outpostId });
3785
+ }
3786
+ if (dispatched.done !== true || dispatched.status !== "Success" || dispatched.exitCode !== 0) {
3787
+ throw new LanesError("outpost_lane_sync_unavailable", `Outpost ${outpostId} did not confirm the snapshot for descendant lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId, command_id: dispatched.commandId });
3788
+ }
3789
+ const remote = parseRemoteJson(dispatched.stdout, "Outpost descendant lane snapshot");
3790
+ if (!remote || remote.ok !== true || remote.action !== "show" || remote.lane === undefined) {
3791
+ throw new LanesError("outpost_lane_sync_invalid", `Outpost ${outpostId} returned an invalid snapshot for descendant lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId, command_id: dispatched.commandId });
3792
+ }
3793
+ return remote.lane;
3794
+ }
3743
3795
  async function dispatchOutpostQuestionAnswer(ctx, lane, questionId, options) {
3744
3796
  if (lane.host.kind !== "outpost") {
3745
3797
  throw new LanesError("outpost_dispatch_failed", `Lane ${lane.lane_id} has no Outpost host to answer its question`, { lane_id: lane.lane_id, question_id: questionId });
@@ -4177,25 +4229,15 @@ async function runList(ctx, opts) {
4177
4229
  })
4178
4230
  .sort(compareLaneQueueOrder);
4179
4231
  const ownerEligibleLaneIds = new Set();
4180
- const companyContexts = new Map();
4181
4232
  for (const lane of selectedLanes) {
4182
4233
  if (lane.host.kind !== "outpost" ||
4183
4234
  isTerminalLaneState(lane.state) ||
4184
4235
  laneRunsHere(lane, host)) {
4185
4236
  continue;
4186
4237
  }
4187
- const companyKey = JSON.stringify([
4188
- lane.company?.uid ?? null,
4189
- lane.company?.slug ?? null,
4190
- ]);
4191
4238
  ownerEligibleLaneIds.add(lane.lane_id);
4192
- companyContexts.set(companyKey, lane.company);
4193
4239
  }
4194
- const enabledByCompany = new Map(await Promise.all([...companyContexts.entries()].map(async ([companyKey, company]) => [
4195
- companyKey,
4196
- await ctx.laneHireGrantsEnabled(company),
4197
- ])));
4198
- const ownerLanes = [];
4240
+ const ownerLanes = selectedLanes.filter((lane) => ownerEligibleLaneIds.has(lane.lane_id));
4199
4241
  const hireByLane = new Map();
4200
4242
  for (const lane of selectedLanes) {
4201
4243
  if (!ownerEligibleLaneIds.has(lane.lane_id)) {
@@ -4205,18 +4247,6 @@ async function runList(ctx, opts) {
4205
4247
  });
4206
4248
  continue;
4207
4249
  }
4208
- const companyKey = JSON.stringify([
4209
- lane.company?.uid ?? null,
4210
- lane.company?.slug ?? null,
4211
- ]);
4212
- if (enabledByCompany.get(companyKey))
4213
- ownerLanes.push(lane);
4214
- else {
4215
- hireByLane.set(lane.lane_id, {
4216
- ...laneHireFields(lane, allLanes),
4217
- hire_source: "local",
4218
- });
4219
- }
4220
4250
  }
4221
4251
  if (ownerLanes.length > 0) {
4222
4252
  const ownerSummaries = await batchOwnerLaneHireFields(ctx, ownerLanes);
@@ -4760,9 +4790,38 @@ async function runMoveTarget(ctx, laneId, opts) {
4760
4790
  account: moved.account ?? null,
4761
4791
  });
4762
4792
  }
4793
+ async function refreshOutpostLaneCopiesForShow(ctx, lane, localLanes) {
4794
+ if (lane.host.kind !== "outpost" || laneRunsHere(lane, reconcileHostFromCtx(ctx))) {
4795
+ return lane;
4796
+ }
4797
+ const outpostId = lane.host.id;
4798
+ const descendantIds = localLanes
4799
+ .filter((candidate) => candidate.lane_id !== lane.lane_id &&
4800
+ candidate.host.kind === "outpost" && candidate.host.id === outpostId &&
4801
+ walkHitsLane(ctx.hqRoot(), candidate.senior, lane.lane_id, ctx))
4802
+ .map((candidate) => candidate.lane_id);
4803
+ const refresh = async (laneId) => {
4804
+ try {
4805
+ const ownerLane = await readOutpostLaneSnapshot(ctx, outpostId, laneId);
4806
+ return syncLaneRecordFromOwner(ctx.hqRoot(), laneId, outpostId, ownerLane, undefined, { allowNonTerminal: true });
4807
+ }
4808
+ catch (err) {
4809
+ const detail = isLanesError(err)
4810
+ ? `${err.error}: ${err.message}`
4811
+ : `outpost_lane_sync_unavailable: ${dispatchErrorMessage(err)}`;
4812
+ process.stderr.write(`hq lanes: owner snapshot refresh failed for lane ${laneId}: ${detail}\n`);
4813
+ return null;
4814
+ }
4815
+ };
4816
+ const refreshed = await mapWithConcurrency([lane.lane_id, ...descendantIds], OUTPOST_HIRE_SUMMARY_CONCURRENCY, refresh);
4817
+ const refreshedLane = refreshed[0] ?? null;
4818
+ return refreshedLane ?? lane;
4819
+ }
4763
4820
  async function runShow(ctx, laneId) {
4764
4821
  const hqRoot = ctx.hqRoot();
4765
- const storedLane = readLane(hqRoot, laneId);
4822
+ const initialLane = readLane(hqRoot, laneId);
4823
+ const initialLanes = loadAllLanes(hqRoot);
4824
+ const storedLane = await refreshOutpostLaneCopiesForShow(ctx, initialLane, initialLanes);
4766
4825
  const allLanes = loadAllLanes(hqRoot);
4767
4826
  const hire = await laneHirePresentation(ctx, storedLane, allLanes);
4768
4827
  const remoteQueued = storedLane.state === "queued" &&
@@ -4852,8 +4911,6 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
4852
4911
  (!grantContext || !sameHireGrantSenior(initial.senior, grantContext.expected_senior))) {
4853
4912
  throw new LanesError("hire_grant_senior_changed", `Lane ${laneId}'s senior changed after the primary authorized its grant update`, { lane_id: laneId, current_senior: initial.senior });
4854
4913
  }
4855
- const enforce = grantContext?.enforcement_enabled ??
4856
- await ctx.laneHireGrantsEnabled(initial.company);
4857
4914
  const lockedLaneIds = [
4858
4915
  laneId,
4859
4916
  ...(initial.senior.kind === "lane" && initial.senior.id
@@ -4878,7 +4935,7 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
4878
4935
  ? mergeOwnerLaneHierarchy(grantContext.hierarchy, loadAllLanes(hqRoot))
4879
4936
  : grantContext.hierarchy
4880
4937
  : loadAllLanes(hqRoot);
4881
- if (enforce && current.senior.kind === "lane") {
4938
+ if (current.senior.kind === "lane") {
4882
4939
  if (!current.senior.id) {
4883
4940
  throw new LanesError("hire_grant_senior_invalid", `Lane ${laneId} has a lane senior without an id`, {
4884
4941
  lane_id: laneId,
@@ -4908,19 +4965,17 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
4908
4965
  });
4909
4966
  }
4910
4967
  }
4911
- if (enforce) {
4912
- const children = aliveDirectChildren(hierarchy, current.lane_id);
4913
- const allocated = assignedChildHireGrants(children);
4914
- const minimumGrant = Math.max(allocated, children.length);
4915
- if (hireGrant < minimumGrant) {
4916
- throw new LanesError(allocated > children.length ? "hire_grant_below_allocated" : "hire_grant_below_occupancy", `Lane ${laneId} cannot lower its grant to ${hireGrant}; it has ${children.length} alive children and ${allocated} grant allocated to them`, {
4917
- lane_id: laneId,
4918
- requested_hire_grant: hireGrant,
4919
- alive_children: children.map((child) => child.lane_id),
4920
- alive_child_count: children.length,
4921
- assigned_child_grants: allocated,
4922
- });
4923
- }
4968
+ const children = aliveDirectChildren(hierarchy, current.lane_id);
4969
+ const allocated = assignedChildHireGrants(children);
4970
+ const minimumGrant = Math.max(allocated, children.length);
4971
+ if (hireGrant < minimumGrant) {
4972
+ throw new LanesError(allocated > children.length ? "hire_grant_below_allocated" : "hire_grant_below_occupancy", `Lane ${laneId} cannot lower its grant to ${hireGrant}; it has ${children.length} alive children and ${allocated} grant allocated to them`, {
4973
+ lane_id: laneId,
4974
+ requested_hire_grant: hireGrant,
4975
+ alive_children: children.map((child) => child.lane_id),
4976
+ alive_child_count: children.length,
4977
+ assigned_child_grants: allocated,
4978
+ });
4924
4979
  }
4925
4980
  return retryUpdateLane(hqRoot, laneId, "command", (record) => ({
4926
4981
  ...record,
@@ -5011,11 +5066,8 @@ async function runBackfillGrants(ctx, opts) {
5011
5066
  }
5012
5067
  const apply = Boolean(opts.apply);
5013
5068
  const hqRoot = ctx.hqRoot();
5014
- if (apply && await ctx.laneHireGrantsEnabled(null)) {
5015
- throw new LanesError("hire_grant_backfill_gate_enabled", "Backfill must be applied and verified before lanes.hire-grants is enabled", { flag: LANE_HIRE_GRANTS_FLAG });
5016
- }
5017
- if (apply && ctx.env.HQ_LANE_ID?.trim()) {
5018
- throw new LanesError("hire_grant_backfill_unauthorized", "A lane cannot apply the hire-grant backfill; run it from an operator session", {});
5069
+ if (apply) {
5070
+ throw new LanesError("hire_grant_backfill_gate_enabled", "Grant backfill can only be applied before always-on enforcement is released", {});
5019
5071
  }
5020
5072
  const allLanes = loadAllLanes(hqRoot);
5021
5073
  const profileIds = new Set(allLanes
@@ -5040,76 +5092,11 @@ async function runBackfillGrants(ctx, opts) {
5040
5092
  if (maxConcurrent !== undefined)
5041
5093
  profileMaxConcurrent.set(profileId, maxConcurrent);
5042
5094
  }
5043
- let rows = planHireGrantBackfill(allLanes, profileMaxConcurrent);
5044
- if (apply) {
5045
- const lanesById = new Map(allLanes.map((lane) => [lane.lane_id, lane]));
5046
- const targetCompanies = new Map();
5047
- for (const row of rows) {
5048
- if (row.action === "unchanged")
5049
- continue;
5050
- const lane = lanesById.get(row.lane_id);
5051
- if (!lane) {
5052
- throw new LanesError("hire_grant_backfill_target_changed", `Backfill target lane ${row.lane_id} is unavailable`, { lane_id: row.lane_id });
5053
- }
5054
- const company = lane.company;
5055
- if (!company)
5056
- continue;
5057
- targetCompanies.set(JSON.stringify([company.uid ?? null, company.slug ?? null]), company);
5058
- }
5059
- const companyGates = await Promise.all([...targetCompanies.values()].map((company) => ctx.laneHireGrantsEnabled(company)));
5060
- if (companyGates.some(Boolean)) {
5061
- throw new LanesError("hire_grant_backfill_gate_enabled", "Backfill must be applied and verified before lanes.hire-grants is enabled", { flag: LANE_HIRE_GRANTS_FLAG });
5062
- }
5063
- const shortfalls = rows.filter((row) => row.grant_shortfall > 0);
5064
- if (shortfalls.length > 0) {
5065
- throw new LanesError("hire_grant_backfill_allocation_shortfall", "Existing parent grants do not cover the grants allocated to alive children; resolve these rows before applying the backfill", { lanes: shortfalls });
5066
- }
5067
- rows = rows.map((row) => {
5068
- if (row.action === "unchanged")
5069
- return row;
5070
- const lane = readLane(hqRoot, row.lane_id);
5071
- const lockIds = [
5072
- lane.lane_id,
5073
- ...(lane.senior.kind === "lane" && lane.senior.id ? [lane.senior.id] : []),
5074
- ];
5075
- return withLaneHireLocks(hqRoot, lockIds, () => {
5076
- const current = readLane(hqRoot, row.lane_id);
5077
- if (current.hire_grant !== undefined) {
5078
- return {
5079
- ...row,
5080
- existing_grant: current.hire_grant,
5081
- proposed_grant: current.hire_grant,
5082
- action: "unchanged",
5083
- };
5084
- }
5085
- const latestLanes = loadAllLanes(hqRoot);
5086
- const latestRow = planHireGrantBackfill(latestLanes, profileMaxConcurrent)
5087
- .find((candidate) => candidate.lane_id === current.lane_id);
5088
- if (!latestRow) {
5089
- throw new LanesError("hire_grant_backfill_target_changed", `Lane ${current.lane_id} no longer qualifies for the hire-grant backfill`, { lane_id: current.lane_id });
5090
- }
5091
- if (latestRow.grant_shortfall > 0) {
5092
- throw new LanesError("hire_grant_backfill_allocation_shortfall", `Lane ${current.lane_id}'s existing grant does not cover grants allocated to its alive children`, { lane: latestRow });
5093
- }
5094
- const proposed = latestRow.proposed_grant;
5095
- const written = retryUpdateLane(hqRoot, current.lane_id, "command", (record) => ({
5096
- ...record,
5097
- ...(record.hire_grant === undefined ? { hire_grant: proposed } : {}),
5098
- }));
5099
- const value = written.hire_grant ?? 0;
5100
- return {
5101
- ...row,
5102
- existing_grant: value,
5103
- proposed_grant: value,
5104
- action: value === proposed ? "set" : "unchanged",
5105
- };
5106
- });
5107
- });
5108
- }
5095
+ const rows = planHireGrantBackfill(allLanes, profileMaxConcurrent);
5109
5096
  printJson({
5110
5097
  ok: true,
5111
5098
  action: "backfill-grants",
5112
- mode: apply ? "apply" : "dry-run",
5099
+ mode: "dry-run",
5113
5100
  lanes: rows,
5114
5101
  });
5115
5102
  }
@@ -5584,7 +5571,6 @@ async function prepareAccountMove(ctx, lane, input) {
5584
5571
  async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, options = {}) {
5585
5572
  if (lane.provider !== "claude")
5586
5573
  return undefined;
5587
- const balanceEnabled = await ctx.laneClaudeAccountBalanceEnabled(lane.company);
5588
5574
  const hqRoot = ctx.hqRoot();
5589
5575
  const config = loadLanesConfig(hqRoot);
5590
5576
  const currentConfig = lane.account === undefined
@@ -5601,19 +5587,6 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
5601
5587
  const accountNeedsRepair = lane.account !== undefined &&
5602
5588
  (currentConfig?.enabled !== true || currentLogin === false);
5603
5589
  const repairUnavailableAccount = options.repairUnavailableAccount === true && accountNeedsRepair;
5604
- if (!balanceEnabled && !(options.repairUnavailableAccount && accountNeedsRepair)) {
5605
- if (lane.account === undefined)
5606
- return undefined;
5607
- if (currentConfig === undefined || !currentConfig.enabled) {
5608
- throw new AccountResolutionError(currentConfig === undefined ? "unknown_account" : "account_disabled", currentConfig === undefined
5609
- ? `Lane ${lane.lane_id} records account ${JSON.stringify(lane.account)} which is not configured for claude`
5610
- : `Account ${JSON.stringify(lane.account)} is unavailable for claude`, { provider: "claude", account: lane.account });
5611
- }
5612
- if (currentLogin === false) {
5613
- throw new LanesError("account_not_logged_in", `Account ${lane.account} for claude is not logged in`, { provider: "claude", account: lane.account, lane_id: lane.lane_id });
5614
- }
5615
- return undefined;
5616
- }
5617
5590
  const profile = workerProfile?.profile ?? (lane.worker_profile !== undefined
5618
5591
  ? loadWorkerProfile(hqRoot, lane.worker_profile)
5619
5592
  : undefined);
@@ -5641,28 +5614,7 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
5641
5614
  const mayRebalance = BALANCE_MOVABLE_STATES.includes(lane.state);
5642
5615
  if (!accountNeedsRepair && !mayRebalance)
5643
5616
  return undefined;
5644
- const balanceHosts = balanceEnabled
5645
- ? await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config)
5646
- : (() => {
5647
- const lanes = loadAllLanes(hqRoot).filter((candidate) => candidate.host.kind === lane.host.kind && candidate.host.id === lane.host.id);
5648
- return {
5649
- snapshots: [{
5650
- capacity: buildHostCapacitySnapshot({
5651
- host: lane.host,
5652
- lanes,
5653
- config,
5654
- existsSync: ctx.existsSync,
5655
- probe: ctx.probeProvider,
5656
- env: ctx.env,
5657
- hostname: ctx.hostname(),
5658
- now: ctx.now(),
5659
- }),
5660
- lanes,
5661
- laneListAvailable: true,
5662
- }],
5663
- unavailableHosts: [],
5664
- };
5665
- })();
5617
+ const balanceHosts = await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config);
5666
5618
  const targetHost = balanceHosts.snapshots.find((host) => host.capacity.host.kind === lane.host.kind && host.capacity.host.id === lane.host.id);
5667
5619
  const currentTargetAccount = lane.account === undefined
5668
5620
  ? undefined
@@ -6294,7 +6246,6 @@ async function applyClaudeBalanceMove(ctx, suggestion) {
6294
6246
  return { lane_id: suggestion.lane_id, host: suggestion.host, from: suggestion.from, to: suggestion.to, state: suggestion.state };
6295
6247
  }
6296
6248
  const lane = laneWithRecoveredProviderSession(ctx.hqRoot(), readLane(ctx.hqRoot(), suggestion.lane_id));
6297
- assertBalanceApplyEnabled(await ctx.laneClaudeAccountBalanceEnabled(lane.company));
6298
6249
  if (lane.state === "running") {
6299
6250
  throw new LanesError("lane_running", `Lane ${lane.lane_id} is running; balance never moves a running turn`, {
6300
6251
  lane_id: lane.lane_id,
@@ -6324,7 +6275,6 @@ async function applyClaudeBalanceMove(ctx, suggestion) {
6324
6275
  }
6325
6276
  async function runClaudeBalanceMoveTarget(ctx, laneId, opts) {
6326
6277
  const lane = laneWithRecoveredProviderSession(ctx.hqRoot(), readLane(ctx.hqRoot(), laneId));
6327
- assertBalanceApplyEnabled(await ctx.laneClaudeAccountBalanceEnabled(lane.company));
6328
6278
  assertLaneRunsHereForOperatorMutation(ctx, lane, "balance-move-target");
6329
6279
  if (lane.provider !== "claude") {
6330
6280
  throw new LanesError("claude_account_balance_provider_mismatch", `Lane ${laneId} does not use Claude`, {
@@ -7065,6 +7015,29 @@ function archiveResumeMessage(hqRoot, laneId, message) {
7065
7015
  fs.writeFileSync(path.join(dir, `${message.message_id}.resume.jsonl`), `${JSON.stringify(message)}\n`, "utf8");
7066
7016
  }
7067
7017
  async function runReparent(ctx, laneId, opts) {
7018
+ const hqRoot = ctx.hqRoot();
7019
+ if (!opts.senior) {
7020
+ throw new LanesError("missing_senior", "--senior is required", {});
7021
+ }
7022
+ const initial = readLane(hqRoot, laneId);
7023
+ if (initial.host.kind === "outpost" && !laneRunsHere(initial, reconcileHostFromCtx(ctx))) {
7024
+ await dispatchOutpostLaneCommand(ctx, initial, "reparent", {
7025
+ senior: opts.senior,
7026
+ reason: opts.reason,
7027
+ });
7028
+ return;
7029
+ }
7030
+ const result = await reparentOnOwner(ctx, laneId, opts);
7031
+ printJson({
7032
+ ok: true,
7033
+ action: "reparent",
7034
+ lane_id: result.lane.lane_id,
7035
+ lane: result.lane,
7036
+ pending_questions: result.pendingQuestions,
7037
+ changed_descendant_ids: result.changedDescendantIds,
7038
+ });
7039
+ }
7040
+ async function reparentOnOwner(ctx, laneId, opts) {
7068
7041
  const hqRoot = ctx.hqRoot();
7069
7042
  if (!opts.senior) {
7070
7043
  throw new LanesError("missing_senior", "--senior is required", {});
@@ -7072,19 +7045,32 @@ async function runReparent(ctx, laneId, opts) {
7072
7045
  const nextSenior = parseSeniorRef(opts.senior);
7073
7046
  assertSeniorExists(hqRoot, nextSenior, ctx);
7074
7047
  const initial = readLane(hqRoot, laneId);
7075
- const needsHireGrantGate = nextSenior.kind === "lane" &&
7076
- seniorKey(initial.senior) !== seniorKey(nextSenior);
7077
- const hireGrantsEnabled = needsHireGrantGate
7078
- ? await ctx.laneHireGrantsEnabled(initial.company)
7079
- : false;
7080
7048
  const lockIds = [
7081
7049
  laneId,
7082
7050
  ...(initial.senior.kind === "lane" && initial.senior.id ? [initial.senior.id] : []),
7083
7051
  ...(nextSenior.kind === "lane" && nextSenior.id ? [nextSenior.id] : []),
7084
7052
  ];
7085
- withLaneHireLocks(hqRoot, lockIds, () => runReparentLocked(ctx, laneId, opts, hireGrantsEnabled));
7053
+ return withLaneHireLocks(hqRoot, lockIds, () => runReparentLocked(ctx, laneId, opts));
7086
7054
  }
7087
- function runReparentLocked(ctx, laneId, opts, hireGrantsEnabled) {
7055
+ async function runReparentOwnerTarget(ctx, laneId, opts) {
7056
+ if (!ctx.isSsmOwnerDispatch()) {
7057
+ throw new LanesError("outpost_reparent_dispatch_unauthorized", "The Outpost reparent target is available only to an SSM Run Command dispatch", { lane_id: laneId });
7058
+ }
7059
+ const lane = readLane(ctx.hqRoot(), laneId);
7060
+ if (lane.host.kind !== "outpost" || !laneIsHostedHere(ctx, lane)) {
7061
+ throw new LanesError("lane_remote", `Outpost reparent target cannot mutate lane ${laneId} on this host`, { lane_id: laneId, host: lane.host });
7062
+ }
7063
+ const result = await reparentOnOwner(ctx, laneId, opts);
7064
+ printJson({
7065
+ ok: true,
7066
+ action: "reparent",
7067
+ lane_id: result.lane.lane_id,
7068
+ lane: result.lane,
7069
+ pending_questions: result.pendingQuestions,
7070
+ changed_descendant_ids: result.changedDescendantIds,
7071
+ });
7072
+ }
7073
+ function runReparentLocked(ctx, laneId, opts) {
7088
7074
  const hqRoot = ctx.hqRoot();
7089
7075
  if (!opts.senior) {
7090
7076
  throw new LanesError("missing_senior", "--senior is required", {});
@@ -7098,8 +7084,7 @@ function runReparentLocked(ctx, laneId, opts, hireGrantsEnabled) {
7098
7084
  }
7099
7085
  const seniorCompany = companyOfSenior(hqRoot, nextSenior, ctx);
7100
7086
  assertSameTenant(lane.company, nextSenior, seniorCompany, { lane_id: laneId });
7101
- if (hireGrantsEnabled &&
7102
- nextSenior.kind === "lane" &&
7087
+ if (nextSenior.kind === "lane" &&
7103
7088
  seniorKey(lane.senior) !== seniorKey(nextSenior)) {
7104
7089
  const parent = readLane(hqRoot, nextSenior.id);
7105
7090
  if (isTerminalLaneState(parent.state)) {
@@ -7154,7 +7139,11 @@ function runReparentLocked(ctx, laneId, opts, hireGrantsEnabled) {
7154
7139
  to_depth: descendantDepth,
7155
7140
  });
7156
7141
  }
7157
- descendants.push({ laneId: other.lane_id, depth: descendantDepth });
7142
+ descendants.push({
7143
+ laneId: other.lane_id,
7144
+ depth: descendantDepth,
7145
+ changed: descendantDepth !== oldDescendantDepth,
7146
+ });
7158
7147
  }
7159
7148
  const at = ctx.now().toISOString();
7160
7149
  const updated = casUpdateLane(hqRoot, laneId, (current) => {
@@ -7199,12 +7188,13 @@ function runReparentLocked(ctx, laneId, opts, hireGrantsEnabled) {
7199
7188
  };
7200
7189
  });
7201
7190
  }
7202
- printJson({
7203
- ok: true,
7204
- action: "reparent",
7191
+ return {
7205
7192
  lane: updated,
7206
- pending_questions: pending.length,
7207
- });
7193
+ pendingQuestions: pending.length,
7194
+ changedDescendantIds: descendants
7195
+ .filter((descendant) => descendant.changed)
7196
+ .map((descendant) => descendant.laneId),
7197
+ };
7208
7198
  }
7209
7199
  const CANONICAL_LOG_STREAMS = [
7210
7200
  "watcher.err",
@@ -8558,27 +8548,9 @@ async function runAccountsBalance(ctx, opts) {
8558
8548
  const hqRoot = ctx.hqRoot();
8559
8549
  const config = loadLanesConfig(hqRoot);
8560
8550
  const loaded = await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config);
8561
- const laneBalanceGateEnabled = new Map();
8562
- const companyBalanceGateEnabled = new Map();
8563
- const lanes = loaded.snapshots.flatMap((host) => host.lanes);
8564
- for (const lane of lanes) {
8565
- const companyKey = JSON.stringify([lane.company?.uid ?? null, lane.company?.slug ?? null]);
8566
- let enabled = companyBalanceGateEnabled.get(companyKey);
8567
- if (enabled === undefined) {
8568
- enabled = await ctx.laneClaudeAccountBalanceEnabled(lane.company);
8569
- companyBalanceGateEnabled.set(companyKey, enabled);
8570
- }
8571
- laneBalanceGateEnabled.set(lane.lane_id, enabled);
8572
- }
8573
- // With no lanes there is no tenant scope to resolve, so report and enforce
8574
- // the unscoped/default gate. Otherwise the report reflects whether any
8575
- // represented lane company has the gate enabled.
8576
- if (lanes.length === 0) {
8577
- companyBalanceGateEnabled.set(JSON.stringify([null, null]), await ctx.laneClaudeAccountBalanceEnabled());
8578
- }
8579
- const balanceGateEnabled = [...companyBalanceGateEnabled.values()].some(Boolean);
8580
- if (opts.apply)
8581
- assertBalanceApplyEnabled(balanceGateEnabled);
8551
+ // Retain the established response field for clients; balancing is now
8552
+ // unconditional, so this compatibility value is always true.
8553
+ const balanceGateEnabled = true;
8582
8554
  const loads = accountLoadsForReport({ hosts: loaded.snapshots, config });
8583
8555
  const suggestions = planClaudeBalanceMoves({ hosts: loaded.snapshots, config });
8584
8556
  const selections = loaded.snapshots.map((host) => {
@@ -8640,16 +8612,6 @@ async function runAccountsBalance(ctx, opts) {
8640
8612
  });
8641
8613
  continue;
8642
8614
  }
8643
- if (laneBalanceGateEnabled.get(suggestion.lane_id) !== true) {
8644
- skipped.push({
8645
- lane_id: suggestion.lane_id,
8646
- host: suggestion.host,
8647
- from: suggestion.from,
8648
- to: suggestion.to,
8649
- reason: "claude_account_balance_disabled",
8650
- });
8651
- continue;
8652
- }
8653
8615
  try {
8654
8616
  moved.push(await applyClaudeBalanceMove(ctx, suggestion));
8655
8617
  }
@@ -9165,6 +9127,14 @@ export function registerLanesCommand(parent, deps = {}) {
9165
9127
  .action(wrap(async (opts) => {
9166
9128
  await runCreate(ctx, opts);
9167
9129
  }));
9130
+ lanes
9131
+ .command("reparent-target", { hidden: true })
9132
+ .argument("<lane-id>", "Lane id")
9133
+ .requiredOption("--senior <ref>", "New senior ref")
9134
+ .option("--reason <text>", "Why the lane is being re-parented")
9135
+ .action(wrap(async (laneId, opts) => {
9136
+ await runReparentOwnerTarget(ctx, laneId, opts);
9137
+ }));
9168
9138
  lanes
9169
9139
  .command("grant-target", { hidden: true })
9170
9140
  .argument("<lane-id>", "Lane id")
@@ -9514,7 +9484,7 @@ export function registerLanesCommand(parent, deps = {}) {
9514
9484
  .command("balance")
9515
9485
  .description("Report Claude account load and suggest safe between-turn moves")
9516
9486
  .requiredOption("--provider <name>", "claude")
9517
- .option("--apply", "Move eligible between-turn sessions (requires the Claude account balance gate)")
9487
+ .option("--apply", "Move eligible between-turn sessions to balance Claude accounts")
9518
9488
  .option("--json", "JSON on stdout (always)")
9519
9489
  .action(wrap(async (opts) => {
9520
9490
  await runAccountsBalance(ctx, opts);
@@ -1,7 +1,6 @@
1
1
  import type { AuthState } from "./accounts.js";
2
2
  import type { HostCapacityAccount, HostCapacitySnapshot } from "./host-capacity.js";
3
3
  import type { LaneHost, LaneRecord, LanesConfig } from "./types.js";
4
- export declare const CLAUDE_ACCOUNT_BALANCE_FLAG = "lanes.claude-account-balance";
5
4
  export declare const BALANCE_LIVE_STATES: readonly ["running", "queued", "awaiting_input"];
6
5
  export declare const BALANCE_MOVABLE_STATES: readonly ["interrupted", "done", "awaiting_input"];
7
6
  export interface BalanceHostSnapshot {
@@ -82,5 +81,4 @@ export declare function planClaudeBalanceMoves(input: {
82
81
  hosts: readonly BalanceHostSnapshot[];
83
82
  config: LanesConfig;
84
83
  }): BalanceMoveSuggestion[];
85
- export declare function assertBalanceApplyEnabled(enabled: boolean): void;
86
- //# sourceMappingURL=claude-account-balance.d.ts.map
84
+ //# sourceMappingURL=claude-balance.d.ts.map
@@ -1,5 +1,4 @@
1
1
  import { LanesError } from "./errors.js";
2
- export const CLAUDE_ACCOUNT_BALANCE_FLAG = "lanes.claude-account-balance";
3
2
  export const BALANCE_LIVE_STATES = ["running", "queued", "awaiting_input"];
4
3
  export const BALANCE_MOVABLE_STATES = ["interrupted", "done", "awaiting_input"];
5
4
  function sameHost(a, b) {
@@ -263,9 +262,4 @@ export function planClaudeBalanceMoves(input) {
263
262
  }
264
263
  return suggestions;
265
264
  }
266
- export function assertBalanceApplyEnabled(enabled) {
267
- if (!enabled) {
268
- throw new LanesError("claude_account_balance_disabled", `Applying Claude account balance moves requires hq-flags gate ${CLAUDE_ACCOUNT_BALANCE_FLAG}`, { flag: CLAUDE_ACCOUNT_BALANCE_FLAG });
269
- }
270
- }
271
- //# sourceMappingURL=claude-account-balance.js.map
265
+ //# sourceMappingURL=claude-balance.js.map
@@ -1,6 +1,5 @@
1
1
  import { type LaneRecord } from "./types.js";
2
2
  import { LanesError } from "./errors.js";
3
- export declare const LANE_HIRE_GRANTS_FLAG = "lanes.hire-grants";
4
3
  export type LaneHireGrantRow = Pick<LaneRecord, "lane_id" | "state" | "senior" | "hire_grant">;
5
4
  export declare function aliveDirectChildren<T extends LaneHireGrantRow>(lanes: readonly T[], parentLaneId: string): T[];
6
5
  export declare function assignedChildHireGrants(children: readonly LaneHireGrantRow[], exceptLaneId?: string): number;
@@ -27,4 +26,4 @@ export interface HireGrantBackfillRow {
27
26
  }
28
27
  /** Select alive manager lanes and calculate the non-destructive one-time backfill. */
29
28
  export declare function planHireGrantBackfill(lanes: readonly LaneRecord[], profileMaxConcurrent: ReadonlyMap<string, number>): HireGrantBackfillRow[];
30
- //# sourceMappingURL=hire-grants.d.ts.map
29
+ //# sourceMappingURL=grants.d.ts.map
@@ -1,7 +1,6 @@
1
1
  import { isTerminalLaneState } from "./types.js";
2
2
  import { listLaneIds, readLane, withLaneHireLocks } from "./store.js";
3
3
  import { LanesError } from "./errors.js";
4
- export const LANE_HIRE_GRANTS_FLAG = "lanes.hire-grants";
5
4
  export function aliveDirectChildren(lanes, parentLaneId) {
6
5
  return lanes.filter((lane) => lane.senior.kind === "lane" &&
7
6
  lane.senior.id === parentLaneId &&
@@ -144,4 +143,4 @@ export function planHireGrantBackfill(lanes, profileMaxConcurrent) {
144
143
  })
145
144
  .sort((a, b) => a.lane_id.localeCompare(b.lane_id));
146
145
  }
147
- //# sourceMappingURL=hire-grants.js.map
146
+ //# sourceMappingURL=grants.js.map
@@ -13,7 +13,7 @@ export interface OutpostHireGrantContext {
13
13
  hierarchy: HireGrantLaneSnapshot[];
14
14
  }
15
15
  /** Encode only the owner-relevant hierarchy from the primary's complete store. */
16
- export declare function createOutpostHireGrantContext(target: LaneRecord, lanes: readonly LaneRecord[], enforcementEnabled: boolean): string;
16
+ export declare function createOutpostHireGrantContext(target: LaneRecord, lanes: readonly LaneRecord[]): string;
17
17
  /** Decode and validate an owner grant context, binding it to the target lane. */
18
18
  export declare function parseOutpostHireGrantContext(encoded: string, targetLaneId: string): OutpostHireGrantContext;
19
19
  export declare function sameHireGrantSenior(a: SeniorRef, b: SeniorRef): boolean;
@@ -1,4 +1,4 @@
1
- import { aliveDirectChildren } from "./hire-grants.js";
1
+ import { aliveDirectChildren } from "./grants.js";
2
2
  import { LanesError } from "./errors.js";
3
3
  import { isLaneState, isSeniorKind, } from "./types.js";
4
4
  const HIRE_GRANT_CONTEXT_SCHEMA = "hq-lane-hire-grant-context.v1";
@@ -24,29 +24,27 @@ function snapshot(lane) {
24
24
  };
25
25
  }
26
26
  /** Encode only the owner-relevant hierarchy from the primary's complete store. */
27
- export function createOutpostHireGrantContext(target, lanes, enforcementEnabled) {
27
+ export function createOutpostHireGrantContext(target, lanes) {
28
28
  const current = lanes.find((lane) => lane.lane_id === target.lane_id);
29
29
  if (!current || !sameSenior(current.senior, target.senior)) {
30
30
  throw new LanesError("hire_grant_hierarchy_unavailable", `Cannot build an authoritative hire-grant hierarchy for lane ${target.lane_id}`, { lane_id: target.lane_id });
31
31
  }
32
32
  const relevant = new Map();
33
33
  relevant.set(current.lane_id, current);
34
- if (enforcementEnabled) {
35
- if (current.senior.kind === "lane") {
36
- const parentId = current.senior.id;
37
- const parent = parentId ? lanes.find((lane) => lane.lane_id === parentId) : undefined;
38
- if (!parent) {
39
- throw new LanesError("hire_grant_senior_invalid", `Lane ${target.lane_id} has no senior lane in the primary hierarchy`, { lane_id: target.lane_id, senior_lane_id: parentId ?? null });
40
- }
41
- relevant.set(parent.lane_id, parent);
42
- for (const sibling of aliveDirectChildren(lanes, parent.lane_id)) {
43
- relevant.set(sibling.lane_id, sibling);
44
- }
34
+ if (current.senior.kind === "lane") {
35
+ const parentId = current.senior.id;
36
+ const parent = parentId ? lanes.find((lane) => lane.lane_id === parentId) : undefined;
37
+ if (!parent) {
38
+ throw new LanesError("hire_grant_senior_invalid", `Lane ${target.lane_id} has no senior lane in the primary hierarchy`, { lane_id: target.lane_id, senior_lane_id: parentId ?? null });
45
39
  }
46
- for (const child of aliveDirectChildren(lanes, current.lane_id)) {
47
- relevant.set(child.lane_id, child);
40
+ relevant.set(parent.lane_id, parent);
41
+ for (const sibling of aliveDirectChildren(lanes, parent.lane_id)) {
42
+ relevant.set(sibling.lane_id, sibling);
48
43
  }
49
44
  }
45
+ for (const child of aliveDirectChildren(lanes, current.lane_id)) {
46
+ relevant.set(child.lane_id, child);
47
+ }
50
48
  const hierarchy = [...relevant.values()]
51
49
  .map(snapshot)
52
50
  .sort((a, b) => a.lane_id.localeCompare(b.lane_id));
@@ -55,7 +53,7 @@ export function createOutpostHireGrantContext(target, lanes, enforcementEnabled)
55
53
  }
56
54
  const context = {
57
55
  schema_version: HIRE_GRANT_CONTEXT_SCHEMA,
58
- enforcement_enabled: enforcementEnabled,
56
+ enforcement_enabled: true,
59
57
  expected_senior: copySenior(current.senior),
60
58
  hierarchy,
61
59
  };
@@ -150,7 +148,7 @@ export function parseOutpostHireGrantContext(encoded, targetLaneId) {
150
148
  if (!target || !sameSenior(target.senior, expectedSenior)) {
151
149
  throw contextError("Outpost grant context does not match the target's expected senior");
152
150
  }
153
- if (record.enforcement_enabled && expectedSenior.kind === "lane" &&
151
+ if (expectedSenior.kind === "lane" &&
154
152
  (!expectedSenior.id || !byId.has(expectedSenior.id))) {
155
153
  throw contextError("Outpost grant context is missing the target's senior lane");
156
154
  }
@@ -3,8 +3,8 @@
3
3
  */
4
4
  export { AccountResolutionError, LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesConfigError, LanesError, WorkerResolutionError, isLaneVersionConflict, isLanesError, } from "./errors.js";
5
5
  export { MAX_LANE_DEPTH } from "./constants.js";
6
- export { assignedChildHireGrants, aliveDirectChildren, hireGrantCapacityError, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./hire-grants.js";
7
- export type { HireGrantBackfillRow } from "./hire-grants.js";
6
+ export { assignedChildHireGrants, aliveDirectChildren, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
7
+ export type { HireGrantBackfillRow } from "./grants.js";
8
8
  export { compareLaneQueueOrder, hasActiveAdmissionReservation, hasActivePendingResume, hasEarlierEligibleQueuedLane, laneOccupiesRunningSlot, laneAdmissionCapReason, laneRunsOnHost, withAllLaneAdmissionLocks, withLaneAdmissionLock, } from "./admission.js";
9
9
  export type { AdmissionCapReason, AdmissionHost } from "./admission.js";
10
10
  export { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "./host-capacity.js";
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export { AccountResolutionError, LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesConfigError, LanesError, WorkerResolutionError, isLaneVersionConflict, isLanesError, } from "./errors.js";
5
5
  export { MAX_LANE_DEPTH } from "./constants.js";
6
- export { assignedChildHireGrants, aliveDirectChildren, hireGrantCapacityError, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./hire-grants.js";
6
+ export { assignedChildHireGrants, aliveDirectChildren, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
7
7
  export { compareLaneQueueOrder, hasActiveAdmissionReservation, hasActivePendingResume, hasEarlierEligibleQueuedLane, laneOccupiesRunningSlot, laneAdmissionCapReason, laneRunsOnHost, withAllLaneAdmissionLocks, withLaneAdmissionLock, } from "./admission.js";
8
8
  export { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "./host-capacity.js";
9
9
  export { CONFIG_SCHEMA_VERSION, ENVELOPE_DECISIONS, ENVELOPE_SCHEMA_VERSION, HISTORY_CAP, HOST_KINDS, LANE_PROVIDERS, LANE_SCHEMA_VERSION, LANE_STORY_STATUSES, LANE_STATES, LIVENESS_STATES, QUESTION_BODY_STORES, QUESTION_SCHEMA_VERSION, QUESTION_STATUSES, QUEUED_REASONS, RAISED_VIA, ROUTING_REASONS, SENIOR_KINDS, STORY_STATUSES, TERMINAL_LANE_STATES, isEnvelopeDecision, isHostKind, isLaneProvider, isLaneState, isLivenessState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseLaneEnvelope, parseLaneProvider, parseLaneState, parseLivenessState, parseMajorSchema, parseQuestionStatus, parseSeniorKind, } from "./types.js";
@@ -27,7 +27,7 @@ export interface OutpostDispatchResult {
27
27
  stderr: string;
28
28
  }
29
29
  export interface OutpostLaneCommand {
30
- action: "message" | "resume" | "stop" | "show" | "grant" | "grant-target";
30
+ action: "message" | "resume" | "stop" | "show" | "grant" | "grant-target" | "reparent-target";
31
31
  laneId: string;
32
32
  hqBinary?: string;
33
33
  text?: string;
@@ -40,6 +40,8 @@ export interface OutpostLaneCommand {
40
40
  keepAlive?: boolean;
41
41
  hire?: string;
42
42
  grantContext?: string;
43
+ senior?: string;
44
+ reason?: string;
43
45
  compact?: boolean;
44
46
  }
45
47
  export declare function buildOutpostQuestionAnswerCommand(input: {
@@ -150,6 +150,8 @@ export function buildOutpostLaneCommand(input) {
150
150
  ["--worker", input.worker],
151
151
  ["--hire", input.hire],
152
152
  ["--grant-context", input.grantContext],
153
+ ["--senior", input.senior],
154
+ ["--reason", input.reason],
153
155
  ]) {
154
156
  if (value !== undefined)
155
157
  args.push(flag, value);
@@ -15,7 +15,7 @@ import { loadLanesConfig, providerQuotaDefaultHoldMs } from "./config.js";
15
15
  import { MAX_LANE_DEPTH } from "./constants.js";
16
16
  import { AccountResolutionError, LanesError, isLaneVersionConflict } from "./errors.js";
17
17
  import { loadWorkerProfile } from "./profile.js";
18
- import { LANE_HIRE_GRANTS_FLAG, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./hire-grants.js";
18
+ import { terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
19
19
  import { classifySessionLiveness, defaultLivenessDeps, loadLivenessIndex, readDaemonPresenceConnected, unindexedClaudeSessionLiveness, } from "./liveness.js";
20
20
  import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, envelopeRepairMode, isPidRecordAlive, laneRunDir, readPidRecord, readSpawnManifest, markSpawnFailure, spawnEnvelopeRepair, spawnLaneWorker, retryLaneOnAlternateAccount, } from "./spawn.js";
21
21
  import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, updateLane, updateQuestion, } from "./store.js";
@@ -807,7 +807,6 @@ export async function runPassBody(hqRoot, deps) {
807
807
  }
808
808
  const readRegistryFlag = deps.readRegistryFlag ?? readLanesRegistryFlag;
809
809
  const keepAliveFlagCompanies = new Map();
810
- const keepAliveHireGrantFlagCompanies = new Map();
811
810
  for (const lane of lanes) {
812
811
  if (!(lane.state === "done" || lane.state === "interrupted" || lane.state === "killed" || lane.state === "cancelled" || lane.state === "failed") ||
813
812
  (lane.keep_alive !== true && lane.worker_profile === undefined) ||
@@ -824,30 +823,18 @@ export async function runPassBody(hqRoot, deps) {
824
823
  }
825
824
  const key = keepAliveCompanyKey(lane.company);
826
825
  keepAliveFlagCompanies.set(key, lane.company);
827
- if (lane.senior.kind === "lane" && lane.senior.id) {
828
- keepAliveHireGrantFlagCompanies.set(key, lane.company);
829
- }
830
826
  }
831
827
  const keepAliveFlagByCompany = new Map();
832
- const keepAliveHireGrantFlagByCompany = new Map();
833
828
  const env = deps.env ?? process.env;
834
- await Promise.all([
835
- ...[...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
836
- keepAliveFlagByCompany.set(key, await readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, {
837
- company: company ?? undefined,
838
- env,
839
- }));
840
- }),
841
- ...[...keepAliveHireGrantFlagCompanies.entries()].map(async ([key, company]) => {
842
- keepAliveHireGrantFlagByCompany.set(key, await readRegistryFlag(LANE_HIRE_GRANTS_FLAG, {
843
- company: company ?? undefined,
844
- env,
845
- }));
846
- }),
847
- ]);
829
+ await Promise.all([...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
830
+ keepAliveFlagByCompany.set(key, await readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, {
831
+ company: company ?? undefined,
832
+ env,
833
+ }));
834
+ }));
848
835
  await withAllLaneAdmissionLocks(hqRoot, async () => {
849
836
  const freshLanes = loadLaneRecords(hqRoot, summary);
850
- resumeKeepAlive(hqRoot, freshLanes, summary, deps, now, keepAliveFlagByCompany, keepAliveHireGrantFlagByCompany);
837
+ resumeKeepAlive(hqRoot, freshLanes, summary, deps, now, keepAliveFlagByCompany);
851
838
  const drainLanes = loadLaneRecords(hqRoot, summary);
852
839
  const freshQuestions = loadQuestionRecords(hqRoot, summary);
853
840
  await admitQueued(hqRoot, drainLanes, summary, deps, now);
@@ -1545,7 +1532,7 @@ function latestEnvelopeMtimeMs(hqRoot, lane, ref) {
1545
1532
  throw new LanesError("keep_alive_envelope_timestamp_unavailable", `Cannot stat latest envelope for lane ${lane.lane_id}: ${err instanceof Error ? err.message : String(err)}`, { lane_id: lane.lane_id, path: filePath });
1546
1533
  }
1547
1534
  }
1548
- function resumeKeepAlive(hqRoot, lanes, summary, deps, now, flagByCompany, hireGrantFlagByCompany) {
1535
+ function resumeKeepAlive(hqRoot, lanes, summary, deps, now, flagByCompany) {
1549
1536
  const nowMs = now.getTime();
1550
1537
  const nowIso = now.toISOString();
1551
1538
  const config = loadLanesConfig(hqRoot);
@@ -1882,8 +1869,7 @@ function resumeKeepAlive(hqRoot, lanes, summary, deps, now, flagByCompany, hireG
1882
1869
  }), deps.dryRun);
1883
1870
  const seniorId = current.senior.kind === "lane" ? current.senior.id : undefined;
1884
1871
  let queued;
1885
- if (seniorId &&
1886
- hireGrantFlagByCompany.get(keepAliveCompanyKey(current.company)) === true) {
1872
+ if (seniorId) {
1887
1873
  try {
1888
1874
  queued = withHireGrantAdmission(hqRoot, seniorId, (snapshot) => {
1889
1875
  const latestLane = readLane(hqRoot, current.lane_id);
@@ -15,7 +15,7 @@ import { fileURLToPath } from "node:url";
15
15
  import { PROVIDER_HOME_ENV, accountStatus, isLoggedOut, resolveAccount, selectBalancedAccount, } from "./accounts.js";
16
16
  import { effectiveAccountHome } from "./account-home.js";
17
17
  import { buildHostCapacitySnapshot } from "./host-capacity.js";
18
- import { selectClaudeAccount } from "./claude-account-balance.js";
18
+ import { selectClaudeAccount } from "./claude-balance.js";
19
19
  import { COMPACTION_PROMPT, compactionAfterUsage, compactionDue, compactLaneSession, } from "./compaction.js";
20
20
  import { installDrainHook } from "./hooks-install.js";
21
21
  import { loadLanesConfig, providerAutoCompactTokens, providerCompactAfterMs, } from "./config.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.227.1",
3
+ "version": "5.228.1",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {