@indigoai-us/hq-cli 5.345.102 → 5.345.104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.104] — 2026-10-12
6
+ - Session-start health no longer files a leftover personal-files journal
7
+ warning plus an ordinary HQ Core upgrade as failed doctor repairs.
8
+ `hq doctor --json` drops both leftover warnings, including from its
9
+ summary; `hq doctor` still prints them.
10
+ - On Macs, idle `hq lanes` work no longer sits queued on the memory throttle
11
+ just because reclaimable cache made free RAM look too low.
12
+
5
13
  ## [5.345.102] — 2026-10-12
6
14
  - Session-start health no longer files a leftover company journal as an
7
15
  unrepaired doctor finding, and company manifest rows keep a real check id
@@ -106,13 +106,14 @@ export interface BuildDoctorJsonInput {
106
106
  clientHealthCheckStatusEnabledCheckIds?: ReadonlySet<string>;
107
107
  /**
108
108
  * JSON consumed by the SessionStart health hook (`--client-health-report`).
109
- * Ordinary hq-core upgrade WARNs are omitted from every JSON document
110
- * (`--json` and `--client-health-report`), including `summary` tallies:
111
- * `--fix` cannot apply a release, and both current hooks and older ones
112
- * that only call `hq doctor --json` treated the leftover WARN as an
113
- * unrepaired failure. Human `hq doctor` still prints the upgrade. Company
114
- * journals this `--fix` pass did not try to refresh are omitted only from
115
- * `--client-health-report`: the repair cap leaves them for a later session.
109
+ * Ordinary hq-core upgrade WARNs and leftover `sync.journal.personal` WARNs
110
+ * are omitted from every JSON document (`--json` and `--client-health-report`),
111
+ * including `summary` tallies: `--fix` cannot apply a release, and the leftover
112
+ * personal shard is not a live sync target. Both current hooks and older ones
113
+ * that only call `hq doctor --json` treated those leftover WARNs as unrepaired
114
+ * failures. Human `hq doctor` still prints them. Company journals this `--fix`
115
+ * pass did not try to refresh are omitted only from `--client-health-report`:
116
+ * the repair cap leaves them for a later session.
116
117
  */
117
118
  clientHealthReport?: boolean;
118
119
  /** Clock for the leftover-journal freshness window. Default: `Date.now`. */
@@ -129,6 +130,17 @@ export declare function isOrdinaryCoreUpdateWarn(result: {
129
130
  checkId: string;
130
131
  status: DoctorStatus;
131
132
  }): boolean;
133
+ /**
134
+ * True when doctor still WARNs on the leftover `personal` journal shard.
135
+ * That file is not the live personal-files target; `--fix` refreshes the vault
136
+ * journal instead. JSON omits the WARN so older `--json` health hooks do not
137
+ * file it as a failed repair. FAIL stays visible. NA leftover rows stay in
138
+ * JSON so operators can still see the unused shard.
139
+ */
140
+ export declare function isLeftoverPersonalJournalWarn(result: {
141
+ checkId: string;
142
+ status: DoctorStatus;
143
+ }): boolean;
132
144
  /** Assemble the machine-readable document from a completed run. */
133
145
  export declare function buildDoctorJson(input: BuildDoctorJsonInput): DoctorJsonDocument;
134
146
  /** Serialise the document as pretty-printed JSON with a trailing newline. */
@@ -40,6 +40,19 @@ export const DOCTOR_JSON_SCHEMA_VERSION = 2;
40
40
  export function isOrdinaryCoreUpdateWarn(result) {
41
41
  return result.checkId === "sync.update.core" && result.status === "WARN";
42
42
  }
43
+ /**
44
+ * True when doctor still WARNs on the leftover `personal` journal shard.
45
+ * That file is not the live personal-files target; `--fix` refreshes the vault
46
+ * journal instead. JSON omits the WARN so older `--json` health hooks do not
47
+ * file it as a failed repair. FAIL stays visible. NA leftover rows stay in
48
+ * JSON so operators can still see the unused shard.
49
+ */
50
+ export function isLeftoverPersonalJournalWarn(result) {
51
+ return result.checkId === "sync.journal.personal" && result.status === "WARN";
52
+ }
53
+ function omitFromDoctorJson(result) {
54
+ return isOrdinaryCoreUpdateWarn(result) || isLeftoverPersonalJournalWarn(result);
55
+ }
43
56
  /** Tally the results that actually ship in the JSON document. */
44
57
  function summarizeJsonResults(results) {
45
58
  const counts = emptyStatusCounts();
@@ -55,7 +68,7 @@ export function buildDoctorJson(input) {
55
68
  const results = [];
56
69
  for (const run of input.families) {
57
70
  for (const result of run.results) {
58
- if (isOrdinaryCoreUpdateWarn(result)) {
71
+ if (omitFromDoctorJson(result)) {
59
72
  continue;
60
73
  }
61
74
  if (input.clientHealthReport === true &&
@@ -36,7 +36,7 @@ export { ENVELOPE_INVALID_REASON, decideEnvelopeRepair, emitMeshSoft, envelopeQu
36
36
  export type { EnvelopeRepairDecision, LatestEnvelopeFile } from "./watcher.js";
37
37
  export { LANE_MESH_EVENTS, WIRE_EVENT_KINDS, createLaneMesh, emitLaneDecisionEvents, emitLaneProgressEvent, laneCanEmit, parseLaneMeshEvent, progressEventDue, toWireThreadEvent, } from "./mesh-events.js";
38
38
  export type { CreateLaneMeshOptions, LaneMesh, LaneMeshEvent, WireEventKind, WireThreadEvent, } from "./mesh-events.js";
39
- export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, parseMemAvailableKb, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
39
+ export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, VM_STAT_BIN, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, parseMemAvailableKb, parseVmStatAvailableMb, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
40
40
  export type { CpuTimes, ThrottleAdmitReason, ThrottleDecision, ThrottleDeferReason, ThrottleDeps, ThrottleState, } from "./throttle.js";
41
41
  export { effectiveAccountHome, } from "./account-home.js";
42
42
  export type { EffectiveAccountHome, } from "./account-home.js";
@@ -20,7 +20,7 @@ export { PID_LIVENESS_DECISIONS, PROVIDER_COMMAND, PROVIDER_MODEL_FLAG, SPAWN_TE
20
20
  export { SILENCE_MS, WATCHER_DECISION_EVENTS, WATCHER_OUT_MAX_BYTES, WATCHER_POLL_MS, WATCHER_SIGNAL_KINDS, appendCappedLog, appendWatcherSignal, applyTerminalToLane, classifyLaneExit, clearLaneStalled, isPidAlive, laneIdentityFromRecord, markLaneStalled, notifySlotFreed, parseWatcherSignalKind, readLatestEnvelope, runWatcherLoop, stalledReason, tickWatcher, watcherEventsPath, watcherLastSignalPath, watcherOutLogPath, writeWatcherDecision, } from "./watcher.js";
21
21
  export { ENVELOPE_INVALID_REASON, decideEnvelopeRepair, emitMeshSoft, envelopeQuestionIds, readLatestEnvelopeFile, readLatestEnvelopeRecord, } from "./watcher.js";
22
22
  export { LANE_MESH_EVENTS, WIRE_EVENT_KINDS, createLaneMesh, emitLaneDecisionEvents, emitLaneProgressEvent, laneCanEmit, parseLaneMeshEvent, progressEventDue, toWireThreadEvent, } from "./mesh-events.js";
23
- export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, parseMemAvailableKb, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
23
+ export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, VM_STAT_BIN, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, parseMemAvailableKb, parseVmStatAvailableMb, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
24
24
  export { effectiveAccountHome, } from "./account-home.js";
25
25
  export { AUTH_JSON_NAME, CLAUDE_CREDENTIALS_NAME, PROVIDER_CREDENTIAL_FILES, PROVIDER_HOME_ENV, accountEnv, accountStatus, authJsonPath, authStateFor, credentialPaths, hasCredentialFile, isCredentialProven, isLoggedOut, isOccupyingLaneState, lastStartedAt, listAccountStatuses, occupyingLaneCount, resolveAccount, runningLaneCount, selectBalancedAccount, } from "./accounts.js";
26
26
  export { LIVE_WITHIN_MS, LIVENESS_EVENT_KINDS, SILENT_LOST_MS, TURN_START_LOST_MS, buildLivenessIndexFromSpool, classifySessionLiveness, defaultLivenessDeps, defaultSpoolPath, ingestSpoolLine, listSessionLiveness, livenessIndexPath, loadLivenessIndex, readDaemonPresenceConnected, readLivenessIndex, rebuildLivenessIndex, sessionLiveness, } from "./liveness.js";
@@ -16,6 +16,7 @@ export declare const CPU_SAMPLE_MS = 5000;
16
16
  export declare const STARVATION_MAX_DEFERRALS = 10;
17
17
  export declare const STARVATION_MAX_MS: number;
18
18
  export declare const PROC_MEMINFO = "/proc/meminfo";
19
+ export declare const VM_STAT_BIN = "/usr/bin/vm_stat";
19
20
  export declare const LANES_CPU_BUSY_OVERRIDE = "HQ_LANES_CPU_BUSY_OVERRIDE";
20
21
  export declare const LANES_CPU_CHECK = "HQ_LANES_CPU_CHECK";
21
22
  export interface CpuTimes {
@@ -30,9 +31,19 @@ export interface ThrottleDeps {
30
31
  readFileSync: (file: string, encoding: "utf8") => string;
31
32
  /**
32
33
  * Fallback only where MemAvailable is absent from /proc/meminfo (or the
33
- * file itself is missing). Do not use this as the Linux primary reading.
34
+ * file itself is missing on a non-darwin host). Do not use this as the
35
+ * Linux primary reading, and do not use it on darwin: os.freemem() there
36
+ * excludes reclaimable page cache and will hold every lane.
34
37
  */
35
38
  freemem: () => number;
39
+ platform: () => NodeJS.Platform;
40
+ /**
41
+ * Darwin only: read vm_stat when /proc/meminfo is absent. Unused on Linux.
42
+ */
43
+ execFileSync: (file: string, args: readonly string[], options: {
44
+ encoding: "utf8";
45
+ timeout: number;
46
+ }) => string;
36
47
  existsSync: (file: string) => boolean;
37
48
  mkdirSync: typeof fs.mkdirSync;
38
49
  writeFileSync: typeof fs.writeFileSync;
@@ -70,8 +81,15 @@ export declare function recordCpuBusyObservation(state: ThrottleState, observati
70
81
  /** Parse MemAvailable kB from /proc/meminfo. */
71
82
  export declare function parseMemAvailableKb(contents: string): number | null;
72
83
  /**
73
- * Available memory in MiB. Prefers kernel MemAvailable; falls back to
74
- * os.freemem() only where MemAvailable is absent.
84
+ * Available memory in MiB from darwin `vm_stat`: free + inactive + speculative
85
+ * pages. That is the reclaimable set os.freemem() omits on macOS.
86
+ */
87
+ export declare function parseVmStatAvailableMb(contents: string): number | null;
88
+ /**
89
+ * Available memory in MiB. Prefers kernel MemAvailable. On darwin without
90
+ * /proc/meminfo, uses vm_stat (free+inactive+speculative) and never os.freemem().
91
+ * Elsewhere, falls back to os.freemem() only where MemAvailable is absent.
92
+ * Returning null skips the memory gate instead of holding every lane.
75
93
  */
76
94
  export declare function readAvailableMemMb(deps?: ThrottleDeps): number | null;
77
95
  export interface ThrottleState {
@@ -9,6 +9,7 @@
9
9
  * Overrides HQ_LANES_CPU_BUSY_OVERRIDE and HQ_LANES_CPU_CHECK are deliberately
10
10
  * distinct from the runner's HQ_WORKFLOW_* pair — this module never reads those.
11
11
  */
12
+ import { execFileSync as nodeExecFileSync } from "node:child_process";
12
13
  import * as fs from "node:fs";
13
14
  import * as os from "node:os";
14
15
  import * as path from "node:path";
@@ -20,6 +21,8 @@ const CPU_CUMULATIVE_MAX_AGE_MS = 5 * 60 * 1000;
20
21
  export const STARVATION_MAX_DEFERRALS = 10;
21
22
  export const STARVATION_MAX_MS = 30 * 60 * 1000;
22
23
  export const PROC_MEMINFO = "/proc/meminfo";
24
+ export const VM_STAT_BIN = "/usr/bin/vm_stat";
25
+ const VM_STAT_TIMEOUT_MS = 2_000;
23
26
  export const LANES_CPU_BUSY_OVERRIDE = "HQ_LANES_CPU_BUSY_OVERRIDE";
24
27
  export const LANES_CPU_CHECK = "HQ_LANES_CPU_CHECK";
25
28
  function defaultSleep(ms) {
@@ -35,8 +38,13 @@ export function defaultThrottleDeps(env = process.env) {
35
38
  sleep: defaultSleep,
36
39
  readFileSync: (file, encoding) => fs.readFileSync(file, encoding),
37
40
  // os.freemem() reports MemFree on Linux (excludes page cache). Used only
38
- // where MemAvailable is absent — see readAvailableMemMb.
41
+ // where MemAvailable is absent on a non-darwin host — see readAvailableMemMb.
39
42
  freemem: () => os.freemem(),
43
+ platform: () => process.platform,
44
+ execFileSync: (file, args, options) => nodeExecFileSync(file, [...args], {
45
+ encoding: "utf8",
46
+ timeout: options.timeout,
47
+ }),
40
48
  existsSync: (file) => fs.existsSync(file),
41
49
  mkdirSync: fs.mkdirSync.bind(fs),
42
50
  writeFileSync: fs.writeFileSync.bind(fs),
@@ -184,9 +192,63 @@ export function parseMemAvailableKb(contents) {
184
192
  }
185
193
  return null;
186
194
  }
195
+ function parseVmStatPages(contents, label) {
196
+ const match = new RegExp(`^Pages ${label}:\\s+([0-9.]+)\\s*$`, "m").exec(contents);
197
+ if (!match)
198
+ return null;
199
+ const n = Number(match[1].replace(/\.$/, ""));
200
+ return Number.isFinite(n) && n >= 0 ? n : null;
201
+ }
202
+ /**
203
+ * Available memory in MiB from darwin `vm_stat`: free + inactive + speculative
204
+ * pages. That is the reclaimable set os.freemem() omits on macOS.
205
+ */
206
+ export function parseVmStatAvailableMb(contents) {
207
+ const pageSizeMatch = /page size of (\d+) bytes/i.exec(contents);
208
+ const pageSize = pageSizeMatch ? Number(pageSizeMatch[1]) : Number.NaN;
209
+ if (!Number.isFinite(pageSize) || pageSize <= 0)
210
+ return null;
211
+ const free = parseVmStatPages(contents, "free");
212
+ const inactive = parseVmStatPages(contents, "inactive");
213
+ const speculative = parseVmStatPages(contents, "speculative");
214
+ if (free === null || inactive === null || speculative === null)
215
+ return null;
216
+ return ((free + inactive + speculative) * pageSize) / (1024 * 1024);
217
+ }
218
+ function readDarwinAvailableMemMb(deps) {
219
+ try {
220
+ const contents = deps.execFileSync(VM_STAT_BIN, [], {
221
+ encoding: "utf8",
222
+ timeout: VM_STAT_TIMEOUT_MS,
223
+ });
224
+ const mb = parseVmStatAvailableMb(contents);
225
+ if (mb !== null)
226
+ return mb;
227
+ process.stderr.write("lanes throttle: darwin vm_stat output was unreadable; skipping the memory gate\n");
228
+ return null;
229
+ }
230
+ catch (err) {
231
+ process.stderr.write(`lanes throttle: darwin vm_stat failed: ${err instanceof Error ? err.message : String(err)}; skipping the memory gate\n`);
232
+ return null;
233
+ }
234
+ }
235
+ function readFreememMb(deps) {
236
+ try {
237
+ const bytes = deps.freemem();
238
+ if (!Number.isFinite(bytes) || bytes < 0)
239
+ return null;
240
+ return bytes / (1024 * 1024);
241
+ }
242
+ catch (err) {
243
+ process.stderr.write(`lanes throttle: freemem fallback failed: ${err instanceof Error ? err.message : String(err)}\n`);
244
+ return null;
245
+ }
246
+ }
187
247
  /**
188
- * Available memory in MiB. Prefers kernel MemAvailable; falls back to
189
- * os.freemem() only where MemAvailable is absent.
248
+ * Available memory in MiB. Prefers kernel MemAvailable. On darwin without
249
+ * /proc/meminfo, uses vm_stat (free+inactive+speculative) and never os.freemem().
250
+ * Elsewhere, falls back to os.freemem() only where MemAvailable is absent.
251
+ * Returning null skips the memory gate instead of holding every lane.
190
252
  */
191
253
  export function readAvailableMemMb(deps = defaultThrottleDeps()) {
192
254
  try {
@@ -203,18 +265,10 @@ export function readAvailableMemMb(deps = defaultThrottleDeps()) {
203
265
  process.stderr.write(`lanes throttle: failed to read ${PROC_MEMINFO}: ${err instanceof Error ? err.message : String(err)}\n`);
204
266
  return null;
205
267
  }
206
- // Platform without /proc/meminfo — fall back to free memory.
207
- }
208
- try {
209
- const bytes = deps.freemem();
210
- if (!Number.isFinite(bytes) || bytes < 0)
211
- return null;
212
- return bytes / (1024 * 1024);
213
- }
214
- catch (err) {
215
- process.stderr.write(`lanes throttle: freemem fallback failed: ${err instanceof Error ? err.message : String(err)}\n`);
216
- return null;
268
+ if (deps.platform() === "darwin")
269
+ return readDarwinAvailableMemMb(deps);
217
270
  }
271
+ return readFreememMb(deps);
218
272
  }
219
273
  export function emptyThrottleState() {
220
274
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.102",
3
+ "version": "5.345.104",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {