@indigoai-us/hq-cli 5.345.55 → 5.345.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/assets/scaffold/core/scripts/derive-trigger-facts.sh +10 -1
- package/assets/scaffold/core/scripts/jobs-validate.sh +2 -1
- package/assets/scaffold/core/scripts/session-title.sh +18 -15
- package/dist/command-catalog.generated.d.ts +124 -0
- package/dist/command-catalog.generated.js +155 -0
- package/dist/commands/core-delegate-w1b.js +18 -5
- package/dist/commands/core-derive-trigger-facts-command.js +14 -0
- package/dist/commands/core-derive-trigger-facts-native.d.ts +1 -0
- package/dist/commands/core-derive-trigger-facts-native.js +20 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +15 -2
- package/dist/commands/lanes-checks.d.ts +3 -0
- package/dist/commands/lanes-checks.js +34 -0
- package/dist/commands/lanes-loop.d.ts +12 -0
- package/dist/commands/lanes-loop.js +214 -0
- package/dist/commands/lanes-questions.js +37 -11
- package/dist/commands/lanes-resume.js +19 -1
- package/dist/commands/lanes-session-start.d.ts +2 -7
- package/dist/commands/lanes-session-start.js +9 -29
- package/dist/commands/lanes-shared.d.ts +14 -2
- package/dist/commands/lanes.d.ts +3 -1
- package/dist/commands/lanes.js +376 -35
- package/dist/commands/members.d.ts +4 -0
- package/dist/commands/members.js +23 -2
- package/dist/lanes-drain-hook.js +6 -2
- package/dist/lib/doctor/checks/sync-health.d.ts +2 -0
- package/dist/lib/doctor/checks/sync-health.js +47 -23
- package/dist/lib/lanes/delivery.d.ts +3 -0
- package/dist/lib/lanes/delivery.js +5 -0
- package/dist/lib/lanes/dropbox.d.ts +7 -2
- package/dist/lib/lanes/dropbox.js +157 -10
- package/dist/lib/lanes/flag-keys.d.ts +12 -0
- package/dist/lib/lanes/flag-keys.js +12 -0
- package/dist/lib/lanes/hooks-install.d.ts +14 -11
- package/dist/lib/lanes/hooks-install.js +70 -50
- package/dist/lib/lanes/loop.d.ts +158 -0
- package/dist/lib/lanes/loop.js +743 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +9 -1
- package/dist/lib/lanes/outpost-dispatch.js +21 -7
- package/dist/lib/lanes/reaper.d.ts +58 -0
- package/dist/lib/lanes/reaper.js +403 -0
- package/dist/lib/lanes/reconcile.d.ts +19 -0
- package/dist/lib/lanes/reconcile.js +173 -6
- package/dist/lib/lanes/spawn.d.ts +4 -0
- package/dist/lib/lanes/spawn.js +41 -9
- package/dist/lib/lanes/status-fields.d.ts +12 -0
- package/dist/lib/lanes/status-fields.js +189 -0
- package/dist/lib/lanes/store.js +49 -2
- package/dist/lib/lanes/types.d.ts +18 -0
- package/dist/lib/lanes/types.js +3 -3
- package/dist/lib/lanes/watcher.js +3 -0
- package/dist/lib/lanes/worker-continuity.d.ts +18 -0
- package/dist/lib/lanes/worker-continuity.js +81 -0
- package/dist/lib/lanes/worker-process.d.ts +4 -0
- package/dist/lib/lanes/worker-process.js +77 -0
- package/dist/main.js +9 -3
- package/dist/utils/cli-telemetry.d.ts +4 -1
- package/dist/utils/cli-telemetry.js +8 -0
- package/dist/utils/jq-windows-compat.d.ts +13 -0
- package/dist/utils/jq-windows-compat.js +48 -0
- package/dist/utils/run-bundled-script.d.ts +3 -0
- package/dist/utils/run-bundled-script.js +9 -80
- package/dist/utils/windows-bash.d.ts +5 -0
- package/dist/utils/windows-bash.js +75 -0
- package/package.json +1 -1
|
@@ -6,15 +6,18 @@
|
|
|
6
6
|
* registers nothing:
|
|
7
7
|
*
|
|
8
8
|
* - grok reads every `*.json` under `$GROK_HOME/hooks/`, so the registration
|
|
9
|
-
* file in that directory is the registration.
|
|
9
|
+
* file in that directory is the registration. Grok passive events cannot
|
|
10
|
+
* inject model-facing context or block, so its drain stays on PreToolUse.
|
|
10
11
|
* - claude reads `$CLAUDE_CONFIG_DIR/settings.json` and nothing under
|
|
11
|
-
* `hooks/`. The registration is merged into
|
|
12
|
-
* that file.
|
|
13
|
-
* - codex reads `$CODEX_HOME/config.toml` (`[[hooks
|
|
14
|
-
* `[features] hooks = true`). The registration is appended
|
|
12
|
+
* `hooks/`. The registration is merged into PreToolUse, PostToolUse, and
|
|
13
|
+
* Stop arrays in that file.
|
|
14
|
+
* - codex reads `$CODEX_HOME/config.toml` (`[[hooks.<Event>]]`, gated by
|
|
15
|
+
* `[features] hooks = true`). The registration is appended for all three
|
|
16
|
+
* delivery events.
|
|
15
17
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
+
* Claude and Codex register all three delivery events. Grok registers only
|
|
19
|
+
* PreToolUse because its PostToolUse and Stop output cannot reach the model.
|
|
20
|
+
* Registration merges keep existing operator hooks and are idempotent.
|
|
18
21
|
* Never merges into hq-hq-bridge.json / hq-hq-bridge.sh.
|
|
19
22
|
*/
|
|
20
23
|
import type { LaneProvider } from "./types.js";
|
|
@@ -40,9 +43,9 @@ export interface DrainHookInstallResult {
|
|
|
40
43
|
files: string[];
|
|
41
44
|
registration: DrainHookRegistration;
|
|
42
45
|
}
|
|
43
|
-
export declare function drainHookCommand(_shPath?: string): string;
|
|
46
|
+
export declare function drainHookCommand(_shPath?: string, provider?: LaneProvider): string;
|
|
44
47
|
export declare function drainHookShPath(providerHome: string): string;
|
|
45
|
-
export declare function renderDrainHookJson(shPath: string): string;
|
|
48
|
+
export declare function renderDrainHookJson(shPath: string, provider?: LaneProvider): string;
|
|
46
49
|
export interface MergeResult {
|
|
47
50
|
text: string;
|
|
48
51
|
changed: boolean;
|
|
@@ -56,9 +59,9 @@ export interface MergeResult {
|
|
|
56
59
|
export declare function mergeClaudeSettingsHook(existingText: string | null, command: string, settingsPath: string, legacyCommand?: string): MergeResult;
|
|
57
60
|
/**
|
|
58
61
|
* Codex: hooks come from `config.toml` in the codex home. The drain is
|
|
59
|
-
* appended
|
|
62
|
+
* appended to PreToolUse, PostToolUse, and Stop, and `[features] hooks = true`
|
|
60
63
|
* is ensured. Edits are textual so comments and unrelated tables are kept
|
|
61
|
-
* byte for byte; the result is re-parsed and must contain
|
|
64
|
+
* byte for byte; the result is re-parsed and must contain every registration,
|
|
62
65
|
* otherwise nothing is written.
|
|
63
66
|
*/
|
|
64
67
|
export declare function mergeCodexConfigHook(existingText: string | null, command: string, configPath: string, legacyCommand?: string): MergeResult;
|
|
@@ -6,15 +6,18 @@
|
|
|
6
6
|
* registers nothing:
|
|
7
7
|
*
|
|
8
8
|
* - grok reads every `*.json` under `$GROK_HOME/hooks/`, so the registration
|
|
9
|
-
* file in that directory is the registration.
|
|
9
|
+
* file in that directory is the registration. Grok passive events cannot
|
|
10
|
+
* inject model-facing context or block, so its drain stays on PreToolUse.
|
|
10
11
|
* - claude reads `$CLAUDE_CONFIG_DIR/settings.json` and nothing under
|
|
11
|
-
* `hooks/`. The registration is merged into
|
|
12
|
-
* that file.
|
|
13
|
-
* - codex reads `$CODEX_HOME/config.toml` (`[[hooks
|
|
14
|
-
* `[features] hooks = true`). The registration is appended
|
|
12
|
+
* `hooks/`. The registration is merged into PreToolUse, PostToolUse, and
|
|
13
|
+
* Stop arrays in that file.
|
|
14
|
+
* - codex reads `$CODEX_HOME/config.toml` (`[[hooks.<Event>]]`, gated by
|
|
15
|
+
* `[features] hooks = true`). The registration is appended for all three
|
|
16
|
+
* delivery events.
|
|
15
17
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
+
* Claude and Codex register all three delivery events. Grok registers only
|
|
19
|
+
* PreToolUse because its PostToolUse and Stop output cannot reach the model.
|
|
20
|
+
* Registration merges keep existing operator hooks and are idempotent.
|
|
18
21
|
* Never merges into hq-hq-bridge.json / hq-hq-bridge.sh.
|
|
19
22
|
*/
|
|
20
23
|
import * as fs from "node:fs";
|
|
@@ -44,8 +47,9 @@ export const PROVIDER_REGISTRATION_KIND = {
|
|
|
44
47
|
codex: "config_toml",
|
|
45
48
|
grok: "hooks_dir",
|
|
46
49
|
};
|
|
47
|
-
export function drainHookCommand(_shPath) {
|
|
48
|
-
|
|
50
|
+
export function drainHookCommand(_shPath, provider) {
|
|
51
|
+
const providerEnv = provider === "grok" ? "HQ_LANE_PROVIDER=grok " : "";
|
|
52
|
+
return `${providerEnv}${shellQuote(process.execPath)} ${shellQuote(drainHookEntryPath())}`;
|
|
49
53
|
}
|
|
50
54
|
function legacyDrainHookCommand(shPath) {
|
|
51
55
|
return `bash ${shPath}`;
|
|
@@ -148,7 +152,10 @@ function isInstalledDrainEntry(entryPath) {
|
|
|
148
152
|
function drainCommandsMatch(candidate, expected) {
|
|
149
153
|
if (candidate === expected)
|
|
150
154
|
return true;
|
|
151
|
-
const
|
|
155
|
+
const withoutProviderEnv = candidate.startsWith("HQ_LANE_PROVIDER=grok ")
|
|
156
|
+
? candidate.slice("HQ_LANE_PROVIDER=grok ".length)
|
|
157
|
+
: candidate;
|
|
158
|
+
const argv = parseShellQuotedArguments(withoutProviderEnv);
|
|
152
159
|
if (argv === null || argv.length !== 2)
|
|
153
160
|
return false;
|
|
154
161
|
if (path.parse(argv[0]).name.toLowerCase() !== "node")
|
|
@@ -170,7 +177,9 @@ function groupsContainDrainCommand(groups, expected) {
|
|
|
170
177
|
});
|
|
171
178
|
}
|
|
172
179
|
function tomlHasDrainCommand(parsed, expected) {
|
|
173
|
-
|
|
180
|
+
const hooks = isPlainObject(parsed.hooks) ? parsed.hooks : null;
|
|
181
|
+
return hooks !== null &&
|
|
182
|
+
["PreToolUse", "PostToolUse", "Stop"].every((event) => groupsContainDrainCommand(hooks[event], expected));
|
|
174
183
|
}
|
|
175
184
|
function removeCommandFromGroups(groups, command) {
|
|
176
185
|
let changed = false;
|
|
@@ -187,22 +196,14 @@ function removeCommandFromGroups(groups, command) {
|
|
|
187
196
|
});
|
|
188
197
|
return { groups: next, changed };
|
|
189
198
|
}
|
|
190
|
-
export function renderDrainHookJson(shPath) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
{
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
command: drainHookCommand(shPath),
|
|
199
|
-
timeout: DRAIN_HOOK_TIMEOUT_SECONDS,
|
|
200
|
-
},
|
|
201
|
-
],
|
|
202
|
-
},
|
|
203
|
-
],
|
|
204
|
-
},
|
|
205
|
-
}, null, 2)}\n`;
|
|
199
|
+
export function renderDrainHookJson(shPath, provider = "grok") {
|
|
200
|
+
const command = drainHookCommand(shPath, provider);
|
|
201
|
+
const registration = {
|
|
202
|
+
hooks: Object.fromEntries(["PreToolUse"].map((event) => [event, [
|
|
203
|
+
{ hooks: [{ type: "command", command, timeout: DRAIN_HOOK_TIMEOUT_SECONDS }] },
|
|
204
|
+
]])),
|
|
205
|
+
};
|
|
206
|
+
return `${JSON.stringify(registration, null, 2)}\n`;
|
|
206
207
|
}
|
|
207
208
|
/**
|
|
208
209
|
* Claude Code: `settings.json` is the one file it reads hooks from. Merge the
|
|
@@ -227,27 +228,35 @@ export function mergeClaudeSettingsHook(existingText, command, settingsPath, leg
|
|
|
227
228
|
if (!isPlainObject(hooks)) {
|
|
228
229
|
throw new LanesError("settings_unparseable", `${settingsPath} has a non-object "hooks" key; refusing to rewrite it`, { path: settingsPath });
|
|
229
230
|
}
|
|
230
|
-
const
|
|
231
|
+
const events = ["PreToolUse", "PostToolUse", "Stop"];
|
|
232
|
+
const eventGroups = new Map();
|
|
233
|
+
for (const event of events) {
|
|
234
|
+
const groups = hooks[event] === undefined ? [] : hooks[event];
|
|
235
|
+
if (!Array.isArray(groups)) {
|
|
236
|
+
throw new LanesError("settings_unparseable", `${settingsPath} has a non-array "hooks.${event}"; refusing to rewrite it`, { path: settingsPath });
|
|
237
|
+
}
|
|
238
|
+
eventGroups.set(event, groups);
|
|
239
|
+
}
|
|
240
|
+
const pre = eventGroups.get("PreToolUse");
|
|
231
241
|
if (!Array.isArray(pre)) {
|
|
232
242
|
throw new LanesError("settings_unparseable", `${settingsPath} has a non-array "hooks.PreToolUse"; refusing to rewrite it`, { path: settingsPath });
|
|
233
243
|
}
|
|
234
244
|
const migration = legacyCommand && legacyCommand !== command
|
|
235
245
|
? removeCommandFromGroups(pre, legacyCommand)
|
|
236
246
|
: { groups: pre, changed: false };
|
|
237
|
-
|
|
247
|
+
const allRegistered = events.every((event) => groupsContainCommand(eventGroups.get(event), command));
|
|
248
|
+
if (allRegistered && !migration.changed) {
|
|
238
249
|
return { text: existingText ?? "", changed: false };
|
|
239
250
|
}
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
{ type: "command", command, timeout: DRAIN_HOOK_TIMEOUT_SECONDS },
|
|
247
|
-
],
|
|
248
|
-
},
|
|
251
|
+
const nextHooks = { ...hooks };
|
|
252
|
+
for (const event of events) {
|
|
253
|
+
const groups = event === "PreToolUse" ? migration.groups : eventGroups.get(event);
|
|
254
|
+
nextHooks[event] = groupsContainCommand(groups, command) ? groups : [
|
|
255
|
+
...groups,
|
|
256
|
+
{ hooks: [{ type: "command", command, timeout: DRAIN_HOOK_TIMEOUT_SECONDS }] },
|
|
249
257
|
];
|
|
250
|
-
|
|
258
|
+
}
|
|
259
|
+
const nextRoot = { ...root, hooks: nextHooks };
|
|
251
260
|
return { text: `${JSON.stringify(nextRoot, null, 2)}\n`, changed: true };
|
|
252
261
|
}
|
|
253
262
|
function tomlBasicString(value) {
|
|
@@ -424,9 +433,9 @@ function enableTomlHooksFeature(text) {
|
|
|
424
433
|
}
|
|
425
434
|
/**
|
|
426
435
|
* Codex: hooks come from `config.toml` in the codex home. The drain is
|
|
427
|
-
* appended
|
|
436
|
+
* appended to PreToolUse, PostToolUse, and Stop, and `[features] hooks = true`
|
|
428
437
|
* is ensured. Edits are textual so comments and unrelated tables are kept
|
|
429
|
-
* byte for byte; the result is re-parsed and must contain
|
|
438
|
+
* byte for byte; the result is re-parsed and must contain every registration,
|
|
430
439
|
* otherwise nothing is written.
|
|
431
440
|
*/
|
|
432
441
|
export function mergeCodexConfigHook(existingText, command, configPath, legacyCommand) {
|
|
@@ -435,7 +444,8 @@ export function mergeCodexConfigHook(existingText, command, configPath, legacyCo
|
|
|
435
444
|
const migration = removeLegacyCodexHook(original, parsed, legacyCommand, configPath);
|
|
436
445
|
const textAfterMigration = migration.text;
|
|
437
446
|
const current = parseTomlOrThrow(textAfterMigration, configPath, "after legacy hook removal");
|
|
438
|
-
const
|
|
447
|
+
const hasPreToolCommand = tomlHasCommand(current, command);
|
|
448
|
+
const hasCommand = tomlHasDrainCommand(current, command);
|
|
439
449
|
const featureOn = tomlHooksFeatureEnabled(current);
|
|
440
450
|
if (hasCommand && featureOn && !migration.changed) {
|
|
441
451
|
return { text: original, changed: false };
|
|
@@ -443,7 +453,7 @@ export function mergeCodexConfigHook(existingText, command, configPath, legacyCo
|
|
|
443
453
|
let text = textAfterMigration;
|
|
444
454
|
if (!featureOn)
|
|
445
455
|
text = enableTomlHooksFeature(text);
|
|
446
|
-
if (!
|
|
456
|
+
if (!hasPreToolCommand) {
|
|
447
457
|
const sep = text === "" || text.endsWith("\n") ? "" : "\n";
|
|
448
458
|
text =
|
|
449
459
|
`${text}${sep}\n` +
|
|
@@ -455,8 +465,18 @@ export function mergeCodexConfigHook(existingText, command, configPath, legacyCo
|
|
|
455
465
|
`command = ${tomlBasicString(command)}\n` +
|
|
456
466
|
`timeout = ${DRAIN_HOOK_TIMEOUT_SECONDS}\n`;
|
|
457
467
|
}
|
|
468
|
+
for (const event of ["PostToolUse", "Stop"]) {
|
|
469
|
+
if (isPlainObject(current.hooks) && groupsContainCommand(current.hooks[event], command))
|
|
470
|
+
continue;
|
|
471
|
+
const sep = text === "" || text.endsWith("\n") ? "" : "\n";
|
|
472
|
+
text += `${sep}\n# hq lanes: drop-box delivery\n[[hooks.${event}]]\nmatcher = ".*"\n\n[[hooks.${event}.hooks]]\ntype = "command"\ncommand = ${tomlBasicString(command)}\ntimeout = ${DRAIN_HOOK_TIMEOUT_SECONDS}\n`;
|
|
473
|
+
}
|
|
458
474
|
const check = parseTomlOrThrow(text, configPath, "after merge");
|
|
459
|
-
if (!tomlHasCommand(check, command) ||
|
|
475
|
+
if (!tomlHasCommand(check, command) ||
|
|
476
|
+
!isPlainObject(check.hooks) ||
|
|
477
|
+
!groupsContainCommand(check.hooks.PostToolUse, command) ||
|
|
478
|
+
!groupsContainCommand(check.hooks.Stop, command) ||
|
|
479
|
+
!tomlHooksFeatureEnabled(check)) {
|
|
460
480
|
throw new LanesError("config_toml_merge_failed", `Merged ${configPath} does not carry the drain registration; refusing to write it`, { path: configPath, command });
|
|
461
481
|
}
|
|
462
482
|
return { text, changed: true };
|
|
@@ -479,7 +499,7 @@ export function probeDrainHookRegistration(provider, providerHome) {
|
|
|
479
499
|
const parsed = parseLaneProvider(provider);
|
|
480
500
|
const kind = PROVIDER_REGISTRATION_KIND[parsed];
|
|
481
501
|
const regPath = drainHookRegistrationPath(parsed, providerHome);
|
|
482
|
-
const command = drainHookCommand(drainHookShPath(providerHome));
|
|
502
|
+
const command = drainHookCommand(drainHookShPath(providerHome), parsed);
|
|
483
503
|
const text = readTextIfExists(regPath);
|
|
484
504
|
const base = { kind, path: regPath, command, file_present: text !== null };
|
|
485
505
|
if (text === null)
|
|
@@ -493,11 +513,11 @@ export function probeDrainHookRegistration(provider, providerHome) {
|
|
|
493
513
|
};
|
|
494
514
|
}
|
|
495
515
|
const json = JSON.parse(text);
|
|
516
|
+
const hooks = isPlainObject(json) && isPlainObject(json.hooks) ? json.hooks : null;
|
|
496
517
|
return {
|
|
497
518
|
...base,
|
|
498
|
-
registered:
|
|
499
|
-
|
|
500
|
-
groupsContainDrainCommand(json.hooks.PreToolUse, command),
|
|
519
|
+
registered: hooks !== null &&
|
|
520
|
+
(parsed === "grok" ? ["PreToolUse"] : ["PreToolUse", "PostToolUse", "Stop"]).every((event) => groupsContainDrainCommand(hooks[event], command)),
|
|
501
521
|
};
|
|
502
522
|
}
|
|
503
523
|
catch (err) {
|
|
@@ -508,9 +528,9 @@ export function probeDrainHookRegistration(provider, providerHome) {
|
|
|
508
528
|
function registerDrainHook(provider, providerHome, shPath) {
|
|
509
529
|
const kind = PROVIDER_REGISTRATION_KIND[provider];
|
|
510
530
|
const regPath = drainHookRegistrationPath(provider, providerHome);
|
|
511
|
-
const command = drainHookCommand(shPath);
|
|
531
|
+
const command = drainHookCommand(shPath, provider);
|
|
512
532
|
if (kind === "hooks_dir") {
|
|
513
|
-
const changed = writeIfChanged(regPath, renderDrainHookJson(shPath), 0o644);
|
|
533
|
+
const changed = writeIfChanged(regPath, renderDrainHookJson(shPath, provider), 0o644);
|
|
514
534
|
return { kind, path: regPath, command, changed };
|
|
515
535
|
}
|
|
516
536
|
const existing = readTextIfExists(regPath);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { LaneRecord, LaneProvider } from "./types.js";
|
|
2
|
+
export declare const LOOP_SCHEMA: "hq-lane-loop.v1";
|
|
3
|
+
export declare const PHASE_ENVELOPE_SCHEMA: "hq-phase-envelope/v1";
|
|
4
|
+
export declare const PHASE_HANDOFF_SCHEMA: "hq-phase-handoff/v1";
|
|
5
|
+
export declare const LOOP_ENVELOPE_LIMIT: number;
|
|
6
|
+
export type GenericPhaseEnvelope = {
|
|
7
|
+
schema_version?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
kind: "phase" | "stop";
|
|
10
|
+
story_id?: string;
|
|
11
|
+
phase?: string;
|
|
12
|
+
engine?: LaneProvider;
|
|
13
|
+
prompt?: string;
|
|
14
|
+
result_path?: string;
|
|
15
|
+
deadline_seconds?: number;
|
|
16
|
+
deadline?: string;
|
|
17
|
+
fresh?: boolean;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
export type Section8PhaseEnvelope = {
|
|
21
|
+
schema: typeof PHASE_ENVELOPE_SCHEMA;
|
|
22
|
+
id?: string;
|
|
23
|
+
story_id: string;
|
|
24
|
+
phase: string;
|
|
25
|
+
engine?: LaneProvider;
|
|
26
|
+
worktree: string;
|
|
27
|
+
fresh_call?: boolean;
|
|
28
|
+
result_path?: string;
|
|
29
|
+
deadline_seconds?: number;
|
|
30
|
+
deadline?: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
};
|
|
33
|
+
export type LoopEnvelope = GenericPhaseEnvelope | Section8PhaseEnvelope;
|
|
34
|
+
export interface LoopQueuePosition {
|
|
35
|
+
lane_id: string;
|
|
36
|
+
envelope_id: string;
|
|
37
|
+
position: number;
|
|
38
|
+
queue_depth: number;
|
|
39
|
+
queued_at: string;
|
|
40
|
+
}
|
|
41
|
+
export interface LoopStatus {
|
|
42
|
+
mode: "loop";
|
|
43
|
+
lane_id: string;
|
|
44
|
+
state: "waiting" | "running" | "parked" | "stopped" | "failed";
|
|
45
|
+
queue_depth: number;
|
|
46
|
+
active_envelope_id: string | null;
|
|
47
|
+
active_phase: string | null;
|
|
48
|
+
stall_counts: Record<string, number>;
|
|
49
|
+
pid?: number;
|
|
50
|
+
updated_at: string;
|
|
51
|
+
}
|
|
52
|
+
export interface LoopResult {
|
|
53
|
+
schema_version: typeof LOOP_SCHEMA;
|
|
54
|
+
envelope_id: string;
|
|
55
|
+
status: "done" | "failed" | "stalled" | "stopped";
|
|
56
|
+
started_at: string;
|
|
57
|
+
finished_at: string;
|
|
58
|
+
output?: string;
|
|
59
|
+
error?: string;
|
|
60
|
+
session_id?: string;
|
|
61
|
+
resumed?: boolean;
|
|
62
|
+
fresh_fallback?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface Section8Handoff {
|
|
65
|
+
schema: typeof PHASE_HANDOFF_SCHEMA;
|
|
66
|
+
story_id: string;
|
|
67
|
+
phase: string;
|
|
68
|
+
worker_id: string;
|
|
69
|
+
status: "passed" | "failed" | "blocked";
|
|
70
|
+
summary: string;
|
|
71
|
+
files_changed: string[];
|
|
72
|
+
commits: string[];
|
|
73
|
+
back_pressure: {
|
|
74
|
+
tests: "pass" | "fail" | "skip";
|
|
75
|
+
lint: "pass" | "fail" | "skip";
|
|
76
|
+
typecheck: "pass" | "fail" | "skip";
|
|
77
|
+
build: "pass" | "fail" | "skip";
|
|
78
|
+
};
|
|
79
|
+
context_for_next: string;
|
|
80
|
+
engine?: string;
|
|
81
|
+
notes?: string;
|
|
82
|
+
[key: string]: unknown;
|
|
83
|
+
}
|
|
84
|
+
export interface LoopExecutionContext {
|
|
85
|
+
lane: LaneRecord;
|
|
86
|
+
envelope: LoopEnvelope;
|
|
87
|
+
envelopeId: string;
|
|
88
|
+
signal: AbortSignal;
|
|
89
|
+
sessionId?: string;
|
|
90
|
+
fresh: boolean;
|
|
91
|
+
}
|
|
92
|
+
export interface LoopExecutionResult {
|
|
93
|
+
output: string;
|
|
94
|
+
sessionId?: string;
|
|
95
|
+
}
|
|
96
|
+
export interface LoopRunnerOptions {
|
|
97
|
+
lane: LaneRecord;
|
|
98
|
+
runDir: string;
|
|
99
|
+
execute(context: LoopExecutionContext): Promise<LoopExecutionResult>;
|
|
100
|
+
canResume?(provider: LaneProvider): boolean;
|
|
101
|
+
raiseQuestion?(input: {
|
|
102
|
+
envelope: LoopEnvelope;
|
|
103
|
+
envelopeId: string;
|
|
104
|
+
attempt: number;
|
|
105
|
+
error: string;
|
|
106
|
+
}): Promise<void> | void;
|
|
107
|
+
defaultDeadlineSeconds?: number;
|
|
108
|
+
now?: () => Date;
|
|
109
|
+
idlePollMs?: number;
|
|
110
|
+
signal?: AbortSignal;
|
|
111
|
+
}
|
|
112
|
+
export interface LoopRunSummary {
|
|
113
|
+
state: "stopped" | "parked" | "aborted";
|
|
114
|
+
completed: number;
|
|
115
|
+
}
|
|
116
|
+
export type LoopAdmissionPath = "initial" | "deferred" | "resume";
|
|
117
|
+
export declare function loopAdmissionPath(state: string, hasPendingResume: boolean, initialAdmission: boolean): LoopAdmissionPath;
|
|
118
|
+
interface StoredEnvelope {
|
|
119
|
+
schema_version: typeof LOOP_SCHEMA;
|
|
120
|
+
envelope_id: string;
|
|
121
|
+
queued_at: string;
|
|
122
|
+
deadline_budget_seconds?: number;
|
|
123
|
+
deadline_at_ms?: number;
|
|
124
|
+
envelope: LoopEnvelope;
|
|
125
|
+
}
|
|
126
|
+
/** Validate both hq-cli phase envelopes and the conduct protocol §8 shape. */
|
|
127
|
+
export declare function validateLoopEnvelope(value: unknown): LoopEnvelope;
|
|
128
|
+
export declare function validateSection8Handoff(value: unknown, envelope: Section8PhaseEnvelope): Section8Handoff;
|
|
129
|
+
/** Queue depth and FIFO ordering are based on the timestamp-prefixed filename. */
|
|
130
|
+
export declare function listLoopQueue(runDir: string): StoredEnvelope[];
|
|
131
|
+
/**
|
|
132
|
+
* Copy and enqueue atomically. Unique timestamp/ULID-like names establish FIFO;
|
|
133
|
+
* temporary files are ignored by claimers until rename completes.
|
|
134
|
+
*/
|
|
135
|
+
export declare function enqueueLoopEnvelope(input: {
|
|
136
|
+
runDir: string;
|
|
137
|
+
laneId: string;
|
|
138
|
+
envelope: unknown;
|
|
139
|
+
now?: () => Date;
|
|
140
|
+
}): LoopQueuePosition;
|
|
141
|
+
/** Atomically claims exactly the FIFO head. The active rename is the claim. */
|
|
142
|
+
export declare function claimNextLoopEnvelope(runDir: string): {
|
|
143
|
+
file: string;
|
|
144
|
+
record: StoredEnvelope;
|
|
145
|
+
} | null;
|
|
146
|
+
/** Withdraw all unclaimed envelopes; active work remains claimed by the worker. */
|
|
147
|
+
export declare function interruptLoopQueue(runDir: string): {
|
|
148
|
+
withdrawn: string[];
|
|
149
|
+
already_picked_up: string[];
|
|
150
|
+
};
|
|
151
|
+
export declare function loopStatus(runDir: string, laneId: string): LoopStatus;
|
|
152
|
+
export declare function setLoopControllerPid(runDir: string, laneId: string, pid: number | undefined): void;
|
|
153
|
+
export declare function clearLoopControllerPid(runDir: string, laneId: string, pid: number): void;
|
|
154
|
+
export declare function loopControllerIsAlive(runDir: string): boolean;
|
|
155
|
+
/** Run queued phases serially. Sessions are isolated by story and provider. */
|
|
156
|
+
export declare function runLoopLane(options: LoopRunnerOptions): Promise<LoopRunSummary>;
|
|
157
|
+
export {};
|
|
158
|
+
//# sourceMappingURL=loop.d.ts.map
|