@indigoai-us/hq-cli 5.345.7 → 5.345.9

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 (50) hide show
  1. package/CHANGELOG.md +8 -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/lanes-message.js +100 -75
  7. package/dist/commands/mcp-status.d.ts +4 -3
  8. package/dist/commands/reindex.d.ts +0 -6
  9. package/dist/commands/reindex.js +1 -1
  10. package/dist/commands/safe-extract.d.ts +2 -17
  11. package/dist/commands/safe-extract.js +2 -2
  12. package/dist/commands/search.d.ts +2 -1
  13. package/dist/lazy-commands.d.ts +0 -6
  14. package/dist/lazy-commands.js +1 -1
  15. package/dist/lib/daemon/heartbeat.d.ts +0 -4
  16. package/dist/lib/daemon/heartbeat.js +3 -3
  17. package/dist/lib/daemon/run.d.ts +1 -1
  18. package/dist/lib/daemon/run.js +11 -2
  19. package/dist/lib/daemon/supervisor.js +2 -1
  20. package/dist/lib/doctor/__testing__/fake-hq-tree.d.ts +8 -9
  21. package/dist/lib/doctor/__testing__/fake-hq-tree.js +1 -1
  22. package/dist/lib/doctor/checks/anywhere.d.ts +3 -4
  23. package/dist/lib/doctor/checks/anywhere.js +2 -2
  24. package/dist/lib/doctor/checks/runtime-probe.d.ts +0 -21
  25. package/dist/lib/doctor/checks/runtime-probe.js +4 -4
  26. package/dist/lib/doctor/checks/sync-health.d.ts +2 -3
  27. package/dist/lib/doctor/checks/sync-health.js +1 -1
  28. package/dist/lib/doctor/compat.d.ts +2 -1
  29. package/dist/lib/doctor/deep/classify.d.ts +2 -1
  30. package/dist/lib/doctor/fix/backup.d.ts +0 -8
  31. package/dist/lib/doctor/fix/backup.js +2 -2
  32. package/dist/lib/doctor/fixtures/discover.d.ts +2 -10
  33. package/dist/lib/doctor/fixtures/discover.js +1 -1
  34. package/dist/lib/install/claude-targets.d.ts +0 -4
  35. package/dist/lib/install/claude-targets.js +3 -3
  36. package/dist/lib/install/codex-targets.d.ts +0 -9
  37. package/dist/lib/install/codex-targets.js +4 -4
  38. package/dist/lib/lanes/autopilot.d.ts +2 -17
  39. package/dist/lib/lanes/autopilot.js +8 -8
  40. package/dist/lib/lanes/flag-keys.d.ts +2 -0
  41. package/dist/lib/lanes/flag-keys.js +2 -0
  42. package/dist/lib/lanes/keep-alive.d.ts +2 -0
  43. package/dist/lib/lanes/keep-alive.js +9 -0
  44. package/dist/lib/lanes/reconcile.js +44 -27
  45. package/dist/lib/lanes/tracker-report.d.ts +16 -15
  46. package/dist/lib/lanes/wait.d.ts +2 -5
  47. package/dist/lib/lanes/wait.js +3 -3
  48. package/dist/lib/mesh/live/report-summary.d.ts +0 -10
  49. package/dist/lib/mesh/live/report-summary.js +5 -5
  50. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.9] — 2026-10-05
6
+ - Lane messages to exited sessions now queue durably when provider admission is busy, so delivery succeeds and reconcile can resume them later.
7
+ - Keep the current sync runner until an hq-cloud version matching the configured pin is available.
8
+
9
+ ## [5.345.8] — 2026-10-05
10
+ - Prioritize due lead keep-alive retries and honor due next-check times ahead of later backoff deadlines behind an hq-flags gate.
11
+ - Drain bot turns before replacing a running bot after a daemon plan change.
12
+
5
13
  ## [5.345.7] — 2026-10-05
6
14
  - Retry a just-published hq-cli version when npm briefly reports `notarget`.
7
15
  - 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(),
@@ -754,6 +754,59 @@ export function createLaneMessageHandlers(deps) {
754
754
  ...(keepCurrentAccount && opts.model !== undefined ? { cliModel: opts.model } : {}),
755
755
  });
756
756
  }
757
+ const queueResumeMessage = (queuedReason, archiveBeforeWrite) => {
758
+ const sessionId = lane.provider_session_id;
759
+ if (!sessionId) {
760
+ throw new LanesError("no_provider_session", `Lane ${laneId} has no provider session id to queue this message`, {
761
+ lane_id: laneId,
762
+ provider: lane.provider,
763
+ });
764
+ }
765
+ if (archiveBeforeWrite)
766
+ archiveResumeMessage(hqRoot, laneId, dropboxMessage);
767
+ const queued = casUpdateLane(hqRoot, laneId, (current) => {
768
+ if (current.version !== lane.version ||
769
+ current.state !== lane.state ||
770
+ current.provider_session_id !== sessionId) {
771
+ throw new LanesError("lane_admission_conflict", `Lane ${laneId} changed while its message was queued`, {
772
+ lane_id: laneId,
773
+ prepared_version: lane.version,
774
+ version: current.version,
775
+ state: current.state,
776
+ });
777
+ }
778
+ return {
779
+ ...current,
780
+ state: "queued",
781
+ queued_reason: queuedReason,
782
+ queued_details: undefined,
783
+ queued_at: at,
784
+ admission_reserved_at: undefined,
785
+ pending_resume: {
786
+ session_id: sessionId,
787
+ trigger: messageHistoryTrigger,
788
+ caller: ctx.env.HQ_LANE_ID?.trim() || "user",
789
+ text: deliveryText,
790
+ ...(isMonitorEvent ? { monitor_event: true } : {}),
791
+ message_id: messageId,
792
+ compact: opts.compact !== false,
793
+ at,
794
+ },
795
+ };
796
+ });
797
+ queuedOnAdmission = true;
798
+ storySwitchDeliveryStarted = true;
799
+ deps.onLaneMessageQueued?.(laneId, "admission");
800
+ if (!archiveBeforeWrite) {
801
+ try {
802
+ archiveResumeMessage(hqRoot, laneId, dropboxMessage);
803
+ }
804
+ catch (archiveError) {
805
+ process.stderr.write(`Could not write the resume message audit copy for ${laneId}: ${archiveError instanceof Error ? archiveError.message : String(archiveError)}\n`);
806
+ }
807
+ }
808
+ return queued;
809
+ };
757
810
  let questionCancellation = null;
758
811
  if (move?.queued || queuedOnQuotaHold) {
759
812
  // Parked at the target's cap: reconcile resumes the session there.
@@ -777,84 +830,56 @@ export function createLaneMessageHandlers(deps) {
777
830
  // Exited worker with a recorded session: queue the message when capacity
778
831
  // is occupied, otherwise start its next turn under the shared admission lock.
779
832
  assertLaneRunsHereForAdmission(ctx, lane);
780
- return withLaneAdmissionLock(hqRoot, lane.provider, async () => {
781
- const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
782
- if (admission.action === "refuse" && admission.error)
783
- throw admission.error;
784
- return withResumeHireAdmission(ctx, lane, () => {
785
- if (admission.action === "queue") {
786
- const sessionId = lane.provider_session_id;
787
- if (!sessionId) {
788
- throw new LanesError("no_provider_session", `Lane ${laneId} has no provider session id to queue this message resume`, {
789
- lane_id: laneId,
790
- provider: lane.provider,
791
- });
833
+ try {
834
+ return await withLaneAdmissionLock(hqRoot, lane.provider, async () => {
835
+ const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
836
+ if (admission.action === "refuse" && admission.error)
837
+ throw admission.error;
838
+ return withResumeHireAdmission(ctx, lane, () => {
839
+ if (admission.action === "queue") {
840
+ return queueResumeMessage(admission.reason ?? "throttle", true);
792
841
  }
793
- archiveResumeMessage(hqRoot, laneId, dropboxMessage);
794
- queuedOnAdmission = true;
795
- const queued = casUpdateLane(hqRoot, laneId, (current) => {
796
- if (current.version !== lane.version || current.state !== lane.state) {
797
- throw new LanesError("lane_admission_conflict", `Lane ${laneId} changed while message admission was checked`, {
798
- lane_id: laneId,
799
- prepared_version: lane.version,
800
- version: current.version,
801
- state: current.state,
802
- });
803
- }
804
- return {
805
- ...current,
806
- state: "queued",
807
- queued_reason: admission.reason ?? "throttle",
808
- queued_details: undefined,
809
- queued_at: at,
810
- pending_resume: {
811
- session_id: sessionId,
812
- trigger: messageHistoryTrigger,
813
- caller: ctx.env.HQ_LANE_ID?.trim() || "user",
814
- text: deliveryText,
815
- ...(isMonitorEvent ? { monitor_event: true } : {}),
816
- message_id: messageId,
817
- compact: opts.compact !== false,
818
- at,
819
- },
820
- };
842
+ const started = spawnLaneWorker(hqRoot, lane, {
843
+ env: ctx.env,
844
+ modelDeps: {
845
+ readFileSync: ctx.readFileSync,
846
+ probe: ctx.probeProvider,
847
+ env: ctx.env,
848
+ },
849
+ existsSync: ctx.existsSync,
850
+ spawnProcess: ctx.spawnProcess,
851
+ proveDetached: ctx.proveDetached,
852
+ killProcess: ctx.killProcess,
853
+ now: ctx.now,
854
+ resolveMachineIdentity: ctx.resolveMachineIdentity,
855
+ runCompaction: ctx.runCompaction,
856
+ locateTranscript: ctx.locateTranscript,
857
+ }, {
858
+ trigger: {
859
+ kind: "message",
860
+ text: deliveryText,
861
+ message_id: messageId,
862
+ ...(isMonitorEvent ? { monitor_event: true } : {}),
863
+ ...(isSystemNotice ? { system_notice: true } : {}),
864
+ },
865
+ compact: opts.compact !== false,
866
+ effort: resumeEffort ?? recordedResumeEffort(lane),
821
867
  });
822
868
  storySwitchDeliveryStarted = true;
823
- deps.onLaneMessageQueued?.(laneId, "admission");
824
- return queued;
825
- }
826
- const started = spawnLaneWorker(hqRoot, lane, {
827
- env: ctx.env,
828
- modelDeps: {
829
- readFileSync: ctx.readFileSync,
830
- probe: ctx.probeProvider,
831
- env: ctx.env,
832
- },
833
- existsSync: ctx.existsSync,
834
- spawnProcess: ctx.spawnProcess,
835
- proveDetached: ctx.proveDetached,
836
- killProcess: ctx.killProcess,
837
- now: ctx.now,
838
- resolveMachineIdentity: ctx.resolveMachineIdentity,
839
- runCompaction: ctx.runCompaction,
840
- locateTranscript: ctx.locateTranscript,
841
- }, {
842
- trigger: {
843
- kind: "message",
844
- text: deliveryText,
845
- message_id: messageId,
846
- ...(isMonitorEvent ? { monitor_event: true } : {}),
847
- ...(isSystemNotice ? { system_notice: true } : {}),
848
- },
849
- compact: opts.compact !== false,
850
- effort: resumeEffort ?? recordedResumeEffort(lane),
851
- });
852
- storySwitchDeliveryStarted = true;
853
- deps.onLaneWorkerStarted?.(laneId, "message");
854
- archiveResumeMessage(hqRoot, laneId, dropboxMessage);
855
- return started;
856
- }, hireGrantContext);
857
- }, ctx.sleep);
869
+ deps.onLaneWorkerStarted?.(laneId, "message");
870
+ archiveResumeMessage(hqRoot, laneId, dropboxMessage);
871
+ return started;
872
+ }, hireGrantContext);
873
+ }, ctx.sleep);
874
+ }
875
+ catch (error) {
876
+ const expectedLockPath = path.join(hqRoot, "workspace", "lanes-runs", ".admission-locks", `${lane.provider}-provider.lock`);
877
+ if (!(error instanceof LanesError) ||
878
+ error.error !== "store_lock_timeout" ||
879
+ error.details.path !== expectedLockPath)
880
+ throw error;
881
+ return queueResumeMessage(undefined, false);
882
+ }
858
883
  },
859
884
  }, mechanism);
860
885
  if (dispatched.mechanism === "resume")
@@ -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);
@@ -5,9 +5,7 @@
5
5
  * from a stale one offline. A device with no beat for 5 minutes is
6
6
  * "daemon stale".
7
7
  */
8
- export declare const HEARTBEAT_INTERVAL_MS = 60000;
9
8
  export declare const HEARTBEAT_STALE_MS: number;
10
- export declare const HEARTBEAT_FILE = "hqd-heartbeat.json";
11
9
  export declare const DAEMON_STALE_LABEL = "daemon stale";
12
10
  export interface HeartbeatState {
13
11
  at: string;
@@ -16,8 +14,6 @@ export interface HeartbeatState {
16
14
  device: string;
17
15
  }
18
16
  export declare function heartbeatFile(home?: string, env?: NodeJS.ProcessEnv): string;
19
- /** Mesh session id for this device's daemon; only characters the spool accepts. */
20
- export declare function heartbeatSessionId(hostname?: string): string;
21
17
  export declare function readHeartbeat(file: string): HeartbeatState | null;
22
18
  /** Presence label for a device given its last beat: "daemon stale" after 5 minutes of silence. */
23
19
  export declare function daemonPresenceLabel(state: HeartbeatState | null, now?: Date): "daemon live" | typeof DAEMON_STALE_LABEL;
@@ -8,16 +8,16 @@
8
8
  import * as fs from "node:fs";
9
9
  import * as os from "node:os";
10
10
  import * as path from "node:path";
11
- export const HEARTBEAT_INTERVAL_MS = 60_000;
11
+ const HEARTBEAT_INTERVAL_MS = 60_000;
12
12
  export const HEARTBEAT_STALE_MS = 5 * 60_000;
13
- export const HEARTBEAT_FILE = "hqd-heartbeat.json";
13
+ const HEARTBEAT_FILE = "hqd-heartbeat.json";
14
14
  export const DAEMON_STALE_LABEL = "daemon stale";
15
15
  export function heartbeatFile(home = os.homedir(), env = process.env) {
16
16
  const dir = env.HQ_REGISTRY_DIR?.trim() ? path.resolve(env.HQ_REGISTRY_DIR.trim()) : path.join(home, ".hq");
17
17
  return path.join(dir, HEARTBEAT_FILE);
18
18
  }
19
19
  /** Mesh session id for this device's daemon; only characters the spool accepts. */
20
- export function heartbeatSessionId(hostname = os.hostname()) {
20
+ function heartbeatSessionId(hostname = os.hostname()) {
21
21
  return `hqd-${hostname.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").slice(0, 48) || "device"}`;
22
22
  }
23
23
  export function readHeartbeat(file) {
@@ -62,7 +62,7 @@ export type SyncRunnerSource = {
62
62
  export declare function syncRunnerSourceForPlan(stagedRunnerPath: string | null, hqCloud: PickedHqCloud | null, legacyBundledRunnerPath?: string | null, staged?: {
63
63
  version: string | null;
64
64
  pinnedVersion?: string | null;
65
- }): SyncRunnerSource;
65
+ }, currentSyncRunnerSource?: SyncRunnerSource | null): SyncRunnerSource;
66
66
  /** Whether every executable in the first service plan remains usable after the CLI package is replaced. */
67
67
  export declare function daemonPlanCanReleasePackageUseLease(plans: readonly UnitPlan[], replaceablePackageRoots: readonly string[], stagingFailed: boolean, hqBootstrapReady?: boolean): boolean;
68
68
  export declare function daemonHqChildCommand(nodeBinary: string, hqBinary: string, args: readonly string[], bootstrapPath: string | null, packagePrefix: string | null): [string, string[]];
@@ -76,13 +76,20 @@ export function handoffForPackageUpdateRequest(requested, restartDaemon) {
76
76
  const LEGACY_PROBE_TTL_MS = 60_000;
77
77
  /** A flag read that has not answered by then counts as off. */
78
78
  const HANDOFF_FLAG_TIMEOUT_MS = 10_000;
79
- export function syncRunnerSourceForPlan(stagedRunnerPath, hqCloud, legacyBundledRunnerPath = null, staged = { version: null }) {
79
+ export function syncRunnerSourceForPlan(stagedRunnerPath, hqCloud, legacyBundledRunnerPath = null, staged = { version: null }, currentSyncRunnerSource = null) {
80
80
  // A managed copy lives in the daemon directory, so it survives a CLI
81
81
  // replacement. Before lease release pickHqCloud returns it only when it is
82
82
  // pinned or newer than the bundled copy; after release it is the only
83
83
  // eligible package, so it must not displace a newer staged bundle unless it
84
84
  // is the pinned version. The staged bundle is built from the bundled copy and
85
85
  // otherwise only stands in for that copy (hq-cli#1547).
86
+ const pin = staged.pinnedVersion;
87
+ const hasRunnerAtOrAbovePin = pin != null &&
88
+ ((staged.version !== null && !hqCloudVersionOlder(staged.version, pin)) ||
89
+ (hqCloud !== null && !hqCloudVersionOlder(hqCloud.version, pin)));
90
+ if (pin && !hasRunnerAtOrAbovePin && currentSyncRunnerSource?.runnerPath) {
91
+ return currentSyncRunnerSource;
92
+ }
86
93
  const managedYieldsToStaged = stagedRunnerPath !== null &&
87
94
  hqCloud?.source === "managed" &&
88
95
  hqCloud.version !== staged.pinnedVersion &&
@@ -373,6 +380,7 @@ export async function runDaemon(opts) {
373
380
  let hqChildBootstrapStageFailed = false;
374
381
  let legacySyncRunnerPathChecked = false;
375
382
  let legacySyncRunnerPath = null;
383
+ let lastPlannedSyncRunnerSource = null;
376
384
  let packageUseLeaseReleased = false;
377
385
  let hqCloudVersionBeforeRelease = null;
378
386
  let startupPlanOpen = true;
@@ -570,7 +578,8 @@ export async function runDaemon(opts) {
570
578
  legacySyncRunnerPath = resolveSyncRunnerPath();
571
579
  legacySyncRunnerPathChecked = true;
572
580
  }
573
- const syncSource = syncRunnerSourceForPlan(staged?.runnerPath ?? null, hqCloud, packageUseLeaseReleased ? null : legacySyncRunnerPath, { version: staged?.metadata.version ?? null, pinnedVersion: config.hqCloudVersion ?? null });
581
+ const syncSource = syncRunnerSourceForPlan(staged?.runnerPath ?? null, hqCloud, packageUseLeaseReleased ? null : legacySyncRunnerPath, { version: staged?.metadata.version ?? null, pinnedVersion: config.hqCloudVersion ?? null }, lastPlannedSyncRunnerSource);
582
+ lastPlannedSyncRunnerSource = syncSource.runnerPath ? syncSource : null;
574
583
  if (!syncSource.runnerPath && !warnedMissingSyncRunner) {
575
584
  warnedMissingSyncRunner = true;
576
585
  log("warn", "the hq-cloud sync runner was not found in this CLI install; sync is unavailable");
@@ -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