@indigoai-us/hq-cli 5.345.129 → 5.345.130

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.
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ export declare const WORKFLOW_RUNNER_ASSET = "core/scripts/workflow-runner.mjs";
3
+ export declare function registerCoreWorkflowRunCommand(core: Command): void;
4
+ //# sourceMappingURL=core-workflow-run-command.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { resolveLiveRoot } from "../utils/hq-roots.js";
2
+ import { runBundledScript } from "../utils/run-bundled-script.js";
3
+ import { describeCoreCommand } from "./core-command-catalog.js";
4
+ import { coreNativePipelineEnabled } from "./core-native-pipeline-flag.js";
5
+ export const WORKFLOW_RUNNER_ASSET = "core/scripts/workflow-runner.mjs";
6
+ export function registerCoreWorkflowRunCommand(core) {
7
+ const workflow = core.command("workflow").description("Workflow orchestration");
8
+ const command = workflow.command("run")
9
+ .description("Run a workflow script or pipeline lane")
10
+ .helpOption(false)
11
+ .allowUnknownOption()
12
+ .allowExcessArguments(true)
13
+ .argument("[args...]")
14
+ .action(async (args, _options, current) => {
15
+ const argv = current.args.length > 0 ? current.args : args;
16
+ const root = resolveLiveRoot({ hqRoot: core.opts().hqRoot });
17
+ if (!await coreNativePipelineEnabled()) {
18
+ const result = runBundledScript({ asset: WORKFLOW_RUNNER_ASSET, args: argv, cwd: process.cwd(), hqRoot: root });
19
+ if (result.code !== 0)
20
+ process.exit(result.code);
21
+ return;
22
+ }
23
+ const previousRoot = process.env.HQ_ROOT;
24
+ process.env.HQ_ROOT = root;
25
+ try {
26
+ const native = await import("./core-pipeline-workflow-runner-native.js");
27
+ const runner = native;
28
+ const code = await runner.runWorkflowRunnerNative(argv, root);
29
+ if (code !== 0)
30
+ process.exit(code);
31
+ }
32
+ finally {
33
+ if (previousRoot === undefined)
34
+ delete process.env.HQ_ROOT;
35
+ else
36
+ process.env.HQ_ROOT = previousRoot;
37
+ }
38
+ });
39
+ describeCoreCommand(command, { execution: "native", asset: WORKFLOW_RUNNER_ASSET, root: "live" });
40
+ }
41
+ //# sourceMappingURL=core-workflow-run-command.js.map
@@ -52,6 +52,7 @@ import { registerCoreHookStateHelpers } from "./core-hook-state-helpers-native.j
52
52
  import { registerCoreHookBatchCommand } from "./core-hook-batch-command.js";
53
53
  import { registerCorePipelineWorkerTableCommand } from "./core-pipeline-workers-command.js";
54
54
  import { registerCorePipelineRuntimeCommands } from "./core-pipeline-conductor-command.js";
55
+ import { registerCoreWorkflowRunCommand } from "./core-workflow-run-command.js";
55
56
  import { resolveLiveRoot } from "../utils/hq-roots.js";
56
57
  import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
57
58
  import { CompanyIndexUserError, renderIndexTarget } from "../lib/index-render/index.js";
@@ -304,6 +305,8 @@ export const SCAFFOLD_ONLY_ASSETS = [
304
305
  { asset: "core/scripts/session-title.sh", root: "live", summary: "Rollback companion for native session title" },
305
306
  { asset: "core/scripts/session-title-config.sh", root: "live", summary: "Rollback companion for native session-title settings" },
306
307
  { asset: "core/scripts/pipeline-worker-table.sh", root: "live", summary: "Rollback companion for native pipeline worker-table" },
308
+ { asset: "core/scripts/workflow-runner.mjs", root: "live", summary: "Rollback companion for native workflow runner" },
309
+ { asset: "core/scripts/lib/codex-output-schema.mjs", root: "live", summary: "Schema helper required by the bundled workflow-runner rollback companion" },
307
310
  { asset: "core/scripts/share-suggestion-state.sh", root: "live", summary: "Rollback shell for native share-suggestion-state" },
308
311
  { asset: "core/scripts/repo-run-registry.sh", root: "live", summary: "Rollback shell for native repo-run-registry" },
309
312
  { asset: "core/scripts/detect-stale-review-base.sh", root: "live", summary: "Rollback shell for native detect-stale-review-base" },
@@ -456,6 +459,7 @@ export function registerCoreCommands(program) {
456
459
  registerCoreHookStateHelpers(core);
457
460
  registerCoreHookBatchCommand(core);
458
461
  registerCorePipelineRuntimeCommands(core);
462
+ registerCoreWorkflowRunCommand(core);
459
463
  const sessionHelperCommands = [
460
464
  {
461
465
  name: "session-journal", asset: "core/scripts/session-journal.sh", root: "live",
@@ -290,7 +290,6 @@ export function createLaneAccountsHandlers(deps) {
290
290
  balance_gate_enabled: balanceGateEnabled,
291
291
  accounts,
292
292
  selections,
293
- lead_reserve_per_host: config.lead_reserve_per_host,
294
293
  queued_leads: loaded.snapshots.flatMap((host) => {
295
294
  const leads = leadLaneIds(host.lanes);
296
295
  return host.lanes
@@ -0,0 +1,7 @@
1
+ /** Port of pipeline-envelope.sh normalize, including its candidate selection rules. */
2
+ export declare function normalizeWorkflowRunnerEnvelope(raw: string): string;
3
+ /** Makes a copy in Codex's strict structured-output dialect. */
4
+ export declare function strictifySchemaForCodex(schema: unknown): unknown;
5
+ /** Removes nulls that Codex had to add for originally optional properties. */
6
+ export declare function stripStrictNulls(value: unknown, schema: unknown): unknown;
7
+ //# sourceMappingURL=workflow-runner-schema.d.ts.map
@@ -0,0 +1,259 @@
1
+ function isJsonObject(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ function jsonDumps(value) {
5
+ if (value === null || typeof value !== "object")
6
+ return JSON.stringify(value) ?? "null";
7
+ if (Array.isArray(value))
8
+ return `[${value.map(jsonDumps).join(", ")}]`;
9
+ const entries = Object.entries(value);
10
+ return `{${entries.map(([key, child]) => `${JSON.stringify(key)}: ${jsonDumps(child)}`).join(", ")}}`;
11
+ }
12
+ function balancedJsonEnd(text, start) {
13
+ let depth = 0;
14
+ let inString = false;
15
+ for (let i = start; i < text.length; i += 1) {
16
+ const char = text[i];
17
+ if (inString) {
18
+ if (char === "\\")
19
+ i += 1;
20
+ else if (char === '"')
21
+ inString = false;
22
+ continue;
23
+ }
24
+ if (char === '"')
25
+ inString = true;
26
+ else if (char === "{" || char === "[")
27
+ depth += 1;
28
+ else if (char === "}" || char === "]") {
29
+ depth -= 1;
30
+ if (depth === 0)
31
+ return i + 1;
32
+ if (depth < 0)
33
+ return -1;
34
+ }
35
+ }
36
+ return -1;
37
+ }
38
+ /** Port of pipeline-envelope.sh normalize, including its candidate selection rules. */
39
+ export function normalizeWorkflowRunnerEnvelope(raw) {
40
+ const text = raw.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
41
+ const candidates = [];
42
+ const fence = /```[A-Za-z0-9_-]*\s*\n([\s\S]*?)```/g;
43
+ let match;
44
+ while ((match = fence.exec(text)) !== null) {
45
+ try {
46
+ const parsed = JSON.parse(match[1]?.trim() ?? "");
47
+ if (isJsonObject(parsed))
48
+ candidates.push(parsed);
49
+ }
50
+ catch { /* malformed fences are skipped by the shell normalizer */ }
51
+ }
52
+ if (candidates.length === 0) {
53
+ let offset = 0;
54
+ while (offset < text.length) {
55
+ const start = text.indexOf("{", offset);
56
+ if (start < 0)
57
+ break;
58
+ const end = balancedJsonEnd(text, start);
59
+ if (end > 0) {
60
+ try {
61
+ const parsed = JSON.parse(text.slice(start, end));
62
+ if (isJsonObject(parsed))
63
+ candidates.push(parsed);
64
+ offset = end;
65
+ continue;
66
+ }
67
+ catch { /* continue scanning after this opening brace */ }
68
+ }
69
+ offset = start + 1;
70
+ }
71
+ }
72
+ if (candidates.length === 0)
73
+ throw new Error("normalize: no JSON object found");
74
+ if (candidates.length > 1) {
75
+ const tagged = candidates.filter((candidate) => candidate.schema === "hq-phase-handoff/v1" || candidate.schema === "hq-phase-envelope/v1");
76
+ if (tagged.length !== 1)
77
+ throw new Error(`normalize: ${candidates.length} JSON objects found, expected exactly one`);
78
+ return `${jsonDumps(tagged[0])}\n`;
79
+ }
80
+ return `${jsonDumps(candidates[0])}\n`;
81
+ }
82
+ function isRecord(value) {
83
+ return value !== null && typeof value === "object" && !Array.isArray(value);
84
+ }
85
+ function typesOf(node) {
86
+ if (node.type === undefined)
87
+ return [];
88
+ return Array.isArray(node.type) ? [...node.type] : [node.type];
89
+ }
90
+ function isObjectNode(node) {
91
+ const types = typesOf(node);
92
+ return types.length ? types.includes("object") : isRecord(node.properties);
93
+ }
94
+ const MAP_KEYS = ["properties", "$defs", "definitions"];
95
+ const LIST_KEYS = ["anyOf", "oneOf", "allOf", "prefixItems"];
96
+ const SINGLE_KEYS = ["items", "not", "if", "then", "else", "contains", "propertyNames"];
97
+ function schemaError(at, message) {
98
+ return new Error(`codex output schema at ${at}: ${message}`);
99
+ }
100
+ function widenToNullable(node, at) {
101
+ if (!isRecord(node))
102
+ return;
103
+ const types = typesOf(node);
104
+ if (types.length) {
105
+ if (!types.includes("null"))
106
+ node.type = [...types, "null"];
107
+ if (Array.isArray(node.enum) && !node.enum.includes(null))
108
+ node.enum = [...node.enum, null];
109
+ return;
110
+ }
111
+ for (const key of ["anyOf", "oneOf"]) {
112
+ const branches = node[key];
113
+ if (!Array.isArray(branches) || branches.length === 0)
114
+ continue;
115
+ if (!branches.some((branch) => isRecord(branch) && typesOf(branch).includes("null")))
116
+ branches.push({ type: "null" });
117
+ return;
118
+ }
119
+ if (typeof node.$ref === "string" || Array.isArray(node.allOf)) {
120
+ const inner = { ...node };
121
+ for (const key of Object.keys(node))
122
+ delete node[key];
123
+ node.anyOf = [inner, { type: "null" }];
124
+ return;
125
+ }
126
+ if (isRecord(node.properties)) {
127
+ node.type = ["object", "null"];
128
+ return;
129
+ }
130
+ throw schemaError(at, "this property is optional (absent from `required`) but its schema declares neither a type nor a composition, so there is no way to express \"no answer\" in the strict dialect. Give it a type, or list it in `required`.");
131
+ }
132
+ function strictifyInPlace(node, at) {
133
+ if (Array.isArray(node)) {
134
+ node.forEach((child, index) => strictifyInPlace(child, `${at}[${index}]`));
135
+ return node;
136
+ }
137
+ if (!isRecord(node))
138
+ return node;
139
+ for (const key of MAP_KEYS) {
140
+ if (!isRecord(node[key]))
141
+ continue;
142
+ for (const [name, child] of Object.entries(node[key]))
143
+ strictifyInPlace(child, `${at}.${key}.${name}`);
144
+ }
145
+ for (const key of LIST_KEYS) {
146
+ if (Array.isArray(node[key]))
147
+ node[key].forEach((child, index) => strictifyInPlace(child, `${at}.${key}[${index}]`));
148
+ }
149
+ for (const key of SINGLE_KEYS)
150
+ if (node[key] !== undefined)
151
+ strictifyInPlace(node[key], `${at}.${key}`);
152
+ if (!isObjectNode(node))
153
+ return node;
154
+ if (node.additionalProperties !== undefined && node.additionalProperties !== false) {
155
+ throw schemaError(at, "strict structured output permits no dynamic keys, so `additionalProperties` must be false (got "
156
+ + `${JSON.stringify(node.additionalProperties)}). Model a map as an array of {key, value} entries, or run this agent on the grok or claude engine, which have no strict-schema flag.`);
157
+ }
158
+ node.additionalProperties = false;
159
+ if (typesOf(node).length === 0)
160
+ node.type = "object";
161
+ const props = isRecord(node.properties) ? node.properties : null;
162
+ if (!props)
163
+ return node;
164
+ const keys = Object.keys(props);
165
+ const declared = Array.isArray(node.required) ? node.required.filter((key) => typeof key === "string") : [];
166
+ const orphans = declared.filter((key) => !keys.includes(key));
167
+ if (orphans.length)
168
+ throw schemaError(at, `\`required\` lists ${orphans.map((key) => JSON.stringify(key)).join(", ")} with no matching property. Strict rejects a required name that is not in \`properties\`, and dropping it would leave the result validator demanding a key the model can no longer emit. Add the property, or remove the name from \`required\`.`);
169
+ const required = [];
170
+ for (const key of declared)
171
+ if (!required.includes(key))
172
+ required.push(key);
173
+ for (const key of keys) {
174
+ if (required.includes(key))
175
+ continue;
176
+ widenToNullable(props[key], `${at}.properties.${key}`);
177
+ required.push(key);
178
+ }
179
+ node.required = required;
180
+ return node;
181
+ }
182
+ /** Makes a copy in Codex's strict structured-output dialect. */
183
+ export function strictifySchemaForCodex(schema) {
184
+ if (!isRecord(schema) && !Array.isArray(schema))
185
+ return schema;
186
+ return strictifyInPlace(JSON.parse(JSON.stringify(schema)), "<root>");
187
+ }
188
+ const MAX_REF_DEPTH = 16;
189
+ function resolveRef(node, root, depth = 0) {
190
+ if (!isRecord(node) || typeof node.$ref !== "string" || depth >= MAX_REF_DEPTH || !isRecord(root))
191
+ return node;
192
+ const match = /^#\/(\$defs|definitions)\/([^/]+)$/.exec(node.$ref);
193
+ if (!match)
194
+ return node;
195
+ const bucket = root[match[1] ?? ""];
196
+ const target = isRecord(bucket) ? bucket[match[2] ?? ""] : undefined;
197
+ return isRecord(target) ? resolveRef(target, root, depth + 1) : node;
198
+ }
199
+ function allowsNull(node, root, depth = 0) {
200
+ const resolved = resolveRef(node, root, depth);
201
+ if (!isRecord(resolved))
202
+ return true;
203
+ const types = typesOf(resolved);
204
+ if (types.length)
205
+ return types.includes("null");
206
+ for (const key of ["anyOf", "oneOf"]) {
207
+ if (Array.isArray(resolved[key]))
208
+ return resolved[key].some((branch) => depth < MAX_REF_DEPTH && allowsNull(branch, root, depth + 1));
209
+ }
210
+ return isRecord(resolved.properties) ? false : true;
211
+ }
212
+ function effectiveSchema(node, root, depth = 0) {
213
+ const resolved = resolveRef(node, root, depth);
214
+ if (!isRecord(resolved))
215
+ return null;
216
+ for (const key of ["anyOf", "oneOf"]) {
217
+ if (!Array.isArray(resolved[key]))
218
+ continue;
219
+ const branches = resolved[key].map((branch) => resolveRef(branch, root, depth + 1))
220
+ .filter(isRecord).filter((branch) => !(typesOf(branch).length === 1 && typesOf(branch)[0] === "null"));
221
+ if (branches.length !== 1 || depth >= MAX_REF_DEPTH)
222
+ return null;
223
+ return effectiveSchema(branches[0], root, depth + 1);
224
+ }
225
+ return resolved;
226
+ }
227
+ function stripInPlace(value, schema, root, depth = 0) {
228
+ const effective = effectiveSchema(schema, root, depth);
229
+ if (!effective)
230
+ return value;
231
+ if (Array.isArray(value)) {
232
+ if (effective.items !== undefined)
233
+ value.forEach((item) => stripInPlace(item, effective.items, root, depth + 1));
234
+ return value;
235
+ }
236
+ if (!isRecord(value))
237
+ return value;
238
+ const props = isRecord(effective.properties) ? effective.properties : null;
239
+ if (!props)
240
+ return value;
241
+ const required = Array.isArray(effective.required) ? effective.required : [];
242
+ for (const [key, sub] of Object.entries(props)) {
243
+ if (!(key in value))
244
+ continue;
245
+ if (value[key] === null && !required.includes(key) && !allowsNull(sub, root)) {
246
+ delete value[key];
247
+ continue;
248
+ }
249
+ stripInPlace(value[key], sub, root, depth + 1);
250
+ }
251
+ return value;
252
+ }
253
+ /** Removes nulls that Codex had to add for originally optional properties. */
254
+ export function stripStrictNulls(value, schema) {
255
+ if (!isRecord(schema) || value === null || typeof value !== "object")
256
+ return value;
257
+ return stripInPlace(JSON.parse(JSON.stringify(value)), schema, schema);
258
+ }
259
+ //# sourceMappingURL=workflow-runner-schema.js.map
@@ -9,10 +9,14 @@
9
9
  import * as fs from "node:fs";
10
10
  import { type AccountConfig, type LaneProvider, type LanesConfig, type RunawaySearchGuardConfig, type LivenessWindows, type ProviderConfig } from "./types.js";
11
11
  export declare const LANES_CONFIG_REL: string;
12
- export declare const DEFAULT_MAX_QUEUED = 3;
13
- export declare const DEFAULT_CPU_BUSY_PAUSE = 0.85;
14
- export declare const DEFAULT_CPU_BUSY_RESUME = 0.7;
15
- export declare const DEFAULT_MIN_AVAILABLE_MEM_MB = 1024;
12
+ /**
13
+ * Owner directive 2026-10-14: no default lane caps or CPU/memory admission
14
+ * gates. A provider has no running or queued limit unless the operator sets
15
+ * `max_running` / `max_queued` explicitly; this value stands for "no limit"
16
+ * and stays a finite integer so it survives JSON output.
17
+ */
18
+ export declare const UNLIMITED_LANES: number;
19
+ export declare const DEFAULT_MAX_QUEUED: number;
16
20
  /**
17
21
  * Owner directive 2026-09-22: "if it's been an hour since last msg, always
18
22
  * run compact". Override per provider with `compact_after_ms`.
@@ -13,10 +13,14 @@ import { parseConfigEffort } from "./effort.js";
13
13
  import { LanesConfigError } from "./errors.js";
14
14
  import { CONFIG_SCHEMA_VERSION, LANE_PROVIDERS, isLaneProvider, parseMajorSchema, } from "./types.js";
15
15
  export const LANES_CONFIG_REL = path.join("personal", "lanes", "config.json");
16
- export const DEFAULT_MAX_QUEUED = 3;
17
- export const DEFAULT_CPU_BUSY_PAUSE = 0.85;
18
- export const DEFAULT_CPU_BUSY_RESUME = 0.7;
19
- export const DEFAULT_MIN_AVAILABLE_MEM_MB = 1024;
16
+ /**
17
+ * Owner directive 2026-10-14: no default lane caps or CPU/memory admission
18
+ * gates. A provider has no running or queued limit unless the operator sets
19
+ * `max_running` / `max_queued` explicitly; this value stands for "no limit"
20
+ * and stays a finite integer so it survives JSON output.
21
+ */
22
+ export const UNLIMITED_LANES = Number.MAX_SAFE_INTEGER;
23
+ export const DEFAULT_MAX_QUEUED = UNLIMITED_LANES;
20
24
  /**
21
25
  * Owner directive 2026-09-22: "if it's been an hour since last msg, always
22
26
  * run compact". Override per provider with `compact_after_ms`.
@@ -69,9 +73,9 @@ export const DEFAULT_LIVENESS_WINDOWS = {
69
73
  silent_lost_ms: DEFAULT_SILENT_LOST_MS,
70
74
  };
71
75
  export const DEFAULT_PROVIDER_MAX_RUNNING = {
72
- claude: 2,
73
- codex: 4,
74
- grok: 4,
76
+ claude: UNLIMITED_LANES,
77
+ codex: UNLIMITED_LANES,
78
+ grok: UNLIMITED_LANES,
75
79
  };
76
80
  export const DEFAULT_ACCOUNT_HOMES = {
77
81
  claude: "~/.claude",
@@ -152,14 +156,6 @@ function positiveInt(value, field, fallback) {
152
156
  }
153
157
  return value;
154
158
  }
155
- function nonNegativeInt(value, field, fallback) {
156
- if (value === undefined)
157
- return fallback;
158
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
159
- throw new LanesConfigError("invalid_config", `${field} must be a non-negative integer`, { field, value });
160
- }
161
- return value;
162
- }
163
159
  function positiveNumber(value, field, fallback) {
164
160
  if (value === undefined)
165
161
  return fallback;
@@ -168,9 +164,9 @@ function positiveNumber(value, field, fallback) {
168
164
  }
169
165
  return value;
170
166
  }
171
- function unitInterval(value, field, fallback) {
167
+ function optionalUnitInterval(value, field) {
172
168
  if (value === undefined)
173
- return fallback;
169
+ return undefined;
174
170
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
175
171
  throw new LanesConfigError("invalid_config", `${field} must be a number in [0, 1]`, { field, value });
176
172
  }
@@ -377,30 +373,35 @@ function parseProvider(provider, raw, deps) {
377
373
  }
378
374
  return parsed;
379
375
  }
376
+ /**
377
+ * CPU and memory admission gates are opt-in. With no `throttle` keys set,
378
+ * admission never waits on CPU load or free memory.
379
+ */
380
380
  function parseThrottle(raw) {
381
381
  const obj = raw === undefined ? {} : raw;
382
382
  if (!isPlainObject(obj)) {
383
383
  throw new LanesConfigError("invalid_config", "throttle must be an object");
384
384
  }
385
- const pause = unitInterval(obj.cpu_busy_pause, "throttle.cpu_busy_pause", DEFAULT_CPU_BUSY_PAUSE);
386
- const resume = unitInterval(obj.cpu_busy_resume, "throttle.cpu_busy_resume", DEFAULT_CPU_BUSY_RESUME);
387
- if (resume > pause) {
388
- throw new LanesConfigError("invalid_config", "throttle.cpu_busy_resume must be <= throttle.cpu_busy_pause", { cpu_busy_pause: pause, cpu_busy_resume: resume });
385
+ const result = {};
386
+ const pause = optionalUnitInterval(obj.cpu_busy_pause, "throttle.cpu_busy_pause");
387
+ const resumeRaw = optionalUnitInterval(obj.cpu_busy_resume, "throttle.cpu_busy_resume");
388
+ if (pause !== undefined) {
389
+ const resume = resumeRaw ?? pause;
390
+ if (resume > pause) {
391
+ throw new LanesConfigError("invalid_config", "throttle.cpu_busy_resume must be <= throttle.cpu_busy_pause", { cpu_busy_pause: pause, cpu_busy_resume: resume });
392
+ }
393
+ result.cpu_busy_pause = pause;
394
+ result.cpu_busy_resume = resume;
389
395
  }
390
- let minMem = DEFAULT_MIN_AVAILABLE_MEM_MB;
391
396
  if (obj.min_available_mem_mb !== undefined) {
392
397
  if (typeof obj.min_available_mem_mb !== "number" ||
393
398
  !Number.isFinite(obj.min_available_mem_mb) ||
394
399
  obj.min_available_mem_mb < 0) {
395
400
  throw new LanesConfigError("invalid_config", "throttle.min_available_mem_mb must be a non-negative number", { value: obj.min_available_mem_mb });
396
401
  }
397
- minMem = obj.min_available_mem_mb;
402
+ result.min_available_mem_mb = obj.min_available_mem_mb;
398
403
  }
399
- return {
400
- cpu_busy_pause: pause,
401
- cpu_busy_resume: resume,
402
- min_available_mem_mb: minMem,
403
- };
404
+ return result;
404
405
  }
405
406
  function parseLiveness(raw) {
406
407
  const obj = raw === undefined ? {} : raw;
@@ -458,16 +459,11 @@ export function documentedDefaultConfig(deps = defaultDeps()) {
458
459
  return {
459
460
  schema_version: CONFIG_SCHEMA_VERSION,
460
461
  providers,
461
- throttle: {
462
- cpu_busy_pause: DEFAULT_CPU_BUSY_PAUSE,
463
- cpu_busy_resume: DEFAULT_CPU_BUSY_RESUME,
464
- min_available_mem_mb: DEFAULT_MIN_AVAILABLE_MEM_MB,
465
- },
462
+ throttle: {},
466
463
  liveness: { ...DEFAULT_LIVENESS_WINDOWS },
467
464
  tracker: { checks: { ...DEFAULT_TRACKER_CHECKS } },
468
465
  runaway_search_guard: { ...DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG },
469
466
  low_disk_min_free_bytes: DEFAULT_LOW_DISK_MIN_FREE_BYTES,
470
- lead_reserve_per_host: 1,
471
467
  codex_rollout_compress_after_hours: DEFAULT_CODEX_ROLLOUT_COMPRESS_AFTER_HOURS,
472
468
  codex_rollout_delete_after_days: DEFAULT_CODEX_ROLLOUT_DELETE_AFTER_DAYS,
473
469
  session_question_timeout_secs: DEFAULT_SESSION_QUESTION_TIMEOUT_SECS,
@@ -508,7 +504,6 @@ export function parseLanesConfig(raw, deps = defaultDeps()) {
508
504
  }
509
505
  const compressAfterHours = positiveInt(raw.codex_rollout_compress_after_hours, "codex_rollout_compress_after_hours", DEFAULT_CODEX_ROLLOUT_COMPRESS_AFTER_HOURS);
510
506
  const deleteAfterDays = positiveInt(raw.codex_rollout_delete_after_days, "codex_rollout_delete_after_days", DEFAULT_CODEX_ROLLOUT_DELETE_AFTER_DAYS);
511
- const leadReservePerHost = nonNegativeInt(raw.lead_reserve_per_host, "lead_reserve_per_host", 1);
512
507
  if (deleteAfterDays * 24 < compressAfterHours) {
513
508
  throw new LanesConfigError("invalid_config", "codex_rollout_delete_after_days must be at least codex_rollout_compress_after_hours / 24", { codex_rollout_compress_after_hours: compressAfterHours, codex_rollout_delete_after_days: deleteAfterDays });
514
509
  }
@@ -522,7 +517,6 @@ export function parseLanesConfig(raw, deps = defaultDeps()) {
522
517
  tracker: { checks: parseTrackerChecks(raw.tracker) },
523
518
  runaway_search_guard: parseRunawaySearchGuard(raw.runaway_search_guard),
524
519
  low_disk_min_free_bytes: positiveInt(raw.low_disk_min_free_bytes, "low_disk_min_free_bytes", DEFAULT_LOW_DISK_MIN_FREE_BYTES),
525
- lead_reserve_per_host: leadReservePerHost,
526
520
  codex_rollout_compress_after_hours: compressAfterHours,
527
521
  codex_rollout_delete_after_days: deleteAfterDays,
528
522
  session_question_timeout_secs: positiveInt(raw.session_question_timeout_secs, "session_question_timeout_secs", DEFAULT_SESSION_QUESTION_TIMEOUT_SECS),
@@ -12,7 +12,7 @@ export type { HostCapacityAccount, HostCapacityProvider, HostCapacitySnapshot, H
12
12
  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";
13
13
  export type { AccountConfig, AttributionHistoryEntry, EnvelopeDecision, EnvelopeQuestion, HostKind, LaneCompanyRef, LaneAccountSelection, LaneAccountSelectionConsidered, LaneAccountSelectionHostCount, LaneEnvelope, LaneHost, LaneProvider, LanePlacementPolicy, LaneRecord, LaneStory, LaneStoryStatus, PendingResume, AccountHistoryEntry, LaneState, LanesConfig, LastEnvelopeRef, LivenessState, LivenessWindows, ProviderConfig, QuestionAnswer, QuestionBodyRef, QuestionPriority, QuestionRecord, QuestionStatus, QueuedReason, RaisedVia, RoutingHistoryEntry, RoutingReason, SeniorHistoryEntry, SeniorKind, SeniorRef, WorkerProfileRepinHistoryEntry, LaneSettingsHistoryEntry, StoryStatus, TerminalLaneState, ThrottleConfig, } from "./types.js";
14
14
  export { ACTIVE_LANE_STORY_STATUSES, activeLaneStories, laneStories, laneStory, storyKey, storyStatusForEnvelope, storyWarning, withCurrentStory, withLaneStories, } from "./stories.js";
15
- export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS, DEFAULT_CPU_BUSY_PAUSE, DEFAULT_CPU_BUSY_RESUME, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_MIN_AVAILABLE_MEM_MB, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, providerAutoCompactTokens, providerQuotaDefaultHoldMs, DEFAULT_QUOTA_HOLD_MS, writeLanesConfig, } from "./config.js";
15
+ export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, providerAutoCompactTokens, providerQuotaDefaultHoldMs, DEFAULT_QUOTA_HOLD_MS, writeLanesConfig, } from "./config.js";
16
16
  export type { ConfigLoadDeps, ConfigWriteDeps } from "./config.js";
17
17
  export { resolveLanesHqRoot } from "./hq-root.js";
18
18
  export type { LanesHqRootOpts } from "./hq-root.js";
@@ -8,7 +8,7 @@ export { compareLaneQueueOrder, hasActiveAdmissionReservation, hasActivePendingR
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";
10
10
  export { ACTIVE_LANE_STORY_STATUSES, activeLaneStories, laneStories, laneStory, storyKey, storyStatusForEnvelope, storyWarning, withCurrentStory, withLaneStories, } from "./stories.js";
11
- export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS, DEFAULT_CPU_BUSY_PAUSE, DEFAULT_CPU_BUSY_RESUME, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_MIN_AVAILABLE_MEM_MB, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, providerAutoCompactTokens, providerQuotaDefaultHoldMs, DEFAULT_QUOTA_HOLD_MS, writeLanesConfig, } from "./config.js";
11
+ export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, providerAutoCompactTokens, providerQuotaDefaultHoldMs, DEFAULT_QUOTA_HOLD_MS, writeLanesConfig, } from "./config.js";
12
12
  export { resolveLanesHqRoot } from "./hq-root.js";
13
13
  export { LANE_STORY_LEDGER_FLAG, buildLaneStatusSnapshot, formatLaneStatusHuman, laneStoryLedgerEnabled, resolveProjectStoryLedger, syncLaneToProjectStoryLedger, removeLaneFromProjectStoryLedger, validateProjectStories, } from "./story-ledger.js";
14
14
  export { buildOutpostPickupCommand, parseOutpostPickupPayload, } from "./outpost-dispatch.js";
@@ -8,7 +8,7 @@
8
8
  import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
10
  import { parseUserYaml } from "../../utils/user-yaml-error.js";
11
- import { compareLaneQueueOrder, compareLaneQueueOrderWithLeads, freeClaudeAccountSlots, leadLaneIds, hasActiveAdmissionReservation, hasActivePendingResume, laneAdmissionCapReason, laneOccupiesRunningSlot, laneRunsOnHost, withAllLaneAdmissionLocks, } from "./admission.js";
11
+ import { compareLaneQueueOrder, compareLaneQueueOrderWithLeads, leadLaneIds, hasActiveAdmissionReservation, hasActivePendingResume, laneAdmissionCapReason, laneOccupiesRunningSlot, laneRunsOnHost, withAllLaneAdmissionLocks, } from "./admission.js";
12
12
  import { accountStatus, isCredentialProven, resolveAccount } from "./accounts.js";
13
13
  import { effectiveAccountHome } from "./account-home.js";
14
14
  import { enforceRegistryApprovalGate } from "./approval-gate.js";
@@ -33,7 +33,7 @@ import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolG
33
33
  import { LEAD_LANE_KEEP_ALIVE_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, dueKeepAliveNextCheckMs, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
34
34
  import { isFailedQuotaKeepAliveLane, providerSupportsSessionResume } from "./delivery.js";
35
35
  import { LANE_PROVIDERS, isIsoDateTime, isTerminalLaneState } from "./types.js";
36
- import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, loadQuotaState, readQuotaSignal, recordAccountQuota, } from "./quota.js";
36
+ import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, readQuotaSignal, recordAccountQuota, } from "./quota.js";
37
37
  import { deliverQuotaNotice, quotaLaneNotice } from "./quota-notice.js";
38
38
  import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
39
39
  import { SILENCE_MS, appendWatcherEvent, classifyLaneExit, isPidAlive, missingExpectedEnvelopeFile, readLatestEnvelopeFile, readLatestEnvelopeRecord, watcherEventsPath, watcherOutLogPath, writeWatcherDecision, } from "./watcher.js";
@@ -2045,7 +2045,6 @@ async function admitQueued(hqRoot, lanes, summary, deps, now, onlyLaneIds, exclu
2045
2045
  if (localQueued.length === 0 || deps.skipAdmissionWithoutCpuSample === true)
2046
2046
  return;
2047
2047
  const config = loadLanesConfig(hqRoot);
2048
- const admissionQuotaState = loadQuotaState(hqRoot);
2049
2048
  const candidateLeadIds = leadLaneIds(lanes);
2050
2049
  const flagReader = deps.readRegistryFlag ?? readLanesRegistryFlag;
2051
2050
  const leadAdmissionEnabledById = new Map();
@@ -2140,25 +2139,6 @@ async function admitQueued(hqRoot, lanes, summary, deps, now, onlyLaneIds, exclu
2140
2139
  }
2141
2140
  }
2142
2141
  const projected = [...running, ...spawned];
2143
- const laneResolution = lane.provider === "claude" ? resolveLaneCompany(hqRoot, lane) : undefined;
2144
- const laneCompany = laneResolution?.company ?? undefined;
2145
- const laneCompanyKey = laneCompany?.uid ?? laneCompany?.slug ?? lane.company?.uid ?? lane.company?.slug;
2146
- let reserveEnabled = false;
2147
- if (lane.provider === "claude" && laneCompanyKey) {
2148
- let enabled = leadFlagByCompany.get(laneCompanyKey);
2149
- if (!enabled) {
2150
- enabled = readLanesRegistryFlagFailOff(LANE_LEAD_ADMISSION_PRIORITY_FLAG, {
2151
- company: laneCompany,
2152
- ...(laneCompany && laneResolution ? { companyUidSource: laneCompanyUidSource(laneResolution.source) } : {}),
2153
- env: deps.env,
2154
- }, flagReader);
2155
- leadFlagByCompany.set(laneCompanyKey, enabled);
2156
- }
2157
- reserveEnabled = await enabled;
2158
- }
2159
- const reserveBlocked = reserveEnabled && lane.provider === "claude" &&
2160
- !priorityLeads.has(lane.lane_id) &&
2161
- freeClaudeAccountSlots(projected, config, deps.host, now.getTime(), admissionQuotaState) <= config.lead_reserve_per_host;
2162
2142
  const alternateAccount = availableBalancedAccount(hqRoot, lane, projected, config, deps.host, profileMaxConcurrent, now.getTime());
2163
2143
  const admittedLane = alternateAccount !== undefined
2164
2144
  ? writeAdmissionAccount(hqRoot, lane, alternateAccount, deps.dryRun)
@@ -2166,10 +2146,7 @@ async function admitQueued(hqRoot, lanes, summary, deps, now, onlyLaneIds, exclu
2166
2146
  const baseCap = laneAdmissionCapReason(running, admittedLane, config, deps.host, profileMaxConcurrent, now.getTime());
2167
2147
  const currentCap = laneAdmissionCapReason([...running, ...spawned], admittedLane, config, deps.host, profileMaxConcurrent, now.getTime());
2168
2148
  let blockedBy;
2169
- if (reserveBlocked) {
2170
- blockedBy = "lead_reserve";
2171
- }
2172
- else if (currentCap !== undefined) {
2149
+ if (currentCap !== undefined) {
2173
2150
  blockedBy = baseCap ?? "queue_position";
2174
2151
  }
2175
2152
  if (!blockedBy && !decision.admit)
@@ -13,7 +13,6 @@ import { execFileSync as nodeExecFileSync } from "node:child_process";
13
13
  import * as fs from "node:fs";
14
14
  import * as os from "node:os";
15
15
  import * as path from "node:path";
16
- import { DEFAULT_CPU_BUSY_PAUSE, DEFAULT_CPU_BUSY_RESUME, DEFAULT_MIN_AVAILABLE_MEM_MB, } from "./config.js";
17
16
  // Reconcile normally measures the interval between cumulative CPU snapshots.
18
17
  // A five-second window establishes a baseline or recovers from stale counters.
19
18
  export const CPU_SAMPLE_MS = 5_000;
@@ -294,17 +293,18 @@ export function evaluateThrottle(input) {
294
293
  const pause = input.throttle.cpu_busy_pause;
295
294
  const resume = input.throttle.cpu_busy_resume;
296
295
  const minMem = input.throttle.min_available_mem_mb;
296
+ // Each gate is opt-in: an unset threshold never blocks admission.
297
297
  let cpuBlocks = false;
298
- if (input.cpuCheckEnabled && input.busy !== null) {
298
+ if (pause !== undefined && input.cpuCheckEnabled && input.busy !== null) {
299
299
  if (input.state.cpuPaused) {
300
- cpuBlocks = input.busy > resume;
300
+ cpuBlocks = input.busy > (resume ?? pause);
301
301
  }
302
302
  else {
303
303
  cpuBlocks = input.busy >= pause;
304
304
  }
305
305
  }
306
306
  let memBlocks = false;
307
- if (input.availableMemMb !== null) {
307
+ if (minMem !== undefined && input.availableMemMb !== null) {
308
308
  memBlocks = input.availableMemMb < minMem;
309
309
  }
310
310
  if (!cpuBlocks && !memBlocks) {
@@ -448,11 +448,7 @@ export function writeThrottleState(filePath, state, deps = defaultThrottleDeps()
448
448
  throw err;
449
449
  }
450
450
  }
451
- export async function decideAdmission(hqRoot, throttle = {
452
- cpu_busy_pause: DEFAULT_CPU_BUSY_PAUSE,
453
- cpu_busy_resume: DEFAULT_CPU_BUSY_RESUME,
454
- min_available_mem_mb: DEFAULT_MIN_AVAILABLE_MEM_MB,
455
- }, deps = defaultThrottleDeps(), cpuBusyFraction, cpuObservation) {
451
+ export async function decideAdmission(hqRoot, throttle = {}, deps = defaultThrottleDeps(), cpuBusyFraction, cpuObservation) {
456
452
  const filePath = throttleStatePath(hqRoot);
457
453
  const loaded = readThrottleState(filePath, deps);
458
454
  if (loaded === "unreadable") {