@indigoai-us/hq-cli 5.345.129 → 5.345.131

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 (35) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/assets/scaffold/core/scripts/lib/codex-output-schema.mjs +304 -0
  3. package/assets/scaffold/core/scripts/workflow-runner.mjs +2890 -0
  4. package/dist/command-catalog.generated.d.ts +29 -0
  5. package/dist/command-catalog.generated.js +34 -0
  6. package/dist/commands/capacity.d.ts +1 -2
  7. package/dist/commands/capacity.js +7 -9
  8. package/dist/commands/core-pipeline-workflow-runner-native.d.ts +175 -0
  9. package/dist/commands/core-pipeline-workflow-runner-native.js +3237 -0
  10. package/dist/commands/core-workflow-run-command.d.ts +4 -0
  11. package/dist/commands/core-workflow-run-command.js +41 -0
  12. package/dist/commands/core.js +4 -0
  13. package/dist/commands/lanes-accounts.js +0 -1
  14. package/dist/commands/lanes-loop.d.ts +14 -0
  15. package/dist/commands/lanes-loop.js +43 -2
  16. package/dist/commands/lanes.js +8 -4
  17. package/dist/lib/capacity/freshness.d.ts +17 -0
  18. package/dist/lib/capacity/freshness.js +16 -0
  19. package/dist/lib/capacity/route.d.ts +7 -0
  20. package/dist/lib/capacity/route.js +46 -1
  21. package/dist/lib/core-utils/workflow-runner-schema.d.ts +7 -0
  22. package/dist/lib/core-utils/workflow-runner-schema.js +259 -0
  23. package/dist/lib/lanes/config.d.ts +8 -4
  24. package/dist/lib/lanes/config.js +30 -36
  25. package/dist/lib/lanes/index.d.ts +1 -1
  26. package/dist/lib/lanes/index.js +1 -1
  27. package/dist/lib/lanes/loop.d.ts +5 -0
  28. package/dist/lib/lanes/loop.js +8 -0
  29. package/dist/lib/lanes/reconcile.js +21 -27
  30. package/dist/lib/lanes/throttle.js +5 -9
  31. package/dist/lib/lanes/types.d.ts +4 -5
  32. package/dist/lib/lanes/types.js +1 -0
  33. package/dist/lib/lanes/worker-continuity.d.ts +13 -1
  34. package/dist/lib/lanes/worker-continuity.js +14 -0
  35. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.131] — 2026-10-14
6
+ - `hq lanes --account auto` and `hq capacity route` no longer refuse every signed-in account when the capacity collector is off or its readings are stale; those accounts are picked with unknown usage, below accounts with fresh headroom.
7
+ - `hq lanes create` now resumes an earlier worker session only when the project, model, and effort match, and otherwise starts a fresh thread.
8
+ - A loop lane waiting for its next phase now shows as running in `hq lanes status` and `hq lanes list`, and becomes done only after it is stopped.
9
+
10
+ ## [5.345.130] — 2026-10-14
11
+ - Add native TypeScript `hq core workflow run` orchestration with the bundled runner as its flag-off fallback.
12
+
13
+ - `hq lanes` no longer limits or pauses lanes by default. Providers have no
14
+ running or queued cap, admission no longer waits on CPU load or free memory,
15
+ and the `lead_reserve_per_host` Claude slot reserve is removed (the key is
16
+ ignored if present). Set `providers.<name>.max_running`, `max_queued`, or
17
+ `throttle.cpu_busy_pause` / `cpu_busy_resume` / `min_available_mem_mb` in
18
+ the lanes config file to opt back in.
19
+
5
20
  ## [5.345.129] — 2026-10-14
6
21
 
7
22
  - Claude Remote Control account pools now stay connected across Claude CLI updates. The daemon still restarts a pool if its host exits, but no longer kills busy pool sessions after the two-hour update deadline.
@@ -0,0 +1,304 @@
1
+ // Codex structured-output ("strict") schema adaptation for the workflow runner.
2
+ //
3
+ // WHY THIS EXISTS
4
+ // `codex exec --output-schema <file>` forwards that file to the model provider
5
+ // as a STRICT structured-output schema (`text.format.schema`). Strict is a
6
+ // NARROWER dialect than the ordinary JSON Schema a workflow script writes, and
7
+ // the provider rejects the whole request — before the agent takes a single turn
8
+ // of work — when a node breaks one of its structural rules. Verified live
9
+ // against codex-cli 0.144.5 on 2026-08-19 (HTTP 400 `invalid_json_schema`):
10
+ //
11
+ // 1. every object node must carry `additionalProperties: false`
12
+ // In context=(), 'additionalProperties' is required to be supplied and
13
+ // to be false.
14
+ // 2. every object node's `required` must list EVERY key in `properties`
15
+ // In context=(), 'required' is required to be supplied and to be an
16
+ // array including every key in properties. Missing 'b'.
17
+ // 3. every schema node must declare a type
18
+ // In context=('properties', 'a'), schema must have a 'type' key.
19
+ //
20
+ // Rule 1 killed the first agent of every /orchestrate run on the codex engine;
21
+ // rule 2 is what the same run hits the moment rule 1 is satisfied. The stdout
22
+ // engines (grok, claude) have no schema flag — they are asked for the shape
23
+ // in-prompt and validated locally — so the dialect problem is codex-only, and
24
+ // so is this module.
25
+ //
26
+ // THE TRANSLATION
27
+ // Ordinary JSON Schema says "optional" by leaving a key out of `required`.
28
+ // Strict has no notion of optionality, so the faithful translation of an
29
+ // optional property is "required, but nullable": the key is always present and
30
+ // the model answers null when it has nothing to say. A plain typed node gets
31
+ // `null` added to its type; a composed node (`anyOf`/`oneOf`/`allOf`/`$ref`)
32
+ // gets a `{type: 'null'}` alternative instead, since a composed node carries no
33
+ // type of its own to widen. Both forms were confirmed accepted by the provider.
34
+ // `strictifySchemaForCodex()` makes that translation on the way out;
35
+ // `stripStrictNulls()` undoes it on the way back, deleting exactly the nulls
36
+ // the widening invited. A script therefore keeps declaring optionality the
37
+ // normal way, sees the same result shape on every engine (an optional field the
38
+ // model had nothing for is ABSENT, not null), and only the codex wire format
39
+ // changes.
40
+ //
41
+ // WHAT IT REFUSES TO TRANSLATE
42
+ // Two shapes have no strict equivalent, and both are rejected loudly here
43
+ // rather than quietly reinterpreted — a schema silently stripped of meaning
44
+ // returns data that looks complete and is not:
45
+ //
46
+ // - a free-form map (`additionalProperties: true`, or an additionalProperties
47
+ // sub-schema). Strict permits no dynamic keys at all; forcing `false` would
48
+ // drop exactly the keys the author asked for.
49
+ // - a `required` name with no matching property. Strict rejects the extra
50
+ // name, and dropping it would leave the runner's own validator demanding a
51
+ // key the model can no longer emit — a result that could never validate.
52
+ //
53
+ // Both throw before the agent is spawned, naming the offending path, so the
54
+ // author sees a fixable contract error instead of an opaque 400 or a silently
55
+ // truncated result.
56
+ //
57
+ // Both exported functions are pure — they never mutate their arguments —
58
+ // because the runner keeps using the script's own schema for the in-prompt copy
59
+ // and for local result validation, and workflow scripts routinely share one
60
+ // schema constant across several agent() calls.
61
+
62
+ function isPlainObject(value) {
63
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
64
+ }
65
+
66
+ function typesOf(node) {
67
+ if (!isPlainObject(node) || node.type === undefined) return [];
68
+ return Array.isArray(node.type) ? node.type.slice() : [node.type];
69
+ }
70
+
71
+ // An object node is one that declares type "object" (possibly among others,
72
+ // e.g. ["object","null"]), or that declares no type but carries `properties` —
73
+ // the shorthand a hand-written schema often uses.
74
+ function isObjectNode(node) {
75
+ const types = typesOf(node);
76
+ if (types.length) return types.includes('object');
77
+ return isPlainObject(node.properties);
78
+ }
79
+
80
+ function isComposed(node) {
81
+ return isPlainObject(node)
82
+ && (Array.isArray(node.anyOf) || Array.isArray(node.oneOf)
83
+ || Array.isArray(node.allOf) || typeof node.$ref === 'string');
84
+ }
85
+
86
+ // Sub-schema positions to recurse through. Keyed by how the value is shaped so
87
+ // a `properties` map named "items" (a legal property name) is never mistaken
88
+ // for the `items` keyword.
89
+ const SUBSCHEMA_MAPS = ['properties', '$defs', 'definitions'];
90
+ const SUBSCHEMA_LISTS = ['anyOf', 'oneOf', 'allOf', 'prefixItems'];
91
+ const SUBSCHEMA_SINGLES = ['items', 'not', 'if', 'then', 'else', 'contains', 'propertyNames'];
92
+
93
+ function schemaError(at, message) {
94
+ return new Error(`codex output schema at ${at}: ${message}`);
95
+ }
96
+
97
+ // Optional -> "required but nullable". A typed node widens its own type; a
98
+ // composed node has no type to widen, so it gains a null ALTERNATIVE instead.
99
+ function widenToNullable(node, at) {
100
+ if (!isPlainObject(node)) {
101
+ // A boolean schema (`true`/`false`) carries no shape to widen. `true`
102
+ // already admits null; `false` admits nothing and is the author's business.
103
+ return;
104
+ }
105
+
106
+ const types = typesOf(node);
107
+ if (types.length) {
108
+ if (!types.includes('null')) node.type = [...types, 'null'];
109
+ // An enum must gain null too, or the widened type is unusable: no enum
110
+ // member could satisfy it.
111
+ if (Array.isArray(node.enum) && !node.enum.some((v) => v === null)) node.enum = [...node.enum, null];
112
+ return;
113
+ }
114
+
115
+ for (const key of ['anyOf', 'oneOf']) {
116
+ if (!Array.isArray(node[key]) || node[key].length === 0) continue;
117
+ if (!node[key].some((branch) => typesOf(branch).includes('null'))) node[key].push({ type: 'null' });
118
+ return;
119
+ }
120
+
121
+ // `$ref` and `allOf` cannot take a sibling type (and `allOf` is an AND, so a
122
+ // null branch inside it would contradict the others) — wrap the whole node in
123
+ // an anyOf whose second branch is null.
124
+ if (typeof node.$ref === 'string' || Array.isArray(node.allOf)) {
125
+ const inner = { ...node };
126
+ for (const key of Object.keys(node)) delete node[key];
127
+ node.anyOf = [inner, { type: 'null' }];
128
+ return;
129
+ }
130
+
131
+ // Object shorthand: `properties` with no declared type. Strict requires a
132
+ // type key anyway, so naming it here fixes two problems at once.
133
+ if (isPlainObject(node.properties)) {
134
+ node.type = ['object', 'null'];
135
+ return;
136
+ }
137
+
138
+ throw schemaError(at,
139
+ 'this property is optional (absent from `required`) but its schema declares '
140
+ + 'neither a type nor a composition, so there is no way to express "no answer" '
141
+ + 'in the strict dialect. Give it a type, or list it in `required`.');
142
+ }
143
+
144
+ function strictifyInPlace(node, at) {
145
+ if (Array.isArray(node)) {
146
+ node.forEach((child, i) => strictifyInPlace(child, `${at}[${i}]`));
147
+ return node;
148
+ }
149
+ if (!isPlainObject(node)) return node;
150
+
151
+ for (const key of SUBSCHEMA_MAPS) {
152
+ if (!isPlainObject(node[key])) continue;
153
+ for (const [name, child] of Object.entries(node[key])) strictifyInPlace(child, `${at}.${key}.${name}`);
154
+ }
155
+ for (const key of SUBSCHEMA_LISTS) {
156
+ if (Array.isArray(node[key])) node[key].forEach((child, i) => strictifyInPlace(child, `${at}.${key}[${i}]`));
157
+ }
158
+ for (const key of SUBSCHEMA_SINGLES) {
159
+ if (node[key] !== undefined) strictifyInPlace(node[key], `${at}.${key}`);
160
+ }
161
+
162
+ if (!isObjectNode(node)) return node;
163
+
164
+ // A free-form map is the one thing strict cannot express. Say so instead of
165
+ // narrowing it away and returning an object missing its dynamic keys.
166
+ if (node.additionalProperties !== undefined && node.additionalProperties !== false) {
167
+ throw schemaError(at,
168
+ 'strict structured output permits no dynamic keys, so `additionalProperties` '
169
+ + `must be false (got ${JSON.stringify(node.additionalProperties)}). Model a map `
170
+ + 'as an array of {key, value} entries, or run this agent on the grok or '
171
+ + 'claude engine, which have no strict-schema flag.');
172
+ }
173
+ node.additionalProperties = false;
174
+ // Strict requires an explicit type; the object shorthand omits it.
175
+ if (typesOf(node).length === 0) node.type = 'object';
176
+
177
+ const props = isPlainObject(node.properties) ? node.properties : null;
178
+ if (!props) return node;
179
+
180
+ const keys = Object.keys(props);
181
+ const declared = Array.isArray(node.required) ? node.required : [];
182
+ const orphans = declared.filter((key) => !keys.includes(key));
183
+ if (orphans.length) {
184
+ throw schemaError(at,
185
+ `\`required\` lists ${orphans.map((k) => JSON.stringify(k)).join(', ')} with no matching `
186
+ + 'property. Strict rejects a required name that is not in `properties`, and dropping it '
187
+ + 'would leave the result validator demanding a key the model can no longer emit. '
188
+ + 'Add the property, or remove the name from `required`.');
189
+ }
190
+
191
+ // Declared-required first (stable order), then the rest — each widened so the
192
+ // model can still answer "nothing here" for a property the script left optional.
193
+ const required = [];
194
+ for (const key of declared) if (!required.includes(key)) required.push(key);
195
+ for (const key of keys) {
196
+ if (required.includes(key)) continue;
197
+ widenToNullable(props[key], `${at}.properties.${key}`);
198
+ required.push(key);
199
+ }
200
+ node.required = required;
201
+ return node;
202
+ }
203
+
204
+ // Returns a strict-dialect copy of `schema`, safe to write to --output-schema.
205
+ // Non-object inputs (a boolean schema, a stray string) pass through untouched.
206
+ // Throws on a schema whose meaning strict cannot carry — see the header.
207
+ export function strictifySchemaForCodex(schema) {
208
+ if (!isPlainObject(schema) && !Array.isArray(schema)) return schema;
209
+ // A JSON round-trip is a faithful clone here: the result is about to be
210
+ // JSON.stringify'd into the schema file, so anything it would drop could not
211
+ // have reached codex anyway.
212
+ return strictifyInPlace(JSON.parse(JSON.stringify(schema)), '<root>');
213
+ }
214
+
215
+ const MAX_REF_DEPTH = 16;
216
+
217
+ // Resolve a local `#/$defs/x` (or `#/definitions/x`) reference against the root
218
+ // schema. Anything else — a remote or pointer-nested ref — is left alone, which
219
+ // makes the caller treat it as "shape unknown" and leave the value untouched.
220
+ function resolveRef(node, root, depth = 0) {
221
+ if (!isPlainObject(node) || typeof node.$ref !== 'string' || depth >= MAX_REF_DEPTH) return node;
222
+ const match = /^#\/(\$defs|definitions)\/([^/]+)$/.exec(node.$ref);
223
+ if (!match || !isPlainObject(root)) return node;
224
+ const bucket = root[match[1]];
225
+ const target = isPlainObject(bucket) ? bucket[match[2]] : undefined;
226
+ return isPlainObject(target) ? resolveRef(target, root, depth + 1) : node;
227
+ }
228
+
229
+ // A property may legitimately hold null when its own schema says so — only the
230
+ // nulls our widening invited get removed. Composition-aware: a union that has
231
+ // no null branch does not allow null either.
232
+ function allowsNull(node, root, depth = 0) {
233
+ const resolved = resolveRef(node, root, depth);
234
+ if (!isPlainObject(resolved)) return true;
235
+ const types = typesOf(resolved);
236
+ if (types.length) return types.includes('null');
237
+ for (const key of ['anyOf', 'oneOf']) {
238
+ if (Array.isArray(resolved[key])) {
239
+ return resolved[key].some((branch) => depth < MAX_REF_DEPTH && allowsNull(branch, root, depth + 1));
240
+ }
241
+ }
242
+ // Object shorthand (`properties`, no declared type) is an object node, not a
243
+ // nullable one — and it is a shape this module widens, so the null it comes
244
+ // back with is ours to remove. Missing this made a live codex answer keep
245
+ // `"meta": null` for a property the script had declared as an object.
246
+ if (isPlainObject(resolved.properties)) return false;
247
+ return true; // nothing declared — do not touch the value
248
+ }
249
+
250
+ // The schema to descend into for a value. A `$ref` resolves; a union resolves
251
+ // only when exactly one branch is non-null (the "optional X" shape, including
252
+ // the one our own widening produces). A genuinely ambiguous union returns null
253
+ // and the subtree is left alone rather than stripped on a guess.
254
+ function effectiveSchema(node, root, depth = 0) {
255
+ const resolved = resolveRef(node, root, depth);
256
+ if (!isPlainObject(resolved)) return null;
257
+ for (const key of ['anyOf', 'oneOf']) {
258
+ if (!Array.isArray(resolved[key])) continue;
259
+ const branches = resolved[key]
260
+ .map((branch) => resolveRef(branch, root, depth + 1))
261
+ .filter((branch) => isPlainObject(branch))
262
+ .filter((branch) => !(typesOf(branch).length === 1 && typesOf(branch)[0] === 'null'));
263
+ if (branches.length !== 1 || depth >= MAX_REF_DEPTH) return null;
264
+ return effectiveSchema(branches[0], root, depth + 1);
265
+ }
266
+ return resolved;
267
+ }
268
+
269
+ // Mirrors the runner's own validator (properties + items), plus the `$ref` and
270
+ // single-alternative union forms this module can widen — so a value that
271
+ // survives this walk is shaped the way that validator will read it.
272
+ function stripInPlace(value, schema, root, depth = 0) {
273
+ const effective = effectiveSchema(schema, root, depth);
274
+ if (!isPlainObject(effective)) return value;
275
+ if (Array.isArray(value)) {
276
+ if (effective.items !== undefined) {
277
+ for (const item of value) stripInPlace(item, effective.items, root, depth + 1);
278
+ }
279
+ return value;
280
+ }
281
+ if (!isPlainObject(value)) return value;
282
+ const props = isPlainObject(effective.properties) ? effective.properties : null;
283
+ if (!props) return value;
284
+ const required = Array.isArray(effective.required) ? effective.required : [];
285
+ for (const [key, sub] of Object.entries(props)) {
286
+ if (!(key in value)) continue;
287
+ if (value[key] === null && !required.includes(key) && !allowsNull(sub, root)) {
288
+ delete value[key];
289
+ continue;
290
+ }
291
+ stripInPlace(value[key], sub, root, depth + 1);
292
+ }
293
+ return value;
294
+ }
295
+
296
+ // Undoes the optional -> nullable widening on a codex result: a null answer for
297
+ // a property the ORIGINAL schema left optional (and did not itself declare
298
+ // nullable) becomes an absent key, which is what the script asked for and what
299
+ // every other engine returns.
300
+ export function stripStrictNulls(value, schema) {
301
+ if (!isPlainObject(schema)) return value;
302
+ if (value === null || typeof value !== 'object') return value;
303
+ return stripInPlace(JSON.parse(JSON.stringify(value)), schema, schema);
304
+ }