@indigoai-us/hq-cli 5.309.1 → 5.311.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 (99) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/assets/scaffold/core/scripts/hq-dm-bind.sh +585 -0
  3. package/assets/scaffold/core/scripts/jobs-validate.sh +1 -1
  4. package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +152 -0
  5. package/dist/command-catalog.generated.d.ts +28 -2
  6. package/dist/command-catalog.generated.js +34 -2
  7. package/dist/commands/channels.d.ts +1 -9
  8. package/dist/commands/channels.js +2 -2
  9. package/dist/commands/company-transfer.d.ts +4 -28
  10. package/dist/commands/company-transfer.js +5 -49
  11. package/dist/commands/core-checkpoint-stop-gate-native.d.ts +34 -0
  12. package/dist/commands/core-checkpoint-stop-gate-native.js +713 -0
  13. package/dist/commands/core-checkpoint.d.ts +0 -24
  14. package/dist/commands/core-checkpoint.js +4 -4
  15. package/dist/commands/core-command-catalog.js +1 -1
  16. package/dist/commands/core-dm-bind-native.d.ts +32 -0
  17. package/dist/commands/core-dm-bind-native.js +668 -0
  18. package/dist/commands/core-hq-session-native.d.ts +39 -0
  19. package/dist/commands/core-hq-session-native.js +579 -0
  20. package/dist/commands/core-jobs-native.js +1 -4
  21. package/dist/commands/core-native-session-flag.d.ts +4 -0
  22. package/dist/commands/core-native-session-flag.js +7 -0
  23. package/dist/commands/core-native-utilities.d.ts +2 -1
  24. package/dist/commands/core-native-utilities.js +4 -2
  25. package/dist/commands/core-native-worker-skills-flag.d.ts +4 -0
  26. package/dist/commands/core-native-worker-skills-flag.js +7 -0
  27. package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +32 -0
  28. package/dist/commands/core-work-mesh-bind-trusted-native.js +190 -0
  29. package/dist/commands/core-worker-lint-native.d.ts +4 -0
  30. package/dist/commands/core-worker-lint-native.js +160 -0
  31. package/dist/commands/core-worker-share-native.d.ts +5 -0
  32. package/dist/commands/core-worker-share-native.js +260 -0
  33. package/dist/commands/core-worker-skills-command.d.ts +8 -0
  34. package/dist/commands/core-worker-skills-command.js +33 -0
  35. package/dist/commands/core-worker-skills-runtime.d.ts +33 -0
  36. package/dist/commands/core-worker-skills-runtime.js +86 -0
  37. package/dist/commands/core.js +10 -2
  38. package/dist/commands/dm.d.ts +3 -7
  39. package/dist/commands/dm.js +3 -3
  40. package/dist/commands/integrations-import.d.ts +1 -1
  41. package/dist/commands/integrations-import.js +34 -19
  42. package/dist/commands/lanes-accounts.js +3 -1
  43. package/dist/commands/lanes-message.js +84 -10
  44. package/dist/commands/lanes-shared.d.ts +4 -1
  45. package/dist/commands/lanes.d.ts +4 -1
  46. package/dist/commands/lanes.js +48 -7
  47. package/dist/commands/personal-connector-settings-flag.d.ts +4 -0
  48. package/dist/commands/personal-connector-settings-flag.js +7 -0
  49. package/dist/commands/report.d.ts +1 -26
  50. package/dist/commands/report.js +6 -6
  51. package/dist/fast-core.js +15 -2
  52. package/dist/fast-scaffold.js +25 -2
  53. package/dist/index.js +8 -0
  54. package/dist/lib/bot/continuity.d.ts +2 -1
  55. package/dist/lib/bot/job-completions.d.ts +0 -2
  56. package/dist/lib/bot/job-completions.js +2 -2
  57. package/dist/lib/bot/local-config.d.ts +0 -10
  58. package/dist/lib/bot/local-config.js +3 -9
  59. package/dist/lib/bot/owner-context.d.ts +2 -6
  60. package/dist/lib/bot/owner-context.js +3 -3
  61. package/dist/lib/bot/runtime/windows.d.ts +0 -2
  62. package/dist/lib/bot/runtime/windows.js +1 -1
  63. package/dist/lib/bot/setup-explain.d.ts +0 -4
  64. package/dist/lib/bot/setup-explain.js +2 -2
  65. package/dist/lib/daemon/catalog.d.ts +3 -2
  66. package/dist/lib/daemon/hq-cloud.d.ts +2 -2
  67. package/dist/lib/daemon/hq-cloud.js +1 -1
  68. package/dist/lib/daemon/machine-type.d.ts +3 -4
  69. package/dist/lib/daemon/machine-type.js +2 -2
  70. package/dist/lib/daemon/remote-control.d.ts +0 -1
  71. package/dist/lib/daemon/remote-control.js +1 -1
  72. package/dist/lib/daemon/supervisor.d.ts +4 -9
  73. package/dist/lib/daemon/supervisor.js +5 -5
  74. package/dist/lib/lanes/admission-host.d.ts +3 -0
  75. package/dist/lib/lanes/admission-host.js +4 -0
  76. package/dist/lib/lanes/autopilot-registry.d.ts +62 -0
  77. package/dist/lib/lanes/autopilot-registry.js +192 -0
  78. package/dist/lib/lanes/autopilot.d.ts +2 -0
  79. package/dist/lib/lanes/autopilot.js +18 -5
  80. package/dist/lib/lanes/claude-balance.d.ts +6 -0
  81. package/dist/lib/lanes/claude-balance.js +17 -2
  82. package/dist/lib/lanes/keep-alive.d.ts +3 -0
  83. package/dist/lib/lanes/keep-alive.js +3 -0
  84. package/dist/lib/lanes/reconcile.js +14 -6
  85. package/dist/lib/lanes/spawn.js +2 -0
  86. package/dist/lib/mesh/live/daemon/credentials.d.ts +2 -17
  87. package/dist/lib/mesh/live/daemon/credentials.js +9 -9
  88. package/dist/lib/mesh/live/daemon/index.d.ts +5 -5
  89. package/dist/lib/mesh/live/daemon/index.js +2 -2
  90. package/dist/lib/mesh/live/daemon/state.d.ts +1 -1
  91. package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +4 -11
  92. package/dist/lib/mesh/live/daemon/transcript-watch.js +5 -5
  93. package/dist/lib/work-context/company.d.ts +2 -25
  94. package/dist/lib/work-context/company.js +3 -3
  95. package/dist/lib/work-context/config.d.ts +3 -8
  96. package/dist/lib/work-context/config.js +2 -13
  97. package/dist/lib/work-context/state.d.ts +4 -13
  98. package/dist/lib/work-context/state.js +2 -30
  99. package/package.json +1 -1
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import * as fs from "node:fs";
24
24
  import * as path from "node:path";
25
- export const TMUX_SOCKET = "hq-daemon";
25
+ const TMUX_SOCKET = "hq-daemon";
26
26
  export const TMUX_SESSION = "remote-control";
27
27
  export const CODEX_TMUX_SESSION = "codex-remote-control";
28
28
  export function remoteControlArgs(opts) {
@@ -25,9 +25,6 @@ import type { UnitPlan } from "./catalog.js";
25
25
  import type { MachineType } from "./machine-type.js";
26
26
  import type { ControlRequest } from "./requests.js";
27
27
  import { syncPassResult } from "./sync-runner.js";
28
- export declare const BACKOFF_BASE_MS = 1000;
29
- export declare const BACKOFF_MAX_MS = 60000;
30
- export declare const HEALTHY_RUN_MS = 60000;
31
28
  export declare const STOP_ESCALATION_MS = 10000;
32
29
  /** After a handoff, how long a replaced unit must stay up before the next one is replaced. */
33
30
  export declare const HANDOFF_STAGGER_MS = 15000;
@@ -42,8 +39,8 @@ export interface SupervisedChild {
42
39
  on(event: "error", handler: (err: Error) => void): unknown;
43
40
  kill(signal?: NodeJS.Signals | number): boolean;
44
41
  }
45
- export type UnitStatus = "running" | "waiting" | "unavailable" | "backoff" | "exited" | "idle" | "stopped";
46
- export interface UnitState {
42
+ type UnitStatus = "running" | "waiting" | "unavailable" | "backoff" | "exited" | "idle" | "stopped";
43
+ interface UnitState {
47
44
  id: string;
48
45
  service: string;
49
46
  kind: UnitPlan["kind"];
@@ -83,7 +80,7 @@ export interface AdoptedUnit {
83
80
  child: SupervisedChild;
84
81
  }
85
82
  /** A long-running service left running by release(). */
86
- export interface ReleasedUnit {
83
+ interface ReleasedUnit {
87
84
  id: string;
88
85
  pid: number;
89
86
  key: string;
@@ -123,9 +120,6 @@ export interface Supervisor {
123
120
  /** Stop supervising without signalling any child; returns the long-running units left running. */
124
121
  release(): ReleasedUnit[];
125
122
  }
126
- /** Plan reads in a row that must find a running remote-control host blocked before it is paused. */
127
- export declare const BLOCKED_READS_BEFORE_PAUSE = 2;
128
- export declare function backoffMs(attempt: number): number;
129
123
  /**
130
124
  * What a unit's process was started from; a unit whose key changes is
131
125
  * restarted. The CLI version counts only for `hq` commands, so a CLI update
@@ -134,4 +128,5 @@ export declare function backoffMs(attempt: number): number;
134
128
  */
135
129
  export declare function planKey(plan: UnitPlan, cliVersion?: string): string;
136
130
  export declare function createSupervisor(deps: SupervisorDeps): Supervisor;
131
+ export {};
137
132
  //# sourceMappingURL=supervisor.d.ts.map
@@ -23,9 +23,9 @@
23
23
  */
24
24
  import { StringDecoder } from "node:string_decoder";
25
25
  import { SYNC_RUNNER_LEASE_CONFLICT_EXIT, SYNC_RUNNER_LEASE_RETRY_MS, parseSyncRunnerProtocolLine, syncPassResult, } from "./sync-runner.js";
26
- export const BACKOFF_BASE_MS = 1_000;
27
- export const BACKOFF_MAX_MS = 60_000;
28
- export const HEALTHY_RUN_MS = 60_000;
26
+ const BACKOFF_BASE_MS = 1_000;
27
+ const BACKOFF_MAX_MS = 60_000;
28
+ const HEALTHY_RUN_MS = 60_000;
29
29
  export const STOP_ESCALATION_MS = 10_000;
30
30
  const MESH_OWNER_RECHECK_MS = 15_000;
31
31
  const MESH_OWNER_LOG_INTERVAL_MS = 10 * 60_000;
@@ -34,8 +34,8 @@ export const HANDOFF_STAGGER_MS = 15_000;
34
34
  /** How long a draining unit may take to finish its work before it gets SIGTERM. */
35
35
  export const HANDOFF_DRAIN_MAX_MS = 15 * 60_000;
36
36
  /** Plan reads in a row that must find a running remote-control host blocked before it is paused. */
37
- export const BLOCKED_READS_BEFORE_PAUSE = 2;
38
- export function backoffMs(attempt) {
37
+ const BLOCKED_READS_BEFORE_PAUSE = 2;
38
+ function backoffMs(attempt) {
39
39
  return Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** Math.max(0, attempt));
40
40
  }
41
41
  /**
@@ -1,4 +1,5 @@
1
1
  import type { AdmissionHost } from "./admission.js";
2
+ import type { LaneHost } from "./types.js";
2
3
  export declare const OUTPOST_ID_PATH = "/etc/outpost/outpost-id";
3
4
  export declare const OUTPOST_DIR = "/etc/outpost";
4
5
  export type OutpostIdentity = {
@@ -16,4 +17,6 @@ export declare function resolveAdmissionHost(deps?: {
16
17
  existsSync?: (p: string) => boolean;
17
18
  readFileSync?: (p: string) => string;
18
19
  }): AdmissionHost;
20
+ /** `primary` names the controller registry unless the caller is a different Outpost. */
21
+ export declare function isControllerAliasOutpostId(outpostId: string, localHost: LaneHost): boolean;
19
22
  //# sourceMappingURL=admission-host.d.ts.map
@@ -30,4 +30,8 @@ export function resolveAdmissionHost(deps = {}) {
30
30
  outpostId: identity.kind === "id" ? identity.id : null,
31
31
  };
32
32
  }
33
+ /** `primary` names the controller registry unless the caller is a different Outpost. */
34
+ export function isControllerAliasOutpostId(outpostId, localHost) {
35
+ return outpostId === "primary" && !(localHost.kind === "outpost" && localHost.id !== "primary");
36
+ }
33
37
  //# sourceMappingURL=admission-host.js.map
@@ -0,0 +1,62 @@
1
+ import type { LaneCompanyRef, LaneHost } from "./types.js";
2
+ export declare const OUTPOST_LEAD_REGISTRY_SCHEMA = "hq-lanes-outpost-lead-registry.v1";
3
+ export declare const OUTPOST_LEAD_REGISTRY_MAX_AGE_MS: number;
4
+ export declare const OUTPOST_LEAD_REGISTRY_REFRESH_MS: number;
5
+ export declare const LEAD_REGISTRY_REL: string;
6
+ export declare const OUTPOST_LEAD_REGISTRY_STATE_REL: string;
7
+ export declare const LEAD_AUTOPILOT_FLAG = "lanes.lead-autopilot";
8
+ export interface LeadRegistryRow {
9
+ team: string;
10
+ lane: string;
11
+ host: string;
12
+ status: string;
13
+ prd: string;
14
+ }
15
+ export interface OutpostLeadRegistry {
16
+ schema_version: typeof OUTPOST_LEAD_REGISTRY_SCHEMA;
17
+ outpost_id: string;
18
+ generated_at: string;
19
+ juniors: LeadRegistryRow[];
20
+ }
21
+ export declare function buildOutpostLeadRegistry(input: {
22
+ rows: readonly LeadRegistryRow[];
23
+ outpostId: string;
24
+ generatedAt: string;
25
+ }): OutpostLeadRegistry;
26
+ /** Stage a host-filtered registry on the Outpost using an atomic local rename. */
27
+ export declare function buildOutpostLeadRegistryInstallCommand(registry: OutpostLeadRegistry): string;
28
+ export declare function syncOutpostLeadRegistries(input: {
29
+ hqRoot: string;
30
+ outpostIds: readonly string[];
31
+ generatedAt: string;
32
+ localHost: LaneHost;
33
+ resolveCompany: (slug: string) => Promise<LaneCompanyRef>;
34
+ readFlag: (flag: string, options: {
35
+ company: LaneCompanyRef;
36
+ }) => Promise<boolean>;
37
+ dispatch: (request: {
38
+ outpostId: string;
39
+ command: string;
40
+ timeoutSeconds?: number;
41
+ waitMs?: number;
42
+ }) => Promise<{
43
+ done?: boolean;
44
+ exitCode: number | null;
45
+ stdout: string;
46
+ stderr: string;
47
+ }>;
48
+ }): Promise<Array<{
49
+ outpost_id: string;
50
+ row_count: number;
51
+ skipped: Array<{
52
+ lane: string;
53
+ reason: string;
54
+ }>;
55
+ dispatched: boolean;
56
+ }>>;
57
+ export declare function validateOutpostLeadRegistry(input: {
58
+ registry: unknown;
59
+ outpostId: string;
60
+ now: Date;
61
+ }): OutpostLeadRegistry;
62
+ //# sourceMappingURL=autopilot-registry.d.ts.map
@@ -0,0 +1,192 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { joinCommandParts, withRemoteHqDir } from "@indigoai-us/hq-cloud/outposts";
5
+ import { isControllerAliasOutpostId } from "./admission-host.js";
6
+ import { LanesError } from "./errors.js";
7
+ export const OUTPOST_LEAD_REGISTRY_SCHEMA = "hq-lanes-outpost-lead-registry.v1";
8
+ export const OUTPOST_LEAD_REGISTRY_MAX_AGE_MS = 24 * 60 * 60 * 1000;
9
+ export const OUTPOST_LEAD_REGISTRY_REFRESH_MS = 6 * 60 * 60 * 1000;
10
+ export const LEAD_REGISTRY_REL = path.join("workspace", "lane-briefs", "juniors.json");
11
+ export const OUTPOST_LEAD_REGISTRY_STATE_REL = path.join("workspace", "lane-briefs", "autopilot-outpost-registry-state.json");
12
+ export const LEAD_AUTOPILOT_FLAG = "lanes.lead-autopilot";
13
+ function isRecord(value) {
14
+ return value !== null && typeof value === "object" && !Array.isArray(value);
15
+ }
16
+ function parseSourceRegistry(file) {
17
+ let parsed;
18
+ try {
19
+ const stat = fs.lstatSync(file);
20
+ if (!stat.isFile() || stat.isSymbolicLink())
21
+ throw new Error("expected a regular file");
22
+ parsed = JSON.parse(fs.readFileSync(file, "utf8"));
23
+ }
24
+ catch (error) {
25
+ throw new LanesError("lead_autopilot_registry_unreadable", `Cannot read primary lead registry ${file}: ${error instanceof Error ? error.message : String(error)}`, { path: file });
26
+ }
27
+ // A retired-only registry has no leads, matching the autopilot reader.
28
+ if (isRecord(parsed) && parsed.juniors === undefined && Array.isArray(parsed.retired))
29
+ return [];
30
+ if (!isRecord(parsed) || !Array.isArray(parsed.juniors)) {
31
+ throw new LanesError("lead_autopilot_registry_invalid", `Invalid primary lead registry ${file}: juniors must be an array`, { path: file });
32
+ }
33
+ return parsed.juniors.map((row, index) => {
34
+ if (!isRecord(row) ||
35
+ ["team", "lane", "host", "status", "prd"].some((key) => typeof row[key] !== "string" || !row[key].trim())) {
36
+ throw new LanesError("lead_autopilot_registry_invalid", `Invalid primary lead registry ${file}: juniors[${index}] must name team, lane, host, status, and prd`, { path: file, index });
37
+ }
38
+ return row;
39
+ });
40
+ }
41
+ export function buildOutpostLeadRegistry(input) {
42
+ if (!input.outpostId.trim() || !Number.isFinite(Date.parse(input.generatedAt))) {
43
+ throw new LanesError("lead_autopilot_registry_invalid", "Outpost registry target and generation time must be valid", { outpost_id: input.outpostId });
44
+ }
45
+ return {
46
+ schema_version: OUTPOST_LEAD_REGISTRY_SCHEMA,
47
+ outpost_id: input.outpostId,
48
+ generated_at: new Date(input.generatedAt).toISOString(),
49
+ juniors: input.rows.filter((row) => row.host === input.outpostId),
50
+ };
51
+ }
52
+ /** Stage a host-filtered registry on the Outpost using an atomic local rename. */
53
+ export function buildOutpostLeadRegistryInstallCommand(registry) {
54
+ const encoded = Buffer.from(`${JSON.stringify(registry)}\n`, "utf8").toString("base64");
55
+ const script = [
56
+ 'const fs=require("node:fs"),path=require("node:path");',
57
+ 'const root=process.cwd(),dir=path.join(root,"workspace","lane-briefs"),file=path.join(dir,"juniors.json");',
58
+ "fs.mkdirSync(dir,{recursive:true});const realRoot=fs.realpathSync(root),realDir=fs.realpathSync(dir);",
59
+ 'if(realDir!==realRoot&&!realDir.startsWith(realRoot+path.sep))throw Error("registry directory escapes HQ root");',
60
+ 'try{const s=fs.lstatSync(file);if(!s.isFile()||s.isSymbolicLink())throw Error("registry destination must be a regular file")}catch(e){if(e.code!=="ENOENT")throw e}',
61
+ `const bytes=Buffer.from("${encoded}","base64"),tmp=file+"."+process.pid+".tmp";`,
62
+ 'fs.writeFileSync(tmp,bytes,{mode:0o600,flag:"wx"});try{fs.renameSync(tmp,file)}catch(e){try{fs.unlinkSync(tmp)}catch{}throw e}',
63
+ ].join("");
64
+ return withRemoteHqDir(joinCommandParts(["node", "-e", script]));
65
+ }
66
+ function companySlug(row) {
67
+ const match = /^companies\/([a-z0-9][a-z0-9._-]*)\//i.exec(row.prd);
68
+ return match?.[1] ?? null;
69
+ }
70
+ function readDeliveryState(file) {
71
+ try {
72
+ const stat = fs.lstatSync(file);
73
+ if (!stat.isFile() || stat.isSymbolicLink())
74
+ throw new Error("expected regular state file");
75
+ const value = JSON.parse(fs.readFileSync(file, "utf8"));
76
+ if (!isRecord(value) ||
77
+ value.schema_version !== "hq-lanes-outpost-registry-delivery-state.v1" ||
78
+ !isRecord(value.delivered)) {
79
+ throw new Error("invalid delivery state shape");
80
+ }
81
+ return value;
82
+ }
83
+ catch (error) {
84
+ if (error.code === "ENOENT") {
85
+ return { schema_version: "hq-lanes-outpost-registry-delivery-state.v1", delivered: {} };
86
+ }
87
+ throw new LanesError("lead_autopilot_registry_state_invalid", `Cannot read registry delivery state: ${error instanceof Error ? error.message : String(error)}`, { path: file });
88
+ }
89
+ }
90
+ function writeDeliveryState(file, state) {
91
+ fs.mkdirSync(path.dirname(file), { recursive: true });
92
+ const temp = `${file}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
93
+ try {
94
+ fs.writeFileSync(temp, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600, flag: "wx" });
95
+ fs.renameSync(temp, file);
96
+ }
97
+ catch (error) {
98
+ try {
99
+ fs.unlinkSync(temp);
100
+ }
101
+ catch (cleanupError) {
102
+ if (cleanupError.code !== "ENOENT")
103
+ throw cleanupError;
104
+ }
105
+ throw error;
106
+ }
107
+ }
108
+ export async function syncOutpostLeadRegistries(input) {
109
+ const sourceFile = path.join(input.hqRoot, LEAD_REGISTRY_REL);
110
+ // Most HQ installs have no lead registry. Nothing to deliver is not a reconcile failure.
111
+ if (!fs.existsSync(sourceFile))
112
+ return [];
113
+ const sourceRows = parseSourceRegistry(sourceFile);
114
+ const eligible = [];
115
+ const skipped = [];
116
+ const enabledByCompany = new Map();
117
+ for (const row of sourceRows) {
118
+ const slug = companySlug(row);
119
+ if (!slug) {
120
+ skipped.push({ lane: row.lane, reason: "company_unresolved" });
121
+ continue;
122
+ }
123
+ let enabled = enabledByCompany.get(slug);
124
+ if (enabled === undefined) {
125
+ const company = await input.resolveCompany(slug);
126
+ enabled = await input.readFlag(LEAD_AUTOPILOT_FLAG, { company });
127
+ enabledByCompany.set(slug, enabled);
128
+ }
129
+ if (!enabled) {
130
+ skipped.push({ lane: row.lane, reason: "flag_off" });
131
+ continue;
132
+ }
133
+ eligible.push(row);
134
+ }
135
+ const stateFile = path.join(input.hqRoot, OUTPOST_LEAD_REGISTRY_STATE_REL);
136
+ const state = readDeliveryState(stateFile);
137
+ const results = [];
138
+ const targets = [...new Set(input.outpostIds.map((id) => id.trim()))]
139
+ .filter((id) => id && !isControllerAliasOutpostId(id, input.localHost))
140
+ .sort();
141
+ for (const outpostId of targets) {
142
+ const registry = buildOutpostLeadRegistry({ rows: eligible, outpostId, generatedAt: input.generatedAt });
143
+ // Hash only the rows. A new generated_at alone does not trigger a remote write.
144
+ const hash = crypto.createHash("sha256").update(JSON.stringify(registry.juniors)).digest("hex");
145
+ const prior = state.delivered[outpostId];
146
+ const age = prior ? Date.parse(input.generatedAt) - Date.parse(prior.at) : Infinity;
147
+ const shouldDispatch = !prior ||
148
+ prior.hash !== hash ||
149
+ !Number.isFinite(age) ||
150
+ age < 0 ||
151
+ age >= OUTPOST_LEAD_REGISTRY_REFRESH_MS;
152
+ if (shouldDispatch) {
153
+ const result = await input.dispatch({
154
+ outpostId,
155
+ command: buildOutpostLeadRegistryInstallCommand(registry),
156
+ timeoutSeconds: 120,
157
+ waitMs: 120_000,
158
+ });
159
+ if (result.done !== true || result.exitCode !== 0) {
160
+ throw new LanesError("lead_autopilot_registry_sync_failed", `Could not sync lead registry to Outpost ${outpostId}`, { outpost_id: outpostId, exit_code: result.exitCode, done: result.done === true });
161
+ }
162
+ state.delivered[outpostId] = { hash, at: new Date(input.generatedAt).toISOString() };
163
+ writeDeliveryState(stateFile, state);
164
+ }
165
+ results.push({
166
+ outpost_id: outpostId,
167
+ row_count: registry.juniors.length,
168
+ skipped: skipped.filter(({ lane }) => sourceRows.some((row) => row.lane === lane && row.host === outpostId)),
169
+ dispatched: shouldDispatch,
170
+ });
171
+ }
172
+ return results;
173
+ }
174
+ export function validateOutpostLeadRegistry(input) {
175
+ const value = input.registry;
176
+ if (!isRecord(value) ||
177
+ value.schema_version !== OUTPOST_LEAD_REGISTRY_SCHEMA ||
178
+ value.outpost_id !== input.outpostId ||
179
+ typeof value.generated_at !== "string" ||
180
+ !Array.isArray(value.juniors)) {
181
+ throw new LanesError("lead_autopilot_registry_invalid", `Outpost ${input.outpostId} has an invalid lead registry`, { outpost_id: input.outpostId });
182
+ }
183
+ const age = input.now.getTime() - Date.parse(value.generated_at);
184
+ if (!Number.isFinite(age) || age < 0 || age > OUTPOST_LEAD_REGISTRY_MAX_AGE_MS) {
185
+ throw new LanesError("lead_autopilot_registry_stale", `Outpost ${input.outpostId} lead registry is stale`, { outpost_id: input.outpostId, generated_at: value.generated_at });
186
+ }
187
+ if (value.juniors.some((row) => !isRecord(row) || row.host !== input.outpostId)) {
188
+ throw new LanesError("lead_autopilot_registry_invalid", `Outpost ${input.outpostId} registry contains rows for another host`, { outpost_id: input.outpostId });
189
+ }
190
+ return value;
191
+ }
192
+ //# sourceMappingURL=autopilot-registry.js.map
@@ -43,6 +43,8 @@ export interface AutopilotPassResult {
43
43
  lead_lane: string;
44
44
  reason: string;
45
45
  }>;
46
+ /** Set on an Outpost that has not received a lead registry from the primary yet. */
47
+ registry?: "missing";
46
48
  }
47
49
  interface ProjectStory {
48
50
  id: string;
@@ -1,6 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { laneRunsOnHost } from "./admission.js";
4
+ import { isControllerAliasOutpostId } from "./admission-host.js";
5
+ import { LEAD_REGISTRY_REL, validateOutpostLeadRegistry } from "./autopilot-registry.js";
4
6
  import { appendDropboxMessage } from "./dropbox.js";
5
7
  import { LanesError } from "./errors.js";
6
8
  import { resolveLaneCompany } from "./lane-company.js";
@@ -37,21 +39,28 @@ function readJsonFile(filePath, code) {
37
39
  throw new LanesError(code, `Cannot parse ${filePath}: ${error instanceof Error ? error.message : String(error)}`, { path: filePath });
38
40
  }
39
41
  }
40
- function parseRegistry(hqRoot) {
41
- const file = path.join(hqRoot, "workspace", "lane-briefs", "juniors.json");
42
+ function parseRegistry(hqRoot, outpostId, now) {
43
+ const file = path.join(hqRoot, LEAD_REGISTRY_REL);
42
44
  let stat;
43
45
  try {
44
46
  stat = fs.lstatSync(file);
45
47
  }
46
48
  catch (error) {
47
- if (error.code === "ENOENT")
49
+ if (error.code === "ENOENT") {
50
+ // An Outpost without a delivered registry reports it; it does not fail reconcile.
51
+ // The primary delivers nothing when it has no registry or every company is flagged off.
52
+ if (outpostId !== null && !isControllerAliasOutpostId(outpostId, { kind: "outpost", id: outpostId }))
53
+ return null;
48
54
  return [];
55
+ }
49
56
  throw new LanesError("lead_autopilot_registry_invalid", `Cannot inspect ${file}: ${error instanceof Error ? error.message : String(error)}`, { path: file });
50
57
  }
51
58
  if (!stat.isFile() || stat.isSymbolicLink()) {
52
59
  throw new LanesError("lead_autopilot_registry_invalid", `Invalid lead registry ${file}: expected a regular file`, { path: file });
53
60
  }
54
61
  const raw = readJsonFile(file, "lead_autopilot_registry_invalid");
62
+ if (outpostId !== null && !isControllerAliasOutpostId(outpostId, { kind: "outpost", id: outpostId }))
63
+ return validateOutpostLeadRegistry({ registry: raw, outpostId, now }).juniors;
55
64
  if (isRecord(raw) && raw.juniors === undefined && Array.isArray(raw.retired))
56
65
  return [];
57
66
  if (!isRecord(raw) || !Array.isArray(raw.juniors)) {
@@ -407,8 +416,12 @@ function recordOperatorEscalation(hqRoot, record) {
407
416
  }
408
417
  async function runLeadAutopilotPassUnlocked(input) {
409
418
  const { hqRoot, host, now } = input;
419
+ const effectiveOutpostId = input.outpostId !== null && isControllerAliasOutpostId(input.outpostId, { kind: "outpost", id: input.outpostId }) ? null : input.outpostId;
410
420
  const result = { ok: true, host, evaluated: 0, sent: [], started: [], escalated: [], skipped: [] };
411
- const leads = parseRegistry(hqRoot);
421
+ const registered = parseRegistry(hqRoot, input.outpostId, now);
422
+ if (registered === null)
423
+ result.registry = "missing";
424
+ const leads = registered ?? [];
412
425
  const lanes = readAllLanes(hqRoot);
413
426
  const byId = new Map(lanes.map((lane) => [lane.lane_id, lane]));
414
427
  const stateFile = path.join(hqRoot, "workspace", "lane-briefs", "autopilot-state.json");
@@ -429,7 +442,7 @@ async function runLeadAutopilotPassUnlocked(input) {
429
442
  ? lead.host.kind === "local"
430
443
  : lead.host.kind === "outpost" && lead.host.id === registered.host && input.outpostId === registered.host;
431
444
  if (!hostMatchesRegistry ||
432
- !laneRunsOnHost(lead, { hostname: input.hostname, outpostId: input.outpostId })) {
445
+ !laneRunsOnHost(lead, { hostname: input.hostname, outpostId: effectiveOutpostId })) {
433
446
  result.skipped.push({ lead_lane: lead.lane_id, reason: "lead_host_mismatch" });
434
447
  continue;
435
448
  }
@@ -1,6 +1,7 @@
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
+ import { type QuotaState } from "./quota.js";
4
5
  export declare const BALANCE_LIVE_STATES: readonly ["running", "queued", "awaiting_input"];
5
6
  export declare const BALANCE_MOVABLE_STATES: readonly ["interrupted", "done", "awaiting_input"];
6
7
  export declare const CLAUDE_RECENT_USAGE_WINDOW_MS: number;
@@ -37,6 +38,9 @@ export interface ClaudeAccountLoad {
37
38
  target_logged_in: boolean | null;
38
39
  eligible: boolean;
39
40
  ineligible_reason?: string;
41
+ quota_reset_at?: string;
42
+ quota_marker?: string;
43
+ quota_model?: string;
40
44
  live_lanes: number;
41
45
  counts_complete: boolean;
42
46
  usage: NonNullable<HostCapacityAccount["usage"]> | null;
@@ -74,6 +78,7 @@ export declare function selectClaudeAccount(input: {
74
78
  model?: string;
75
79
  /** Keep the recorded account eligible for its already-running CLI model. */
76
80
  recordedModelAccount?: string;
81
+ quotaState?: QuotaState;
77
82
  /** Recent completed-run tiebreak; callers pass the default-off hq-flags result. */
78
83
  recentUsageEnabled?: boolean;
79
84
  now?: Date;
@@ -83,6 +88,7 @@ export declare function selectClaudeAccount(input: {
83
88
  export declare function accountLoadsForReport(input: {
84
89
  hosts: readonly BalanceHostSnapshot[];
85
90
  config: LanesConfig;
91
+ quotaState?: QuotaState;
86
92
  }): ClaudeAccountLoad[];
87
93
  export declare function planClaudeBalanceMoves(input: {
88
94
  hosts: readonly BalanceHostSnapshot[];
@@ -1,4 +1,5 @@
1
1
  import { LanesError } from "./errors.js";
2
+ import { quotaBlockFor } from "./quota.js";
2
3
  export const BALANCE_LIVE_STATES = ["running", "queued", "awaiting_input"];
3
4
  export const BALANCE_MOVABLE_STATES = ["interrupted", "done", "awaiting_input"];
4
5
  export const CLAUDE_RECENT_USAGE_WINDOW_MS = 5 * 60 * 60 * 1000;
@@ -126,10 +127,16 @@ function buildLoads(input) {
126
127
  const hosts = input.hosts.map((host) => accountHostLoad(host, account));
127
128
  const targetLoad = hosts.find((host) => sameHost(host.host, targetHost));
128
129
  const configuredModel = configAccounts[account]?.model;
130
+ const quotaModel = input.model ?? configuredModel ?? input.config.providers.claude.model;
131
+ const quotaBlock = input.quotaState === undefined
132
+ ? undefined
133
+ : quotaBlockFor(input.quotaState, "claude", account, quotaModel, input.now);
129
134
  const ineligible = targetIneligibilityReason(account, configAccounts[account]?.enabled === true, input.allowedAccounts, targetLoad) ?? (account !== input.recordedModelAccount &&
130
135
  input.model !== undefined && configuredModel !== undefined && configuredModel !== input.model
131
136
  ? "model_mismatch"
132
- : undefined);
137
+ : quotaBlock?.blocked === true
138
+ ? "quota_held"
139
+ : undefined);
133
140
  if (ineligible !== undefined)
134
141
  skipped.push({ account, host: targetHost, reason: ineligible });
135
142
  const knownCounts = hosts.flatMap((host) => host.live_lanes === null ? [] : [host.live_lanes]);
@@ -139,6 +146,11 @@ function buildLoads(input) {
139
146
  target_logged_in: targetLoad.logged_in,
140
147
  eligible: ineligible === undefined,
141
148
  ...(ineligible !== undefined ? { ineligible_reason: ineligible } : {}),
149
+ ...(quotaBlock?.blocked ? {
150
+ ...(quotaBlock.quota_reset_at !== undefined ? { quota_reset_at: quotaBlock.quota_reset_at } : {}),
151
+ ...(quotaBlock.marker !== undefined ? { quota_marker: quotaBlock.marker } : {}),
152
+ ...(quotaBlock.model !== undefined ? { quota_model: quotaBlock.model } : {}),
153
+ } : {}),
142
154
  live_lanes: knownCounts.reduce((sum, count) => sum + count, 0),
143
155
  counts_complete: hosts.every((host) => host.live_lanes !== null && host.count_source !== "capacity_partial"),
144
156
  usage: input.target.capacity.providers.claude.accounts[account]?.usage ?? null,
@@ -193,6 +205,9 @@ export function selectClaudeAccount(input) {
193
205
  account_reasons: loads.map((load) => ({
194
206
  account: load.account,
195
207
  reason: load.ineligible_reason ?? "unavailable",
208
+ ...(load.quota_reset_at !== undefined ? { quota_reset_at: load.quota_reset_at } : {}),
209
+ ...(load.quota_marker !== undefined ? { marker: load.quota_marker } : {}),
210
+ ...(load.quota_model !== undefined ? { model: load.quota_model } : {}),
196
211
  })),
197
212
  unavailable_hosts: input.unavailableHosts ?? [],
198
213
  });
@@ -234,7 +249,7 @@ export function accountLoadsForReport(input) {
234
249
  const first = input.hosts[0];
235
250
  if (!first)
236
251
  return [];
237
- return buildLoads({ hosts: input.hosts, target: first, config: input.config, now: new Date() }).loads.map((load) => ({
252
+ return buildLoads({ hosts: input.hosts, target: first, config: input.config, quotaState: input.quotaState, now: new Date() }).loads.map((load) => ({
238
253
  ...load,
239
254
  eligible: false,
240
255
  ineligible_reason: undefined,
@@ -6,6 +6,9 @@ export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_DEFAULT_ENABLED = false;
6
6
  export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS: number;
7
7
  export declare const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG = "lanes.keep-alive-resume-blocked";
8
8
  export declare const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_DEFAULT_ENABLED = false;
9
+ export declare const LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG = "lanes.keep-alive-immediate-first-resume";
10
+ export declare const LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_DEFAULT_ENABLED = false;
11
+ export declare const LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS = 30000;
9
12
  export declare const LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS: number;
10
13
  /** Missing or unreadable registry values retain the legacy supersession behavior. */
11
14
  export declare const LANE_SUPERSESSION_LINEAGE_AWARE_FLAG = "lanes.supersession-lineage-aware";
@@ -11,6 +11,9 @@ export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_DEFAULT_ENABLED = false;
11
11
  export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS = 24 * 60 * 60 * 1000;
12
12
  export const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG = "lanes.keep-alive-resume-blocked";
13
13
  export const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_DEFAULT_ENABLED = false;
14
+ export const LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG = "lanes.keep-alive-immediate-first-resume";
15
+ export const LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_DEFAULT_ENABLED = false;
16
+ export const LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS = 30_000;
14
17
  export const LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS = 15 * 60 * 1000;
15
18
  /** Missing or unreadable registry values retain the legacy supersession behavior. */
16
19
  export const LANE_SUPERSESSION_LINEAGE_AWARE_FLAG = "lanes.supersession-lineage-aware";
@@ -25,7 +25,7 @@ import { readLanesRegistryFlag } from "./registry-flag.js";
25
25
  import { resolveLaneCompany } from "./lane-company.js";
26
26
  import { appendDropboxMessage } from "./dropbox.js";
27
27
  import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolGuard, RUNAWAY_TOOL_GUARD_FLAG, } from "./runaway-tool-guard.js";
28
- import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, queueLaneTerminalResume, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
28
+ import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, queueLaneTerminalResume, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
29
29
  import { providerSupportsSessionResume } from "./delivery.js";
30
30
  import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, readQuotaSignal, recordAccountQuota, } from "./quota.js";
31
31
  import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
@@ -1119,6 +1119,7 @@ export async function runPassBody(hqRoot, deps) {
1119
1119
  const keepAliveFlagByCompany = new Map();
1120
1120
  const nextCheckAtFlagByCompany = new Map();
1121
1121
  const blockedResumeFlagByCompany = new Map();
1122
+ const immediateFirstResumeFlagByCompany = new Map();
1122
1123
  const supersessionLineageAwareByCompany = new Map();
1123
1124
  await Promise.all([...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
1124
1125
  const options = {
@@ -1129,7 +1130,7 @@ export async function runPassBody(hqRoot, deps) {
1129
1130
  lane.keep_alive === true &&
1130
1131
  laneRunsHere(lane, startDeps.host) &&
1131
1132
  keepAliveCompanyKey(resolveLaneCompany(hqRoot, lane).company) === key);
1132
- const [keepAliveEnabled, nextCheckAtEnabled, lineageAware, blockedResumeEnabled] = await Promise.all([
1133
+ const [keepAliveEnabled, nextCheckAtEnabled, lineageAware, blockedResumeEnabled, immediateFirstResumeEnabled] = await Promise.all([
1133
1134
  readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, options),
1134
1135
  nextCheckAtFlagCompanyKeys.has(key)
1135
1136
  ? readRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, options)
@@ -1138,10 +1139,12 @@ export async function runPassBody(hqRoot, deps) {
1138
1139
  hasBlockedKeepAliveLane
1139
1140
  ? readRegistryFlag(LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, options)
1140
1141
  : Promise.resolve(false),
1142
+ readRegistryFlag(LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, options),
1141
1143
  ]);
1142
1144
  keepAliveFlagByCompany.set(key, keepAliveEnabled);
1143
1145
  nextCheckAtFlagByCompany.set(key, nextCheckAtEnabled);
1144
1146
  blockedResumeFlagByCompany.set(key, blockedResumeEnabled);
1147
+ immediateFirstResumeFlagByCompany.set(key, immediateFirstResumeEnabled);
1145
1148
  supersessionLineageAwareByCompany.set(key, lineageAware);
1146
1149
  }));
1147
1150
  const admissionThrottleDeps = startDeps.throttleDeps ?? defaultThrottleDeps(startDeps.env ?? process.env);
@@ -1162,7 +1165,7 @@ export async function runPassBody(hqRoot, deps) {
1162
1165
  const freshLanes = loadLaneRecords(hqRoot, summary);
1163
1166
  const freshQuestions = loadQuestionRecords(hqRoot, summary);
1164
1167
  await closeInterruptedDoneStoryWorkers(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, storyCloseEnabledByCompany);
1165
- await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany);
1168
+ await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany, immediateFirstResumeFlagByCompany);
1166
1169
  const drainLanes = loadLaneRecords(hqRoot, summary);
1167
1170
  await admitQueued(hqRoot, drainLanes, summary, admissionDeps, now);
1168
1171
  await resumeAwaiting(hqRoot, drainLanes, freshQuestions, summary, admissionDeps);
@@ -2241,7 +2244,7 @@ function appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, at, reason, dep
2241
2244
  }),
2242
2245
  }), false);
2243
2246
  }
2244
- async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany) {
2247
+ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany, immediateFirstResumeFlagByCompany) {
2245
2248
  const nowMs = now.getTime();
2246
2249
  const nowIso = now.toISOString();
2247
2250
  const config = loadLanesConfig(hqRoot);
@@ -2634,7 +2637,12 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
2634
2637
  const terminalAtMs = initialState === "done"
2635
2638
  ? Math.min(Date.parse(current.updated_at), currentEligibilityAtMs)
2636
2639
  : currentEligibilityAtMs;
2637
- const attemptEligibleMs = terminalAtMs + leadLaneKeepAliveBackoffMs(attempt);
2640
+ const immediateFirstResumeEnabled = initialState === "done" &&
2641
+ immediateFirstResumeFlagByCompany.get(keepAliveCompanyKey(companyForLane(current))) === true;
2642
+ const attemptDelayMs = immediateFirstResumeEnabled && attempt === 1
2643
+ ? LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS
2644
+ : leadLaneKeepAliveBackoffMs(immediateFirstResumeEnabled ? Math.max(1, attempt - 1) : attempt);
2645
+ const attemptEligibleMs = terminalAtMs + attemptDelayMs;
2638
2646
  const storedNextMs = resetByHuman
2639
2647
  ? Number.NaN
2640
2648
  : Date.parse(current.keep_alive_next_eligible_at ?? "");
@@ -2653,7 +2661,7 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
2653
2661
  recordKeepAliveSkipEvent(hqRoot, skipLane, "backoff", skipLane.version, summary, deps, new Date(nextEligibleMs).toISOString());
2654
2662
  continue;
2655
2663
  }
2656
- const nextEligibleAt = new Date(nowMs + leadLaneKeepAliveBackoffMs(attempt + 1)).toISOString();
2664
+ const nextEligibleAt = new Date(nowMs + leadLaneKeepAliveBackoffMs(immediateFirstResumeEnabled ? attempt : attempt + 1)).toISOString();
2657
2665
  const queuedCount = loadLaneRecords(hqRoot, summary).filter((queuedLane) => queuedLane.state === "queued" &&
2658
2666
  queuedLane.provider === current.provider &&
2659
2667
  laneRunsHere(queuedLane, deps.host) &&
@@ -16,6 +16,7 @@ import { PROVIDER_HOME_ENV, accountStatus, isLoggedOut, resolveAccount, selectBa
16
16
  import { effectiveAccountHome } from "./account-home.js";
17
17
  import { buildHostCapacitySnapshot } from "./host-capacity.js";
18
18
  import { selectClaudeAccount } from "./claude-balance.js";
19
+ import { loadQuotaState } from "./quota.js";
19
20
  import { COMPACTION_PROMPT, compactionAfterUsage, compactionDue, compactLaneSession, } from "./compaction.js";
20
21
  import { installDrainHook } from "./hooks-install.js";
21
22
  import { loadLanesConfig, providerAutoCompactTokens, providerCompactAfterMs, } from "./config.js";
@@ -269,6 +270,7 @@ function selectSpawnAccount(input) {
269
270
  hosts: [{ capacity, lanes, laneListAvailable: true }],
270
271
  targetHost: lane.host,
271
272
  config,
273
+ quotaState: loadQuotaState(hqRoot),
272
274
  ...((recordedModel ?? pinnedModel ?? profileModel) !== undefined
273
275
  ? { model: recordedModel ?? pinnedModel ?? profileModel }
274
276
  : {}),