@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.
Files changed (66) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/assets/scaffold/core/scripts/derive-trigger-facts.sh +10 -1
  3. package/assets/scaffold/core/scripts/jobs-validate.sh +2 -1
  4. package/assets/scaffold/core/scripts/session-title.sh +18 -15
  5. package/dist/command-catalog.generated.d.ts +124 -0
  6. package/dist/command-catalog.generated.js +155 -0
  7. package/dist/commands/core-delegate-w1b.js +18 -5
  8. package/dist/commands/core-derive-trigger-facts-command.js +14 -0
  9. package/dist/commands/core-derive-trigger-facts-native.d.ts +1 -0
  10. package/dist/commands/core-derive-trigger-facts-native.js +20 -1
  11. package/dist/commands/doctor.d.ts +1 -0
  12. package/dist/commands/doctor.js +15 -2
  13. package/dist/commands/lanes-checks.d.ts +3 -0
  14. package/dist/commands/lanes-checks.js +34 -0
  15. package/dist/commands/lanes-loop.d.ts +12 -0
  16. package/dist/commands/lanes-loop.js +214 -0
  17. package/dist/commands/lanes-questions.js +37 -11
  18. package/dist/commands/lanes-resume.js +19 -1
  19. package/dist/commands/lanes-session-start.d.ts +2 -7
  20. package/dist/commands/lanes-session-start.js +9 -29
  21. package/dist/commands/lanes-shared.d.ts +14 -2
  22. package/dist/commands/lanes.d.ts +3 -1
  23. package/dist/commands/lanes.js +376 -35
  24. package/dist/commands/members.d.ts +4 -0
  25. package/dist/commands/members.js +23 -2
  26. package/dist/lanes-drain-hook.js +6 -2
  27. package/dist/lib/doctor/checks/sync-health.d.ts +2 -0
  28. package/dist/lib/doctor/checks/sync-health.js +47 -23
  29. package/dist/lib/lanes/delivery.d.ts +3 -0
  30. package/dist/lib/lanes/delivery.js +5 -0
  31. package/dist/lib/lanes/dropbox.d.ts +7 -2
  32. package/dist/lib/lanes/dropbox.js +157 -10
  33. package/dist/lib/lanes/flag-keys.d.ts +12 -0
  34. package/dist/lib/lanes/flag-keys.js +12 -0
  35. package/dist/lib/lanes/hooks-install.d.ts +14 -11
  36. package/dist/lib/lanes/hooks-install.js +70 -50
  37. package/dist/lib/lanes/loop.d.ts +158 -0
  38. package/dist/lib/lanes/loop.js +743 -0
  39. package/dist/lib/lanes/outpost-dispatch.d.ts +9 -1
  40. package/dist/lib/lanes/outpost-dispatch.js +21 -7
  41. package/dist/lib/lanes/reaper.d.ts +58 -0
  42. package/dist/lib/lanes/reaper.js +403 -0
  43. package/dist/lib/lanes/reconcile.d.ts +19 -0
  44. package/dist/lib/lanes/reconcile.js +173 -6
  45. package/dist/lib/lanes/spawn.d.ts +4 -0
  46. package/dist/lib/lanes/spawn.js +41 -9
  47. package/dist/lib/lanes/status-fields.d.ts +12 -0
  48. package/dist/lib/lanes/status-fields.js +189 -0
  49. package/dist/lib/lanes/store.js +49 -2
  50. package/dist/lib/lanes/types.d.ts +18 -0
  51. package/dist/lib/lanes/types.js +3 -3
  52. package/dist/lib/lanes/watcher.js +3 -0
  53. package/dist/lib/lanes/worker-continuity.d.ts +18 -0
  54. package/dist/lib/lanes/worker-continuity.js +81 -0
  55. package/dist/lib/lanes/worker-process.d.ts +4 -0
  56. package/dist/lib/lanes/worker-process.js +77 -0
  57. package/dist/main.js +9 -3
  58. package/dist/utils/cli-telemetry.d.ts +4 -1
  59. package/dist/utils/cli-telemetry.js +8 -0
  60. package/dist/utils/jq-windows-compat.d.ts +13 -0
  61. package/dist/utils/jq-windows-compat.js +48 -0
  62. package/dist/utils/run-bundled-script.d.ts +3 -0
  63. package/dist/utils/run-bundled-script.js +9 -80
  64. package/dist/utils/windows-bash.d.ts +5 -0
  65. package/dist/utils/windows-bash.js +75 -0
  66. 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 the `hooks.PreToolUse` array of
12
- * that file.
13
- * - codex reads `$CODEX_HOME/config.toml` (`[[hooks.PreToolUse]]`, gated by
14
- * `[features] hooks = true`). The registration is appended to that file.
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
- * Every registration merge keeps what the operator already has and is a
17
- * no-op when the entry is present, because this runs on every spawn.
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 as one `[[hooks.PreToolUse]]` table and `[features] hooks = true`
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 the registration,
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 the `hooks.PreToolUse` array of
12
- * that file.
13
- * - codex reads `$CODEX_HOME/config.toml` (`[[hooks.PreToolUse]]`, gated by
14
- * `[features] hooks = true`). The registration is appended to that file.
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
- * Every registration merge keeps what the operator already has and is a
17
- * no-op when the entry is present, because this runs on every spawn.
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
- return `${shellQuote(process.execPath)} ${shellQuote(drainHookEntryPath())}`;
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 argv = parseShellQuotedArguments(candidate);
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
- return isPlainObject(parsed.hooks) && groupsContainDrainCommand(parsed.hooks.PreToolUse, expected);
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
- return `${JSON.stringify({
192
- hooks: {
193
- PreToolUse: [
194
- {
195
- hooks: [
196
- {
197
- type: "command",
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 pre = hooks.PreToolUse === undefined ? [] : hooks.PreToolUse;
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
- if (groupsContainCommand(migration.groups, command) && !migration.changed) {
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 nextPre = groupsContainCommand(migration.groups, command)
241
- ? migration.groups
242
- : [
243
- ...migration.groups,
244
- {
245
- hooks: [
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
- const nextRoot = { ...root, hooks: { ...hooks, PreToolUse: nextPre } };
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 as one `[[hooks.PreToolUse]]` table and `[features] hooks = true`
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 the registration,
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 hasCommand = tomlHasCommand(current, command);
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 (!hasCommand) {
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) || !tomlHooksFeatureEnabled(check)) {
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: isPlainObject(json) &&
499
- isPlainObject(json.hooks) &&
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