@indigoai-us/hq-cli 5.345.7 → 5.345.8

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 (37) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/commands/__fixtures__/make-tar.d.ts +2 -10
  3. package/dist/commands/__fixtures__/make-tar.js +2 -2
  4. package/dist/commands/agent-enroll.d.ts +2 -3
  5. package/dist/commands/agent-enroll.js +2 -2
  6. package/dist/commands/mcp-status.d.ts +4 -3
  7. package/dist/commands/reindex.d.ts +0 -6
  8. package/dist/commands/reindex.js +1 -1
  9. package/dist/commands/safe-extract.d.ts +2 -17
  10. package/dist/commands/safe-extract.js +2 -2
  11. package/dist/commands/search.d.ts +2 -1
  12. package/dist/lazy-commands.d.ts +0 -6
  13. package/dist/lazy-commands.js +1 -1
  14. package/dist/lib/daemon/supervisor.js +2 -1
  15. package/dist/lib/doctor/__testing__/fake-hq-tree.d.ts +8 -9
  16. package/dist/lib/doctor/__testing__/fake-hq-tree.js +1 -1
  17. package/dist/lib/doctor/checks/runtime-probe.d.ts +0 -21
  18. package/dist/lib/doctor/checks/runtime-probe.js +4 -4
  19. package/dist/lib/doctor/checks/sync-health.d.ts +2 -3
  20. package/dist/lib/doctor/checks/sync-health.js +1 -1
  21. package/dist/lib/doctor/compat.d.ts +2 -1
  22. package/dist/lib/doctor/deep/classify.d.ts +2 -1
  23. package/dist/lib/doctor/fix/backup.d.ts +0 -8
  24. package/dist/lib/doctor/fix/backup.js +2 -2
  25. package/dist/lib/doctor/fixtures/discover.d.ts +2 -10
  26. package/dist/lib/doctor/fixtures/discover.js +1 -1
  27. package/dist/lib/lanes/autopilot.d.ts +2 -17
  28. package/dist/lib/lanes/autopilot.js +8 -8
  29. package/dist/lib/lanes/flag-keys.d.ts +2 -0
  30. package/dist/lib/lanes/flag-keys.js +2 -0
  31. package/dist/lib/lanes/keep-alive.d.ts +2 -0
  32. package/dist/lib/lanes/keep-alive.js +9 -0
  33. package/dist/lib/lanes/reconcile.js +44 -27
  34. package/dist/lib/lanes/tracker-report.d.ts +16 -15
  35. package/dist/lib/lanes/wait.d.ts +2 -5
  36. package/dist/lib/lanes/wait.js +3 -3
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.8] — 2026-10-05
6
+ - Prioritize due lead keep-alive retries and honor due next-check times ahead of later backoff deadlines behind an hq-flags gate.
7
+ - Drain bot turns before replacing a running bot after a daemon plan change.
8
+
5
9
  ## [5.345.7] — 2026-10-05
6
10
  - Retry a just-published hq-cli version when npm briefly reports `notarget`.
7
11
  - Wait for an active sync pass to finish before restarting sync after a CLI update.
@@ -22,7 +22,7 @@
22
22
  * lists whatever bytes we wrote, so the guard sees the real attack everywhere.
23
23
  */
24
24
  /** ustar typeflag values we use. */
25
- export type TarType = 'file' | 'symlink' | 'hardlink';
25
+ type TarType = 'file' | 'symlink' | 'hardlink';
26
26
  export interface TarEntrySpec {
27
27
  /** Recorded entry name — written VERBATIM (may contain `..`, be absolute…). */
28
28
  name: string;
@@ -32,15 +32,7 @@ export interface TarEntrySpec {
32
32
  /** File content (regular files only). */
33
33
  content?: string | Buffer;
34
34
  }
35
- /**
36
- * Build one 512-byte ustar header (plus content blocks for regular files) for a
37
- * single entry. The header checksum is computed exactly per spec: sum every
38
- * header byte treating the 8 checksum bytes themselves as ASCII spaces, then
39
- * write that sum as 6 octal digits + NUL + space.
40
- */
41
- export declare function makeTarEntry(spec: TarEntrySpec): Buffer;
42
- /** Concatenate entries and append the two zero blocks that end every tar. */
43
- export declare function makeTar(entries: TarEntrySpec[]): Buffer;
44
35
  /** Build the tar, gzip it, and write it to `outPath`. Returns `outPath`. */
45
36
  export declare function writeMaliciousTarGz(outPath: string, entries: TarEntrySpec[]): string;
37
+ export {};
46
38
  //# sourceMappingURL=make-tar.d.ts.map
@@ -50,7 +50,7 @@ function writeOctal(buf, offset, width, value) {
50
50
  * header byte treating the 8 checksum bytes themselves as ASCII spaces, then
51
51
  * write that sum as 6 octal digits + NUL + space.
52
52
  */
53
- export function makeTarEntry(spec) {
53
+ function makeTarEntry(spec) {
54
54
  const type = spec.type ?? 'file';
55
55
  const content = type === 'file'
56
56
  ? Buffer.isBuffer(spec.content)
@@ -89,7 +89,7 @@ export function makeTarEntry(spec) {
89
89
  return Buffer.concat([header, content, Buffer.alloc(pad)]);
90
90
  }
91
91
  /** Concatenate entries and append the two zero blocks that end every tar. */
92
- export function makeTar(entries) {
92
+ function makeTar(entries) {
93
93
  const blocks = entries.map(makeTarEntry);
94
94
  blocks.push(Buffer.alloc(BLOCK * 2)); // end-of-archive marker
95
95
  return Buffer.concat(blocks);
@@ -17,18 +17,16 @@
17
17
  import { Command } from "commander";
18
18
  import { generateHostKeyPair } from "../lib/agent-kit/creds.js";
19
19
  import { type AgentKitPaths } from "../lib/agent-kit/paths.js";
20
- export declare const ENROLL_PATH = "/v1/agents/enroll";
21
20
  /** Canonical code: un-grouped, uppercase (server hashes exactly this). */
22
21
  export declare function normalizeEnrollmentCode(raw: string): string;
23
22
  /** 24 chars of Crockford base32 (no I, L, O, U). */
24
23
  export declare function isWellFormedEnrollmentCode(code: string): boolean;
25
- export interface HostInfo {
24
+ interface HostInfo {
26
25
  os: string;
27
26
  arch: string;
28
27
  hostname: string;
29
28
  cliVersion: string;
30
29
  }
31
- export declare function collectHostInfo(): HostInfo;
32
30
  export type ExistingIdentity = {
33
31
  kind: "none";
34
32
  } | {
@@ -143,4 +141,5 @@ export interface EnrollResult {
143
141
  /** Pure-ish orchestration so tests can drive it without a TTY or network. */
144
142
  export declare function enrollHost(opts: EnrollOptions, deps?: EnrollDeps): Promise<EnrollResult>;
145
143
  export declare function registerAgentEnrollCommand(agent: Command): void;
144
+ export {};
146
145
  //# sourceMappingURL=agent-enroll.d.ts.map
@@ -25,7 +25,7 @@ import { networkTransportErrorCode } from "../utils/network-transport-error.js";
25
25
  import { canOfferTeamUpgrade, formatUpgradeRefusal, isPlanGateError, offerUpgradeLink, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
26
26
  import { generateHostKeyPair, machineCredsFileExists, readExternalMachineCreds, writeHostKeyPair, writeMachineCreds, } from "../lib/agent-kit/creds.js";
27
27
  import { DEFAULT_LOCAL_AGENT_NAME, agentKitPaths, localAgentDir, } from "../lib/agent-kit/paths.js";
28
- export const ENROLL_PATH = "/v1/agents/enroll";
28
+ const ENROLL_PATH = "/v1/agents/enroll";
29
29
  /** Canonical code: un-grouped, uppercase (server hashes exactly this). */
30
30
  export function normalizeEnrollmentCode(raw) {
31
31
  return raw.replace(/[\s-]+/g, "").toUpperCase();
@@ -34,7 +34,7 @@ export function normalizeEnrollmentCode(raw) {
34
34
  export function isWellFormedEnrollmentCode(code) {
35
35
  return /^[0-9A-HJKMNP-TV-Z]{24}$/.test(code);
36
36
  }
37
- export function collectHostInfo() {
37
+ function collectHostInfo() {
38
38
  return {
39
39
  os: `${os.platform()} ${os.release()}`,
40
40
  arch: os.arch(),
@@ -1,7 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { type SafeWriteEnv } from './mcp-registration.js';
3
3
  /** A server's presence on ONE runtime surface. */
4
- export type PresenceState =
4
+ type PresenceState =
5
5
  /** The server def is present (and parsed) on this runtime. */
6
6
  'present'
7
7
  /** The runtime parsed fine but this server is not registered there. */
@@ -30,7 +30,7 @@ export interface RuntimeServerStatus {
30
30
  healthy?: boolean;
31
31
  }
32
32
  /** A single HQ-owned MCP server's cross-runtime status. */
33
- export interface McpServerStatus {
33
+ interface McpServerStatus {
34
34
  /** The bare server name (the `mcpServers` / `mcp_servers` key). */
35
35
  server: string;
36
36
  /** The pack that owns it (`_hqPack` provenance). */
@@ -59,7 +59,7 @@ export interface McpServerStatus {
59
59
  drifted: boolean;
60
60
  }
61
61
  /** A runtime's top-level status. */
62
- export interface RuntimeStatus {
62
+ interface RuntimeStatus {
63
63
  /** Whether the runtime is installed (Claude is always; Codex iff `~/.codex` exists). */
64
64
  installed: boolean;
65
65
  /** Whether the runtime's config parsed (false => unparseable). */
@@ -133,4 +133,5 @@ export declare function renderMcpCapabilities(caps: McpCapabilities): string;
133
133
  * {@link registerReindexCommand} and the subcommand-group pattern in index.ts.
134
134
  */
135
135
  export declare function registerMcpCommand(program: Command): void;
136
+ export {};
136
137
  //# sourceMappingURL=mcp-status.d.ts.map
@@ -5,12 +5,6 @@ import { Command } from 'commander';
5
5
  * layout scan and Git's status output rather than recursively walking trees.
6
6
  */
7
7
  export declare function removeStaleHqWorktrees(hqRoot: string, now?: number, deadline?: number): void;
8
- /**
9
- * Check hook health without relying on lifecycle hooks. A fully disabled
10
- * configuration is repaired only after a successful reindex; partial and
11
- * malformed configurations remain untouched and receive recovery guidance.
12
- */
13
- export declare function repairExtremeHookDrift(hqRoot: string, allowRepair?: boolean): void;
14
8
  /**
15
9
  * Stop the generated project settings from silently downgrading an operator who
16
10
  * chose Bypass mode globally.
@@ -491,7 +491,7 @@ function runSilently(operation) {
491
491
  * configuration is repaired only after a successful reindex; partial and
492
492
  * malformed configurations remain untouched and receive recovery guidance.
493
493
  */
494
- export function repairExtremeHookDrift(hqRoot, allowRepair = true) {
494
+ function repairExtremeHookDrift(hqRoot, allowRepair = true) {
495
495
  // `hq reindex` can still be invoked from an arbitrary directory. Never turn
496
496
  // an absent settings file there into a rescue attempt.
497
497
  if (!isHqRoot(hqRoot))
@@ -45,7 +45,7 @@ export declare const DEFAULT_CAPS: ExtractCaps;
45
45
  export declare class UnsafeArchiveError extends Error {
46
46
  constructor(message: string);
47
47
  }
48
- export type EntryType = 'file' | 'dir' | 'symlink' | 'hardlink' | 'other';
48
+ type EntryType = 'file' | 'dir' | 'symlink' | 'hardlink' | 'other';
49
49
  export interface TarEntry {
50
50
  type: EntryType;
51
51
  /** Entry path as recorded in the archive (may contain `..`, be absolute…). */
@@ -108,14 +108,6 @@ export declare function resolveContainedPath(targetDir: string, entryName: strin
108
108
  * way the final location must be contained in the pack dir.
109
109
  */
110
110
  export declare function assertLinkTargetContained(packDir: string, entryAbs: string, linkTarget: string): void;
111
- /**
112
- * List a gzip-compressed tarball's entries via `tar -tvf` (no extraction).
113
- * argv form, never a shell string, keeps hostile filenames out of shell syntax.
114
- * Windows drive paths always use a basename relative to the archive directory.
115
- * GNU tar cannot parse `C:\\...` as remote `host:path`, and Windows' bundled
116
- * bsdtar accepts the same relative argv without GNU-only `--force-local`.
117
- */
118
- export declare function listTarball(tarballPath: string, opts?: Pick<SafeExtractOptions, 'platform' | 'execFileSync'>): TarEntry[];
119
111
  /**
120
112
  * Pre-flight validation against the entry headers. Enforces (a) containment of
121
113
  * every entry name under `targetDir`, (b) containment of every link target
@@ -123,14 +115,6 @@ export declare function listTarball(tarballPath: string, opts?: Pick<SafeExtract
123
115
  * UnsafeArchiveError on the first violation — default-deny.
124
116
  */
125
117
  export declare function validateEntries(entries: TarEntry[], targetDir: string, caps?: ExtractCaps): void;
126
- /**
127
- * Walk everything actually written under `stagingDir` and assert that no entry
128
- * — and, for symlinks, no link target — escapes. This is the defense-in-depth
129
- * backstop: it does not trust the pre-flight scan OR the tar binary. Uses
130
- * realpath on the link's PARENT (which exists) plus lstat to inspect links
131
- * without following them off-tree.
132
- */
133
- export declare function assertExtractContained(stagingDir: string): void;
134
118
  export interface SafeExtractOptions {
135
119
  caps?: ExtractCaps;
136
120
  /** Test seam for platform-specific argv construction. */
@@ -158,4 +142,5 @@ export interface SafeExtractOptions {
158
142
  * so a half-extracted pack can never be wired.
159
143
  */
160
144
  export declare function safeExtractTarball(tarballPath: string, finalDir: string, opts?: SafeExtractOptions): void;
145
+ export {};
161
146
  //# sourceMappingURL=safe-extract.d.ts.map
@@ -219,7 +219,7 @@ function tarballArgvLocation(tarballPath, platform) {
219
219
  * GNU tar cannot parse `C:\\...` as remote `host:path`, and Windows' bundled
220
220
  * bsdtar accepts the same relative argv without GNU-only `--force-local`.
221
221
  */
222
- export function listTarball(tarballPath, opts = {}) {
222
+ function listTarball(tarballPath, opts = {}) {
223
223
  const platform = opts.platform ?? process.platform;
224
224
  const location = tarballArgvLocation(tarballPath, platform);
225
225
  const out = (opts.execFileSync ?? execFileSync)('tar', ['-tvf', location.archiveArg], {
@@ -277,7 +277,7 @@ export function validateEntries(entries, targetDir, caps = DEFAULT_CAPS) {
277
277
  * realpath on the link's PARENT (which exists) plus lstat to inspect links
278
278
  * without following them off-tree.
279
279
  */
280
- export function assertExtractContained(stagingDir) {
280
+ function assertExtractContained(stagingDir) {
281
281
  const realRoot = fs.realpathSync(stagingDir);
282
282
  const walk = (dir) => {
283
283
  for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- export type SearchMode = 'keyword' | 'semantic' | 'hybrid';
2
+ type SearchMode = 'keyword' | 'semantic' | 'hybrid';
3
3
  export type SearchOptions = {
4
4
  mode?: SearchMode;
5
5
  collection?: string;
@@ -32,4 +32,5 @@ export declare function normalizeQueryForQmd(query: string): string;
32
32
  export declare function buildSearchArgs(query: string, options?: SearchOptions): string[];
33
33
  export declare function buildGetArgs(document: string, options?: Pick<SearchOptions, 'collection'>): string[];
34
34
  export declare function registerSearchCommand(program: Command): void;
35
+ export {};
35
36
  //# sourceMappingURL=search.d.ts.map
@@ -5,12 +5,6 @@ export type LazyCommand = {
5
5
  /** Registers every contributor for this root and no unrelated root. */
6
6
  register: (program: Command) => Promise<void>;
7
7
  };
8
- /**
9
- * One entry for every root, including groups that are assembled by several
10
- * registrar modules. The routing metadata itself is generated from the eager
11
- * command graph and checked in tests and the build.
12
- */
13
- export declare const LAZY_COMMANDS: readonly LazyCommand[];
14
8
  /**
15
9
  * Keep the registration seam at register-all.js. Besides making one place own
16
10
  * the graph, this lets the torn-install recovery wrap failures while an update
@@ -9,7 +9,7 @@ const commandCatalog = COMMAND_CATALOG;
9
9
  * registrar modules. The routing metadata itself is generated from the eager
10
10
  * command graph and checked in tests and the build.
11
11
  */
12
- export const LAZY_COMMANDS = commandCatalog.map((command) => ({
12
+ const LAZY_COMMANDS = commandCatalog.map((command) => ({
13
13
  name: command.name,
14
14
  register: async (program) => {
15
15
  const registered = await registerCommandRoot(program, command.name);
@@ -683,6 +683,7 @@ export function createSupervisor(deps) {
683
683
  existing.blockedReads = 0;
684
684
  const adopted = existing.adopted === true;
685
685
  existing.adopted = false;
686
+ const hadDrainSignal = existing.plan.drainSignal !== undefined;
686
687
  const key = planKey(plan, deps.cliVersion);
687
688
  const wasBlocked = existing.plan.blocked !== undefined;
688
689
  const changed = key !== existing.key;
@@ -746,7 +747,7 @@ export function createSupervisor(deps) {
746
747
  if (cliRestartInProgress && plan.service === "sync")
747
748
  terminate(existing);
748
749
  else
749
- terminate(existing, () => activate(existing));
750
+ terminate(existing, () => activate(existing), hadDrainSignal);
750
751
  }
751
752
  }
752
753
  slots = next;
@@ -25,7 +25,7 @@
25
25
  * which stands up a throwaway HQ tree with `mktemp -d` and stubbed hook scripts.
26
26
  */
27
27
  /** Claude/Codex lifecycle events a hook can be registered against. */
28
- export type HookEventName = "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "SessionStart" | "Stop" | "SubagentStop" | "PreCompact" | "Notification";
28
+ type HookEventName = "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "SessionStart" | "Stop" | "SubagentStop" | "PreCompact" | "Notification";
29
29
  /** The three `hook-gate.sh` allowlist profiles. */
30
30
  export type GateProfile = "minimal" | "standard" | "strict";
31
31
  /** Canonical ordering of the gate profiles. */
@@ -35,7 +35,7 @@ export declare const GATE_PROFILES: readonly GateProfile[];
35
35
  * canonical `.claude/hooks/` scripts; only Codex runs duplicated copies, so the
36
36
  * Codex mirror is the entire drift surface the doctor has to watch.
37
37
  */
38
- export interface FakeCodexMirrorSpec {
38
+ interface FakeCodexMirrorSpec {
39
39
  /** Whether the mirror file is written to `.codex/hooks/`. Default: true. */
40
40
  present?: boolean;
41
41
  /**
@@ -99,7 +99,7 @@ export interface FakeHookSpec {
99
99
  * written by default (declare a hook's `codex` spec explicitly, or set
100
100
  * `legacyHooksJson`, to model pre-adapter leftovers).
101
101
  */
102
- export interface FakeCodexAdapterSpec {
102
+ interface FakeCodexAdapterSpec {
103
103
  /** Events wired to the adapter in config.toml. Default: the shipped eight. */
104
104
  events?: string[];
105
105
  /** Whether the adapter script is written. Default: true. */
@@ -111,10 +111,8 @@ export interface FakeCodexAdapterSpec {
111
111
  /** Whether a leftover `.codex/hooks.json` is still written. Default: false. */
112
112
  legacyHooksJson?: boolean;
113
113
  }
114
- /** The lifecycle events the shipped `.codex/config.toml` wires. */
115
- export declare const ADAPTER_DEFAULT_EVENTS: readonly string[];
116
114
  /** Declares the `.grok/hooks/` state. */
117
- export interface FakeGrokSpec {
115
+ interface FakeGrokSpec {
118
116
  /** Whether the `.grok/hooks/` scaffold is created at all. Default: true. */
119
117
  present?: boolean;
120
118
  /** Whether `hq-grok-hook-adapter.sh` carries the executable bit. Default: true. */
@@ -147,7 +145,7 @@ export interface FakeHqTreeSpec {
147
145
  prefix?: string;
148
146
  }
149
147
  /** Manifest entry describing a materialised Codex mirror. */
150
- export interface FakeCodexManifestEntry {
148
+ interface FakeCodexManifestEntry {
151
149
  /** Absolute path where the mirror lives (whether or not it was written). */
152
150
  scriptPath: string;
153
151
  /** Whether the mirror file exists on disk. */
@@ -162,7 +160,7 @@ export interface FakeCodexManifestEntry {
162
160
  drifted: boolean;
163
161
  }
164
162
  /** Manifest entry describing a materialised hook across all platforms. */
165
- export interface FakeHookManifestEntry {
163
+ interface FakeHookManifestEntry {
166
164
  id: string;
167
165
  /** Absolute path of the Claude script (whether or not it was written). */
168
166
  scriptPath: string;
@@ -184,7 +182,7 @@ export interface FakeHookManifestEntry {
184
182
  codex: FakeCodexManifestEntry | null;
185
183
  }
186
184
  /** A structured description of everything {@link buildFakeHqTree} wrote. */
187
- export interface FakeHqTreeManifest {
185
+ interface FakeHqTreeManifest {
188
186
  root: string;
189
187
  claudeSettingsPath: string;
190
188
  claudeHooksDir: string;
@@ -224,4 +222,5 @@ export declare function trackedFakeHqTreeRoots(): string[];
224
222
  * when the process exits.
225
223
  */
226
224
  export declare function buildFakeHqTree(spec?: FakeHqTreeSpec): FakeHqTree;
225
+ export {};
227
226
  //# sourceMappingURL=fake-hq-tree.d.ts.map
@@ -39,7 +39,7 @@ const EXECUTABLE_MODE = 0o755;
39
39
  const NON_EXECUTABLE_MODE = 0o644;
40
40
  const DEFAULT_PREFIX = "hq-doctor-fake-";
41
41
  /** The lifecycle events the shipped `.codex/config.toml` wires. */
42
- export const ADAPTER_DEFAULT_EVENTS = [
42
+ const ADAPTER_DEFAULT_EVENTS = [
43
43
  "SessionStart",
44
44
  "UserPromptSubmit",
45
45
  "PreToolUse",
@@ -47,27 +47,6 @@ export declare const RUNTIME_PROBE_PREFIX = "hooks.runtime";
47
47
  * `check-hq-hooks.sh` (`LEDGER_DIR="$HQ_ROOT/workspace/orchestrator/policy-trigger-state"`).
48
48
  */
49
49
  export declare const POLICY_TRIGGER_LEDGER_RELPATH = "workspace/orchestrator/policy-trigger-state";
50
- /** The two lifecycle events `check-hq-hooks.sh` requires a command hook on. */
51
- export declare const REQUIRED_HOOK_EVENTS: readonly ["SessionStart", "PreToolUse"];
52
- /**
53
- * Default runtime marker path an agents-v2 (hq-fleet) box writes its runtime mode
54
- * to. Overridable via `HQ_RUNTIME_MARKER_FILE`, exactly as the shell reads
55
- * `${HQ_RUNTIME_MARKER_FILE:-/var/lib/hq-agent/runtime.json}`.
56
- */
57
- export declare const HQ_RUNTIME_MARKER_DEFAULT = "/var/lib/hq-agent/runtime.json";
58
- /**
59
- * Path (relative to the HQ root) of the on-box agents-v2 hook adapter. Its
60
- * presence under the tree is one of the two signals that the runtime is
61
- * agents-v2, mirroring `hq_runtime_mode` in `check-hq-hooks.sh`.
62
- */
63
- export declare const AGENTS_V2_ADAPTER_RELPATH = ".agents-v2-hooks/hq-agents-v2-hook-adapter.sh";
64
- /**
65
- * Default hours a policy-trigger ledger may age and still evidence a live
66
- * agents-v2 turn when no exact session id is given. Overridable via
67
- * `HQ_V2_LEDGER_MAX_AGE_HOURS`, matching the shell's
68
- * `HQ_V2_LEDGER_MAX_AGE_HOURS="${HQ_V2_LEDGER_MAX_AGE_HOURS:-24}"`.
69
- */
70
- export declare const HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT = 24;
71
50
  /** Whether the ledger was found. Mirrors the script's `present`/`missing`. */
72
51
  export type LedgerState = "present" | "missing";
73
52
  /** Options for {@link reproduceCheckHqHooks}. */
@@ -51,26 +51,26 @@ export const RUNTIME_PROBE_PREFIX = "hooks.runtime";
51
51
  */
52
52
  export const POLICY_TRIGGER_LEDGER_RELPATH = "workspace/orchestrator/policy-trigger-state";
53
53
  /** The two lifecycle events `check-hq-hooks.sh` requires a command hook on. */
54
- export const REQUIRED_HOOK_EVENTS = ["SessionStart", "PreToolUse"];
54
+ const REQUIRED_HOOK_EVENTS = ["SessionStart", "PreToolUse"];
55
55
  /**
56
56
  * Default runtime marker path an agents-v2 (hq-fleet) box writes its runtime mode
57
57
  * to. Overridable via `HQ_RUNTIME_MARKER_FILE`, exactly as the shell reads
58
58
  * `${HQ_RUNTIME_MARKER_FILE:-/var/lib/hq-agent/runtime.json}`.
59
59
  */
60
- export const HQ_RUNTIME_MARKER_DEFAULT = "/var/lib/hq-agent/runtime.json";
60
+ const HQ_RUNTIME_MARKER_DEFAULT = "/var/lib/hq-agent/runtime.json";
61
61
  /**
62
62
  * Path (relative to the HQ root) of the on-box agents-v2 hook adapter. Its
63
63
  * presence under the tree is one of the two signals that the runtime is
64
64
  * agents-v2, mirroring `hq_runtime_mode` in `check-hq-hooks.sh`.
65
65
  */
66
- export const AGENTS_V2_ADAPTER_RELPATH = ".agents-v2-hooks/hq-agents-v2-hook-adapter.sh";
66
+ const AGENTS_V2_ADAPTER_RELPATH = ".agents-v2-hooks/hq-agents-v2-hook-adapter.sh";
67
67
  /**
68
68
  * Default hours a policy-trigger ledger may age and still evidence a live
69
69
  * agents-v2 turn when no exact session id is given. Overridable via
70
70
  * `HQ_V2_LEDGER_MAX_AGE_HOURS`, matching the shell's
71
71
  * `HQ_V2_LEDGER_MAX_AGE_HOURS="${HQ_V2_LEDGER_MAX_AGE_HOURS:-24}"`.
72
72
  */
73
- export const HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT = 24;
73
+ const HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT = 24;
74
74
  /**
75
75
  * Faithfully reproduce `core/scripts/check-hq-hooks.sh --require-ledger` in
76
76
  * TypeScript. The checks, in the script's order:
@@ -26,8 +26,6 @@
26
26
  import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
27
27
  /** The id of the versions/sync family. */
28
28
  export declare const SYNC_FAMILY_ID = "sync";
29
- /** Human title for grouped output. */
30
- export declare const SYNC_FAMILY_TITLE = "Versions & sync";
31
29
  /**
32
30
  * A journal shard older than this is reported stale. Seven days: long enough
33
31
  * that a laptop shut over a weekend never warns, short enough that a silently
@@ -45,7 +43,7 @@ export interface SyncJournalSummary {
45
43
  };
46
44
  }
47
45
  /** The component versions this family reports. */
48
- export interface SyncVersionInfo {
46
+ interface SyncVersionInfo {
49
47
  cli: string;
50
48
  core: string | null;
51
49
  desktop: string | null;
@@ -128,4 +126,5 @@ export declare function semverGt(a: string, b: string): boolean;
128
126
  * something — the daemon, the network, the kill switch — is not running.
129
127
  */
130
128
  export declare const STALE_MANIFEST_THRESHOLD_MS: number;
129
+ export {};
131
130
  //# sourceMappingURL=sync-health.d.ts.map
@@ -38,7 +38,7 @@ import { readHqVersion } from "../../../utils/pack-contributions.js";
38
38
  /** The id of the versions/sync family. */
39
39
  export const SYNC_FAMILY_ID = "sync";
40
40
  /** Human title for grouped output. */
41
- export const SYNC_FAMILY_TITLE = "Versions & sync";
41
+ const SYNC_FAMILY_TITLE = "Versions & sync";
42
42
  /**
43
43
  * A journal shard older than this is reported stale. Seven days: long enough
44
44
  * that a laptop shut over a weekend never warns, short enough that a silently
@@ -36,7 +36,7 @@ export declare const CHECK_HQ_HOOKS_FAIL_EXIT = 2;
36
36
  * `HQ runtime enforcement: …` line. `NOT CHECKED` is the default when
37
37
  * `--require-ledger` was not passed (the wrapper prints "ledger: not checked").
38
38
  */
39
- export type RuntimeObservation = "OBSERVED" | "NOT OBSERVED" | "NOT CHECKED";
39
+ type RuntimeObservation = "OBSERVED" | "NOT OBSERVED" | "NOT CHECKED";
40
40
  /** The verdict the wrapper renders, derived from a scoped `hq doctor` run. */
41
41
  export interface CheckHqHooksVerdict {
42
42
  /** True iff the wrapper exits 0 (no in-scope failure, ledger observed when required). */
@@ -82,4 +82,5 @@ export interface DeriveVerdictOptions {
82
82
  * what lets `compat.test.ts` prove the wrapper and `hq doctor` agree.
83
83
  */
84
84
  export declare function deriveCheckHqHooksVerdict(doc: DoctorJsonDocument, options?: DeriveVerdictOptions): CheckHqHooksVerdict;
85
+ export {};
85
86
  //# sourceMappingURL=compat.d.ts.map
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import type { HookFixture } from "../fixtures/schema.js";
27
27
  /** A hook's deep-test class: verifiable by verdict, or effect-bearing. */
28
- export type HookClass = "pure-guard" | "side-effecting";
28
+ type HookClass = "pure-guard" | "side-effecting";
29
29
  /**
30
30
  * Substrings that mark a hook id as belonging to a side-effecting family. This
31
31
  * is the seed set US-009 names — autocommit, checkpoint, journal, reindex — and
@@ -58,4 +58,5 @@ export declare function classifyFixture(fixture: HookFixture): Classification;
58
58
  export declare function isPureGuard(fixture: HookFixture): boolean;
59
59
  /** Whether a hook id names a side-effecting hook (US-009's execution surface). */
60
60
  export declare function isSideEffectingHookId(hookId: string): boolean;
61
+ export {};
61
62
  //# sourceMappingURL=classify.d.ts.map
@@ -11,8 +11,6 @@
11
11
  * The module is pure filesystem work with an injectable clock, so the applier's
12
12
  * tests get a deterministic backup directory name without racing the wall clock.
13
13
  */
14
- /** Where backups live under the resolved HQ root. */
15
- export declare const BACKUP_RELDIR: string;
16
14
  /** The result of a backup pass. */
17
15
  export interface BackupResult {
18
16
  /** Absolute path of the timestamped backup directory. */
@@ -22,12 +20,6 @@ export interface BackupResult {
22
20
  /** A single shell command that restores every backed-up file in place. */
23
21
  restoreCommand: string;
24
22
  }
25
- /**
26
- * A filesystem-safe backup directory name derived from an instant, e.g.
27
- * `2026-08-11T08-30-00-000Z`. Colons and dots — illegal or awkward in paths —
28
- * are replaced with dashes; the value stays sortable and unique per millisecond.
29
- */
30
- export declare function backupTimestamp(now?: Date): string;
31
23
  /**
32
24
  * Copy each of `relpaths` (relative to `hqRoot`) into a fresh timestamped backup
33
25
  * directory, preserving both the relative path structure and the file mode.
@@ -14,13 +14,13 @@
14
14
  import * as fs from "node:fs";
15
15
  import * as path from "node:path";
16
16
  /** Where backups live under the resolved HQ root. */
17
- export const BACKUP_RELDIR = path.join("workspace", "doctor", "backups");
17
+ const BACKUP_RELDIR = path.join("workspace", "doctor", "backups");
18
18
  /**
19
19
  * A filesystem-safe backup directory name derived from an instant, e.g.
20
20
  * `2026-08-11T08-30-00-000Z`. Colons and dots — illegal or awkward in paths —
21
21
  * are replaced with dashes; the value stays sortable and unique per millisecond.
22
22
  */
23
- export function backupTimestamp(now = new Date()) {
23
+ function backupTimestamp(now = new Date()) {
24
24
  return now.toISOString().replace(/[:.]/g, "-");
25
25
  }
26
26
  /**
@@ -28,8 +28,6 @@ import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
28
28
  import { type ParsedFixture } from "./schema.js";
29
29
  /** Directory, relative to the HQ root, fixtures are discovered from. */
30
30
  export declare const HOOK_TESTS_RELDIR: string;
31
- /** The id of the fixture-coverage family. */
32
- export declare const FIXTURES_FAMILY_ID = "fixtures";
33
31
  /** checkId of the single coverage-ratio result the report surfaces in its summary. */
34
32
  export declare const FIXTURE_COVERAGE_CHECK_ID = "hooks.fixtures.coverage";
35
33
  /**
@@ -45,7 +43,7 @@ export declare const FIXTURE_COVERAGE_CHECK_ID = "hooks.fixtures.coverage";
45
43
  */
46
44
  export declare function registeredHookIds(hqRoot: string): string[];
47
45
  /** A fixture file located on disk and validated (or a YAML-load failure). */
48
- export interface DiscoveredFixture {
46
+ interface DiscoveredFixture {
49
47
  /** Path relative to the HQ root, e.g. `core/hook-tests/block-core-writes.yaml`. */
50
48
  relpath: string;
51
49
  /** Absolute path on disk. */
@@ -74,13 +72,6 @@ export interface FixtureDiscovery {
74
72
  * looksLikeFixture} are validated and reported.
75
73
  */
76
74
  export declare function discoverFixtures(hqRoot: string): FixtureDiscovery;
77
- /** The coverage tally: how many registered hooks have a usable fixture. */
78
- export interface FixtureCoverage {
79
- /** Registered hooks with a valid, recognised-version fixture. */
80
- tested: number;
81
- /** Registered hooks in total. */
82
- total: number;
83
- }
84
75
  /**
85
76
  * Run the fixture-coverage tier against the resolved tree: enumerate registered
86
77
  * hooks, discover fixtures, and emit UNTESTED / WARN / UNKNOWN results plus the
@@ -93,4 +84,5 @@ export declare function checkFixtureCoverage(context: CheckContext): CheckResult
93
84
  * registry contract).
94
85
  */
95
86
  export declare const fixtureCoverageFamily: CheckFamily;
87
+ export {};
96
88
  //# sourceMappingURL=discover.d.ts.map
@@ -32,7 +32,7 @@ import { looksLikeFixture, parseFixture, SUPPORTED_FIXTURE_SCHEMA_VERSIONS, } fr
32
32
  /** Directory, relative to the HQ root, fixtures are discovered from. */
33
33
  export const HOOK_TESTS_RELDIR = path.join("core", "hook-tests");
34
34
  /** The id of the fixture-coverage family. */
35
- export const FIXTURES_FAMILY_ID = "fixtures";
35
+ const FIXTURES_FAMILY_ID = "fixtures";
36
36
  /** checkId of the single coverage-ratio result the report surfaces in its summary. */
37
37
  export const FIXTURE_COVERAGE_CHECK_ID = "hooks.fixtures.coverage";
38
38
  /** A `*.yaml` file in `core/hook-tests/` that is not a fixture and must be skipped. */
@@ -2,22 +2,7 @@ import { readLanesRegistryFlag } from "./registry-flag.js";
2
2
  import type { LaneRecord } from "./types.js";
3
3
  export declare const LEAD_AUTOPILOT_FLAG = "lanes.lead-autopilot";
4
4
  export declare const LEAD_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG = "lanes.autopilot-missed-deadline-escalation";
5
- export declare const LEAD_AUTOPILOT_MISSED_DEADLINE_ESCALATION_DELAY_MS: number;
6
- export declare const LEAD_AUTOPILOT_NUDGE_REDELIVERY_DELAY_MS: number;
7
- export declare const LEAD_AUTOPILOT_NUDGE_STATE_RETENTION_MS: number;
8
- export declare const LEAD_AUTOPILOT_REVIEW_DELAY_MS: number;
9
- export declare const LEAD_AUTOPILOT_FILL_DELAY_MS: number;
10
- export declare const LEAD_AUTOPILOT_PROMPT_LOOP = "personal/knowledge/team-lead-operating-loop.md";
11
- export declare const LEAD_AUTOPILOT_KPI_STALE_AFTER_MINUTES = 55;
12
- export declare const LEAD_AUTOPILOT_HEADLINE_STALE_AFTER_MINUTES = 55;
13
- export interface RegisteredLead {
14
- team: string;
15
- lane: string;
16
- host: string;
17
- status: string;
18
- prd: string;
19
- }
20
- export type AutopilotCondition = "review_finished_worker" | "fill_grant" | "plan_free_slots" | "flat_kpi" | "stale_kpi" | "stale_headline" | "blocker_record_needs_update" | "project_ready_to_close" | "stale_worker_ledger" | "story_without_live_lane" | "missed_deadline";
5
+ type AutopilotCondition = "review_finished_worker" | "fill_grant" | "plan_free_slots" | "flat_kpi" | "stale_kpi" | "stale_headline" | "blocker_record_needs_update" | "project_ready_to_close" | "stale_worker_ledger" | "story_without_live_lane" | "missed_deadline";
21
6
  export interface AutopilotWake {
22
7
  condition: AutopilotCondition;
23
8
  lead_lane: string;
@@ -29,7 +14,7 @@ export interface AutopilotWake {
29
14
  /** Headline episode represented inside the KPI batch notice, if any. */
30
15
  headline_fingerprint?: string;
31
16
  }
32
- export interface AutopilotKpiStatus {
17
+ interface AutopilotKpiStatus {
33
18
  lead_lane: string;
34
19
  team: string;
35
20
  kpi: string;
@@ -12,15 +12,15 @@ import { readLanesRegistryFlag } from "./registry-flag.js";
12
12
  import { readAllLanes } from "./store.js";
13
13
  export const LEAD_AUTOPILOT_FLAG = "lanes.lead-autopilot";
14
14
  export const LEAD_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG = LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG;
15
- export const LEAD_AUTOPILOT_MISSED_DEADLINE_ESCALATION_DELAY_MS = 60 * 60_000;
16
- export const LEAD_AUTOPILOT_NUDGE_REDELIVERY_DELAY_MS = 30 * 60_000;
17
- export const LEAD_AUTOPILOT_NUDGE_STATE_RETENTION_MS = 24 * 60 * 60_000;
15
+ const LEAD_AUTOPILOT_MISSED_DEADLINE_ESCALATION_DELAY_MS = 60 * 60_000;
16
+ const LEAD_AUTOPILOT_NUDGE_REDELIVERY_DELAY_MS = 30 * 60_000;
17
+ const LEAD_AUTOPILOT_NUDGE_STATE_RETENTION_MS = 24 * 60 * 60_000;
18
18
  const LEAD_AUTOPILOT_DELIVERED_ID_RETENTION_MS = 7 * 24 * 60 * 60_000;
19
- export const LEAD_AUTOPILOT_REVIEW_DELAY_MS = 15 * 60_000;
20
- export const LEAD_AUTOPILOT_FILL_DELAY_MS = 15 * 60_000;
21
- export const LEAD_AUTOPILOT_PROMPT_LOOP = "personal/knowledge/team-lead-operating-loop.md";
22
- export const LEAD_AUTOPILOT_KPI_STALE_AFTER_MINUTES = 55;
23
- export const LEAD_AUTOPILOT_HEADLINE_STALE_AFTER_MINUTES = 55;
19
+ const LEAD_AUTOPILOT_REVIEW_DELAY_MS = 15 * 60_000;
20
+ const LEAD_AUTOPILOT_FILL_DELAY_MS = 15 * 60_000;
21
+ const LEAD_AUTOPILOT_PROMPT_LOOP = "personal/knowledge/team-lead-operating-loop.md";
22
+ const LEAD_AUTOPILOT_KPI_STALE_AFTER_MINUTES = 55;
23
+ const LEAD_AUTOPILOT_HEADLINE_STALE_AFTER_MINUTES = 55;
24
24
  const LIVE_STATES = new Set(["running", "queued", "awaiting_input", "blocked"]);
25
25
  const OPEN_STORY_STATES = new Set(["todo", "queued", "in_progress", "review", "blocked"]);
26
26
  function isOpenProjectStory(story) {
@@ -16,6 +16,8 @@ export declare const LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG = "lanes.aut
16
16
  export declare const LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG = "lanes.message-route-owner-host";
17
17
  /** Enables stale manager-status KPI reporting and one-time lead nudges. */
18
18
  export declare const LEAD_AUTOPILOT_KPI_STALENESS_FLAG = "lanes.autopilot-kpi-staleness";
19
+ /** Enables precise scheduling and priority admission for due lead keep-alive wakes. */
20
+ export declare const LANE_KEEP_ALIVE_DUE_WAKE_PRECISE_FLAG = "lanes.keep-alive-due-wake-precise";
19
21
  /** Enables staleness notices for a registered lead's dashboard headline. */
20
22
  export declare const LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG = "lanes.autopilot-headline-staleness";
21
23
  /** Lets an Outpost's autopilot wake leads created on that Outpost, which record a local host. */
@@ -16,6 +16,8 @@ export const LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG = "lanes.autopilot-m
16
16
  export const LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG = "lanes.message-route-owner-host";
17
17
  /** Enables stale manager-status KPI reporting and one-time lead nudges. */
18
18
  export const LEAD_AUTOPILOT_KPI_STALENESS_FLAG = "lanes.autopilot-kpi-staleness";
19
+ /** Enables precise scheduling and priority admission for due lead keep-alive wakes. */
20
+ export const LANE_KEEP_ALIVE_DUE_WAKE_PRECISE_FLAG = "lanes.keep-alive-due-wake-precise";
19
21
  /** Enables staleness notices for a registered lead's dashboard headline. */
20
22
  export const LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG = "lanes.autopilot-headline-staleness";
21
23
  /** Lets an Outpost's autopilot wake leads created on that Outpost, which record a local host. */
@@ -44,6 +44,8 @@ export interface KeepAliveDoneEnvelope {
44
44
  }
45
45
  export type DoneKeepAliveVerdict = "eligible" | "standing_idle" | "story_complete" | "next_check_at";
46
46
  export declare function keepAliveNextCheckAtMs(value: unknown, nowMs: number, anchorMs?: number): number | undefined;
47
+ /** Shared due-time decision for pre-admission ordering and the resume path. */
48
+ export declare function dueKeepAliveNextCheckMs(nextCheckMs: number, nowMs: number, anchorMs: number, preciseDueWakeEnabled: boolean): number | undefined;
47
49
  /** Shared done-state policy used by watch, reconcile, and the show command. */
48
50
  export declare function doneKeepAliveVerdict(lane: Pick<LaneRecord, "keep_alive_standing">, envelope: KeepAliveDoneEnvelope, hasQueuedWork: boolean, options?: {
49
51
  hasPendingWork?: boolean;
@@ -113,6 +113,15 @@ export function keepAliveNextCheckAtMs(value, nowMs, anchorMs = nowMs) {
113
113
  const capped = Math.min(parsed, stableAnchorMs + LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS);
114
114
  return capped > nowMs ? capped : undefined;
115
115
  }
116
+ /** Shared due-time decision for pre-admission ordering and the resume path. */
117
+ export function dueKeepAliveNextCheckMs(nextCheckMs, nowMs, anchorMs, preciseDueWakeEnabled) {
118
+ if (!Number.isFinite(nextCheckMs) || !Number.isFinite(nowMs) || !Number.isFinite(anchorMs))
119
+ return undefined;
120
+ const capped = Math.min(nextCheckMs, anchorMs + LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS);
121
+ if (capped > nowMs || (preciseDueWakeEnabled && capped <= anchorMs))
122
+ return undefined;
123
+ return capped;
124
+ }
116
125
  /** Shared done-state policy used by watch, reconcile, and the show command. */
117
126
  export function doneKeepAliveVerdict(lane, envelope, hasQueuedWork, options = {}) {
118
127
  if (envelope.decision !== "done")
@@ -22,12 +22,12 @@ import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, envelopeRepairMode, isPidRecor
22
22
  import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, listQuestionIds, updateLane, updateQuestion, } from "./store.js";
23
23
  import { applyThrottleDecision, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyObservation, recordCpuBusyObservation, throttleStatePath, writeThrottleState, } from "./throttle.js";
24
24
  import { readLanesRegistryFlag, readLanesRegistryFlagFailOff } from "./registry-flag.js";
25
- import { LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_LEAD_ADMISSION_PRIORITY_FLAG } from "./flag-keys.js";
25
+ import { LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_KEEP_ALIVE_DUE_WAKE_PRECISE_FLAG, LANE_LEAD_ADMISSION_PRIORITY_FLAG, } from "./flag-keys.js";
26
26
  import { checkLaneDiskSpace, configuredCodexRolloutHomes, laneDiskAccount, retainCodexRollouts } from "./disk-guard.js";
27
27
  import { laneCompanyUidSource, resolveLaneCompany } from "./lane-company.js";
28
28
  import { appendDropboxMessage } from "./dropbox.js";
29
29
  import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolGuard, RUNAWAY_TOOL_GUARD_FLAG, } from "./runaway-tool-guard.js";
30
- 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_NEXT_CHECK_MAX_DELAY_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, queueLaneTerminalResume, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
30
+ 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";
31
31
  import { providerSupportsSessionResume } from "./delivery.js";
32
32
  import { isIsoDateTime } from "./types.js";
33
33
  import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, loadQuotaState, readQuotaSignal, recordAccountQuota, } from "./quota.js";
@@ -1236,6 +1236,7 @@ export async function runPassBody(hqRoot, deps) {
1236
1236
  const keepAliveFlagByCompany = new Map();
1237
1237
  const blockedResumeFlagByCompany = new Map();
1238
1238
  const immediateFirstResumeFlagByCompany = new Map();
1239
+ const preciseDueWakeFlagByCompany = new Map();
1239
1240
  const supersessionLineageAwareByCompany = new Map();
1240
1241
  await Promise.all([...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
1241
1242
  const options = {
@@ -1246,17 +1247,19 @@ export async function runPassBody(hqRoot, deps) {
1246
1247
  lane.keep_alive === true &&
1247
1248
  laneRunsHere(lane, startDeps.host) &&
1248
1249
  keepAliveCompanyKey(resolveLaneCompany(hqRoot, lane).company) === key);
1249
- const [keepAliveEnabled, lineageAware, blockedResumeEnabled, immediateFirstResumeEnabled] = await Promise.all([
1250
+ const [keepAliveEnabled, lineageAware, blockedResumeEnabled, immediateFirstResumeEnabled, preciseDueWakeEnabled] = await Promise.all([
1250
1251
  readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, options),
1251
1252
  readRegistryFlag(LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, options),
1252
1253
  hasBlockedKeepAliveLane
1253
1254
  ? readRegistryFlag(LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, options)
1254
1255
  : Promise.resolve(false),
1255
1256
  readRegistryFlag(LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, options),
1257
+ readRegistryFlag(LANE_KEEP_ALIVE_DUE_WAKE_PRECISE_FLAG, options),
1256
1258
  ]);
1257
1259
  keepAliveFlagByCompany.set(key, keepAliveEnabled);
1258
1260
  blockedResumeFlagByCompany.set(key, blockedResumeEnabled);
1259
1261
  immediateFirstResumeFlagByCompany.set(key, immediateFirstResumeEnabled);
1262
+ preciseDueWakeFlagByCompany.set(key, preciseDueWakeEnabled);
1260
1263
  supersessionLineageAwareByCompany.set(key, lineageAware);
1261
1264
  }));
1262
1265
  const admissionThrottleDeps = startDeps.throttleDeps ?? defaultThrottleDeps(startDeps.env ?? process.env);
@@ -1278,7 +1281,7 @@ export async function runPassBody(hqRoot, deps) {
1278
1281
  const freshLanes = loadLaneRecords(hqRoot, summary);
1279
1282
  const freshQuestions = loadQuestionRecords(hqRoot, summary);
1280
1283
  await closeInterruptedDoneStoryWorkers(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, storyCloseEnabledByCompany);
1281
- const dueKeepAlivesAdmitted = await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, immediateFirstResumeFlagByCompany, deps.terminalKeepAliveCandidate, async (laneIds) => {
1284
+ const dueKeepAlivesAdmitted = await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, immediateFirstResumeFlagByCompany, preciseDueWakeFlagByCompany, deps.terminalKeepAliveCandidate, async (laneIds) => {
1282
1285
  dueKeepAliveLaneIds = new Set(laneIds);
1283
1286
  await admitQueued(hqRoot, loadLaneRecords(hqRoot, summary), summary, admissionDeps, now, laneIds);
1284
1287
  });
@@ -2513,7 +2516,7 @@ function appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, at, reason, dep
2513
2516
  }),
2514
2517
  }), false);
2515
2518
  }
2516
- async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, immediateFirstResumeFlagByCompany, terminalKeepAliveCandidate, admitDueCandidates) {
2519
+ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, immediateFirstResumeFlagByCompany, preciseDueWakeFlagByCompany, terminalKeepAliveCandidate, admitDueCandidates) {
2517
2520
  const nowMs = now.getTime();
2518
2521
  const nowIso = now.toISOString();
2519
2522
  const config = loadLanesConfig(hqRoot);
@@ -2546,10 +2549,14 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
2546
2549
  continue;
2547
2550
  const anchorMs = Date.parse(lane.last_envelope?.at ?? lane.updated_at);
2548
2551
  const nextCheckMs = Date.parse(envelope.next_check_at ?? "");
2549
- if (!Number.isFinite(anchorMs) || !Number.isFinite(nextCheckMs))
2552
+ if (!Number.isFinite(anchorMs))
2550
2553
  continue;
2551
- const cappedNextCheckMs = Math.min(nextCheckMs, anchorMs + LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS);
2552
- if (cappedNextCheckMs <= nowMs &&
2554
+ const companyKey = keepAliveCompanyKey(resolveLaneCompany(hqRoot, lane).company);
2555
+ const preciseDueWakeEnabled = preciseDueWakeFlagByCompany.get(companyKey) === true;
2556
+ // This set only accelerates lanes with an explicit due next-check. Retry
2557
+ // backoff is decided once in the resume path and queuedDueCandidateIds.
2558
+ const dueNextCheckMs = dueKeepAliveNextCheckMs(nextCheckMs, nowMs, anchorMs, preciseDueWakeEnabled);
2559
+ if (dueNextCheckMs !== undefined &&
2553
2560
  doneKeepAliveVerdict(lane, envelope, false, { nowMs, nextCheckAnchorMs: anchorMs }) === "eligible")
2554
2561
  dueCandidateIds.add(lane.lane_id);
2555
2562
  }
@@ -2939,23 +2946,13 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
2939
2946
  const storedNextMs = resetByHuman
2940
2947
  ? Number.NaN
2941
2948
  : Date.parse(current.keep_alive_next_eligible_at ?? "");
2942
- const nextEligibleMs = Number.isFinite(storedNextMs)
2949
+ let nextEligibleMs = Number.isFinite(storedNextMs)
2943
2950
  ? Math.max(attemptEligibleMs, storedNextMs)
2944
2951
  : attemptEligibleMs;
2945
- if (nowMs < nextEligibleMs) {
2946
- let skipLane = current;
2947
- if (resetByHuman && hasKeepAliveProgress(current)) {
2948
- const clearedProgress = casLaneIf(hqRoot, current.lane_id, (latestLane) => latestLane.version === current.version &&
2949
- latestLane.state === current.state &&
2950
- latestLane.keep_alive === true &&
2951
- latestLane.keep_alive_opted_in_at === current.keep_alive_opted_in_at, (latestLane) => clearedKeepAliveProgress(latestLane), deps.dryRun);
2952
- skipLane = clearedProgress.record;
2953
- }
2954
- recordKeepAliveSkipEvent(hqRoot, skipLane, "backoff", skipLane.version, summary, deps, new Date(nextEligibleMs).toISOString());
2955
- continue;
2956
- }
2957
- const nextEligibleAt = new Date(nowMs + leadLaneKeepAliveBackoffMs(immediateFirstResumeEnabled ? attempt : attempt + 1)).toISOString();
2958
2952
  let resumeReason = "backoff";
2953
+ let dueNextCheckMs;
2954
+ const preciseDueWakeEnabled = initialState === "done" &&
2955
+ preciseDueWakeFlagByCompany.get(keepAliveCompanyKey(companyForLane(current))) === true;
2959
2956
  if (initialState === "done") {
2960
2957
  if (hasPendingKeepAliveWork(hqRoot, current, questions)) {
2961
2958
  resumeReason = "pending_work";
@@ -2965,13 +2962,30 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
2965
2962
  const nextCheckMs = Date.parse(finalEnvelope?.next_check_at ?? "");
2966
2963
  const anchorMs = Date.parse(current.last_envelope?.at ?? current.updated_at);
2967
2964
  if (Number.isFinite(nextCheckMs) && Number.isFinite(anchorMs)) {
2968
- const cappedNextCheckMs = Math.min(nextCheckMs, anchorMs + LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS);
2969
- if (cappedNextCheckMs > nextEligibleMs && cappedNextCheckMs <= nowMs) {
2965
+ const eligibleNextCheckMs = dueKeepAliveNextCheckMs(nextCheckMs, nowMs, anchorMs, preciseDueWakeEnabled);
2966
+ if (eligibleNextCheckMs !== undefined &&
2967
+ (preciseDueWakeEnabled || eligibleNextCheckMs > nextEligibleMs)) {
2970
2968
  resumeReason = "next_check_at_due";
2969
+ dueNextCheckMs = eligibleNextCheckMs;
2970
+ if (preciseDueWakeEnabled)
2971
+ nextEligibleMs = Math.min(nextEligibleMs, eligibleNextCheckMs);
2971
2972
  }
2972
2973
  }
2973
2974
  }
2974
2975
  }
2976
+ if (nowMs < nextEligibleMs) {
2977
+ let skipLane = current;
2978
+ if (resetByHuman && hasKeepAliveProgress(current)) {
2979
+ const clearedProgress = casLaneIf(hqRoot, current.lane_id, (latestLane) => latestLane.version === current.version &&
2980
+ latestLane.state === current.state &&
2981
+ latestLane.keep_alive === true &&
2982
+ latestLane.keep_alive_opted_in_at === current.keep_alive_opted_in_at, (latestLane) => clearedKeepAliveProgress(latestLane), deps.dryRun);
2983
+ skipLane = clearedProgress.record;
2984
+ }
2985
+ recordKeepAliveSkipEvent(hqRoot, skipLane, "backoff", skipLane.version, summary, deps, new Date(nextEligibleMs).toISOString());
2986
+ continue;
2987
+ }
2988
+ const nextEligibleAt = new Date(nowMs + leadLaneKeepAliveBackoffMs(immediateFirstResumeEnabled ? attempt : attempt + 1)).toISOString();
2975
2989
  const queuedCount = loadLaneRecords(hqRoot, summary).filter((queuedLane) => queuedLane.state === "queued" &&
2976
2990
  queuedLane.provider === current.provider &&
2977
2991
  laneRunsHere(queuedLane, deps.host) &&
@@ -3233,7 +3247,8 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
3233
3247
  }
3234
3248
  if (!queued.changed)
3235
3249
  continue;
3236
- if (resumeReason === "next_check_at_due")
3250
+ if (resumeReason === "next_check_at_due" ||
3251
+ (preciseDueWakeEnabled && initialState === "done" && nowMs >= nextEligibleMs && dueNextCheckMs === undefined))
3237
3252
  queuedDueCandidateIds.add(current.lane_id);
3238
3253
  summary.resumed.push({
3239
3254
  id: current.lane_id,
@@ -3478,11 +3493,13 @@ async function runTerminalKeepAliveCandidatePass(hqRoot, lanes, summary, deps, n
3478
3493
  let keepAliveEnabled;
3479
3494
  let lineageAware;
3480
3495
  let immediateFirstResumeEnabled;
3496
+ let preciseDueWakeEnabled;
3481
3497
  try {
3482
- [keepAliveEnabled, lineageAware, immediateFirstResumeEnabled] = await Promise.all([
3498
+ [keepAliveEnabled, lineageAware, immediateFirstResumeEnabled, preciseDueWakeEnabled] = await Promise.all([
3483
3499
  readFlag(LEAD_LANE_KEEP_ALIVE_FLAG, options),
3484
3500
  readFlag(LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, options),
3485
3501
  readFlag(LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, options),
3502
+ readFlag(LANE_KEEP_ALIVE_DUE_WAKE_PRECISE_FLAG, options),
3486
3503
  ]);
3487
3504
  }
3488
3505
  catch (err) {
@@ -3497,7 +3514,7 @@ async function runTerminalKeepAliveCandidatePass(hqRoot, lanes, summary, deps, n
3497
3514
  const current = freshLanes.find((row) => row.lane_id === candidate.laneId);
3498
3515
  if (!current || current.version !== candidate.version || current.state !== "done")
3499
3516
  return;
3500
- await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, deps, now, new Map([[keepAliveCompanyKey(company), true]]), new Map([[keepAliveCompanyKey(company), lineageAware]]), new Map(), new Map([[keepAliveCompanyKey(company), immediateFirstResumeEnabled]]), candidate);
3517
+ await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, deps, now, new Map([[keepAliveCompanyKey(company), true]]), new Map([[keepAliveCompanyKey(company), lineageAware]]), new Map(), new Map([[keepAliveCompanyKey(company), immediateFirstResumeEnabled]]), new Map([[keepAliveCompanyKey(company), preciseDueWakeEnabled]]), candidate);
3501
3518
  await admitQueued(hqRoot, loadLaneRecords(hqRoot, summary), summary, deps, now, new Set([candidate.laneId]));
3502
3519
  });
3503
3520
  return summary;
@@ -3,12 +3,12 @@ import type { QuestionRecord } from "./types.js";
3
3
  import type { TrackerRecord } from "./tracker-store.js";
4
4
  export type ReportStoryStatus = "queued" | "in_progress" | "review" | "done" | "dropped";
5
5
  export type ReportAudience = "owner" | "operator" | "lane";
6
- export type ApprovalState = "proposal" | "approved";
6
+ type ApprovalState = "proposal" | "approved";
7
7
  export interface TrackerReportRecord extends TrackerRecord {
8
8
  /** Absolute path of the validated on-disk record; used only in errors. */
9
9
  source_path: string;
10
10
  }
11
- export interface TrackerReportStory {
11
+ interface TrackerReportStory {
12
12
  id: string;
13
13
  title: string;
14
14
  status: ReportStoryStatus;
@@ -24,18 +24,18 @@ export interface TrackerReportIssue {
24
24
  record_id?: string;
25
25
  question_id?: string;
26
26
  }
27
- export interface TrackerReportPullRequest {
27
+ interface TrackerReportPullRequest {
28
28
  number: string;
29
29
  title: string;
30
30
  merged_at: string;
31
31
  source_path: string;
32
32
  }
33
- export interface TrackerReportRelease {
33
+ interface TrackerReportRelease {
34
34
  version: string;
35
35
  released_at: string;
36
36
  source_path: string;
37
37
  }
38
- export interface TrackerReportQuestion {
38
+ interface TrackerReportQuestion {
39
39
  record: QuestionRecord;
40
40
  /** Loaded through the existing company-checked question body reader. */
41
41
  body: QuestionBody;
@@ -63,22 +63,22 @@ export interface TrackerReportInput {
63
63
  since_source: "explicit" | "previous_report" | "first_run";
64
64
  teams: readonly TrackerReportTeamInput[];
65
65
  }
66
- export interface ReportTimedStory {
66
+ interface ReportTimedStory {
67
67
  id: string;
68
68
  title: string;
69
69
  at: string;
70
70
  references: string[];
71
71
  }
72
- export interface ReportPullRequest {
72
+ interface ReportPullRequest {
73
73
  number: string;
74
74
  title: string;
75
75
  merged_at: string;
76
76
  }
77
- export interface ReportRelease {
77
+ interface ReportRelease {
78
78
  version: string;
79
79
  released_at: string;
80
80
  }
81
- export interface ReportKpiValue {
81
+ interface ReportKpiValue {
82
82
  kpi_id: string;
83
83
  name: string;
84
84
  at: string;
@@ -86,7 +86,7 @@ export interface ReportKpiValue {
86
86
  unit: string;
87
87
  source: string;
88
88
  }
89
- export interface ReportQuestion {
89
+ interface ReportQuestion {
90
90
  question_id: string;
91
91
  question: string;
92
92
  asked_at: string;
@@ -99,7 +99,7 @@ export interface ReportQuestion {
99
99
  answered_by: string;
100
100
  };
101
101
  }
102
- export interface ReportBlocker {
102
+ interface ReportBlocker {
103
103
  id: string;
104
104
  story_id: string;
105
105
  what: string;
@@ -107,7 +107,7 @@ export interface ReportBlocker {
107
107
  next_step: string;
108
108
  opened_at: string;
109
109
  }
110
- export interface ReportDirective {
110
+ interface ReportDirective {
111
111
  id: string;
112
112
  text: string;
113
113
  date: string;
@@ -117,7 +117,7 @@ export interface ReportDirective {
117
117
  at: string;
118
118
  }>;
119
119
  }
120
- export interface TrackerOwnerTeamReport {
120
+ interface TrackerOwnerTeamReport {
121
121
  team: string;
122
122
  project_id: string;
123
123
  errors: TrackerReportIssue[];
@@ -142,7 +142,7 @@ export interface TrackerOwnerReport {
142
142
  since_source: TrackerReportInput["since_source"];
143
143
  teams: TrackerOwnerTeamReport[];
144
144
  }
145
- export interface TrackerTeamKpi {
145
+ interface TrackerTeamKpi {
146
146
  id: string;
147
147
  name: string;
148
148
  definition: string;
@@ -162,7 +162,7 @@ export interface TrackerTeamKpi {
162
162
  }>;
163
163
  trend: "improving" | "regressing" | "flat" | "insufficient_data";
164
164
  }
165
- export interface TrackerTeamOkr {
165
+ interface TrackerTeamOkr {
166
166
  id: string;
167
167
  objective: string;
168
168
  period: string;
@@ -200,4 +200,5 @@ export declare function buildTrackerTeamPage(team: TrackerReportTeamInput, compa
200
200
  export declare function renderTrackerOwnerReportText(report: TrackerOwnerReport): string;
201
201
  /** Human-readable team page, with proposal status visible beside each objective and KPI. */
202
202
  export declare function renderTrackerTeamPageText(page: TrackerTeamPage): string;
203
+ export {};
203
204
  //# sourceMappingURL=tracker-report.d.ts.map
@@ -1,11 +1,7 @@
1
1
  import type { LaneRecord, QuestionRecord } from "./types.js";
2
- export declare const LANE_WAIT_POLL_INTERVAL_MS = 2000;
3
- export declare const LANE_WAIT_MAX_LANES = 64;
4
2
  export declare const LANE_WAIT_TIMEOUT_EXIT_CODE = 4;
5
3
  export declare const LANE_WAIT_INVALID_INPUT_EXIT_CODE = 2;
6
- export declare const LANE_WAIT_CHANGE_TYPES: readonly ["envelope", "question", "state"];
7
- export type LaneWaitChangeType = (typeof LANE_WAIT_CHANGE_TYPES)[number];
8
- export type LaneWaitChange = {
4
+ type LaneWaitChange = {
9
5
  type: "envelope";
10
6
  ref: string;
11
7
  decision: "done" | "ask" | "blocked";
@@ -36,4 +32,5 @@ export interface LaneWaitDeps {
36
32
  * lock. Question records are scanned in one pass when question changes matter.
37
33
  */
38
34
  export declare function runLaneWait(hqRoot: string, laneIds: readonly string[], changeTypes: readonly string[], timeoutMs: number, deps?: LaneWaitDeps): Promise<LaneWaitResult | null>;
35
+ export {};
39
36
  //# sourceMappingURL=wait.d.ts.map
@@ -1,10 +1,10 @@
1
1
  import { listQuestionIds, readLane, readQuestion } from "./store.js";
2
2
  import { LanesError } from "./errors.js";
3
- export const LANE_WAIT_POLL_INTERVAL_MS = 2_000;
4
- export const LANE_WAIT_MAX_LANES = 64;
3
+ const LANE_WAIT_POLL_INTERVAL_MS = 2_000;
4
+ const LANE_WAIT_MAX_LANES = 64;
5
5
  export const LANE_WAIT_TIMEOUT_EXIT_CODE = 4;
6
6
  export const LANE_WAIT_INVALID_INPUT_EXIT_CODE = 2;
7
- export const LANE_WAIT_CHANGE_TYPES = ["envelope", "question", "state"];
7
+ const LANE_WAIT_CHANGE_TYPES = ["envelope", "question", "state"];
8
8
  function defaultSleep(ms) {
9
9
  return new Promise((resolve) => setTimeout(resolve, ms));
10
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.7",
3
+ "version": "5.345.8",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {