@indigoai-us/hq-cli 5.108.9 → 5.108.10

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,8 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.108.10] — 2026-09-05
6
+
5
7
  ## [5.108.9] — 2026-09-05
6
8
 
7
9
  ## [5.108.8] — 2026-09-05
@@ -49,6 +49,25 @@ export declare const RUNTIME_PROBE_PREFIX = "hooks.runtime";
49
49
  export declare const POLICY_TRIGGER_LEDGER_RELPATH = "workspace/orchestrator/policy-trigger-state";
50
50
  /** The two lifecycle events `check-hq-hooks.sh` requires a command hook on. */
51
51
  export declare const REQUIRED_HOOK_EVENTS: readonly ["SessionStart", "PreToolUse"];
52
+ /**
53
+ * Default runtime marker path an agents-v2 (hermes) 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;
52
71
  /** Whether the ledger was found. Mirrors the script's `present`/`missing`. */
53
72
  export type LedgerState = "present" | "missing";
54
73
  /** Options for {@link reproduceCheckHqHooks}. */
@@ -92,6 +111,43 @@ export interface HookLoadReproduction {
92
111
  * exception, so the probe degrades exactly like the defensively-written script.
93
112
  */
94
113
  export declare function reproduceCheckHqHooks(opts: ReproduceOptions): HookLoadReproduction;
114
+ /** Inputs for the agents-v2 attestation helpers. */
115
+ export interface AgentsV2AttestationOptions {
116
+ /** Absolute HQ tree root. */
117
+ hqRoot: string;
118
+ /** Optional session scope for the ledger check (the `--session-id` flag). */
119
+ sessionId?: string;
120
+ /** Environment to read (marker path, freshness window). Default: process.env. */
121
+ env?: NodeJS.ProcessEnv;
122
+ }
123
+ /**
124
+ * Whether the runtime is agents-v2. Two signals, either sufficient — the same
125
+ * two `hq_runtime_mode` uses: the runtime marker
126
+ * (`HQ_RUNTIME_MARKER_FILE`, default {@link HQ_RUNTIME_MARKER_DEFAULT}) reads
127
+ * `runtimeMode == "agents-v2"`, OR the on-box adapter is installed under the
128
+ * tree at {@link AGENTS_V2_ADAPTER_RELPATH}.
129
+ */
130
+ export declare function isAgentsV2Runtime(hqRoot: string, env?: NodeJS.ProcessEnv): boolean;
131
+ /**
132
+ * Whether `.claude/settings.json` wires the on-box agents-v2 hook adapter — a
133
+ * raw substring match on the file, exactly like the shell's
134
+ * `grep -q 'hq-agents-v2-hook-adapter\.sh'` in `hq_settings_wires_v2_adapter`.
135
+ */
136
+ export declare function settingsWireV2Adapter(hqRoot: string): boolean;
137
+ /**
138
+ * Whether a policy-trigger ledger evidencing a live agents-v2 turn is present:
139
+ * the exact session's ledger when a session id is given (session identity
140
+ * implies freshness), otherwise any ledger modified within the freshness window
141
+ * so a long-dead tree cannot self-attest off a stale file. Mirrors
142
+ * `hq_v2_ledger_present`.
143
+ */
144
+ export declare function v2LedgerPresent(opts: AgentsV2AttestationOptions): boolean;
145
+ /**
146
+ * All three agents-v2 self-attestation conditions. Used only to GRANT PASS to a
147
+ * hermes box that host detection leaves platform-unknown; never to withhold it.
148
+ * The exact conjunction of `agents_v2_attested` in `check-hq-hooks.sh`.
149
+ */
150
+ export declare function agentsV2Attested(opts: AgentsV2AttestationOptions): boolean;
95
151
  /**
96
152
  * The runtime-probe check family entry. Reproduces the script verdict, then
97
153
  * renders it as a single platform-aware doctor result. See the module header for
@@ -51,6 +51,25 @@ export const RUNTIME_PROBE_PREFIX = "hooks.runtime";
51
51
  export const POLICY_TRIGGER_LEDGER_RELPATH = "workspace/orchestrator/policy-trigger-state";
52
52
  /** The two lifecycle events `check-hq-hooks.sh` requires a command hook on. */
53
53
  export const REQUIRED_HOOK_EVENTS = ["SessionStart", "PreToolUse"];
54
+ /**
55
+ * Default runtime marker path an agents-v2 (hermes) box writes its runtime mode
56
+ * to. Overridable via `HQ_RUNTIME_MARKER_FILE`, exactly as the shell reads
57
+ * `${HQ_RUNTIME_MARKER_FILE:-/var/lib/hq-agent/runtime.json}`.
58
+ */
59
+ export const HQ_RUNTIME_MARKER_DEFAULT = "/var/lib/hq-agent/runtime.json";
60
+ /**
61
+ * Path (relative to the HQ root) of the on-box agents-v2 hook adapter. Its
62
+ * presence under the tree is one of the two signals that the runtime is
63
+ * agents-v2, mirroring `hq_runtime_mode` in `check-hq-hooks.sh`.
64
+ */
65
+ export const AGENTS_V2_ADAPTER_RELPATH = ".agents-v2-hooks/hq-agents-v2-hook-adapter.sh";
66
+ /**
67
+ * Default hours a policy-trigger ledger may age and still evidence a live
68
+ * agents-v2 turn when no exact session id is given. Overridable via
69
+ * `HQ_V2_LEDGER_MAX_AGE_HOURS`, matching the shell's
70
+ * `HQ_V2_LEDGER_MAX_AGE_HOURS="${HQ_V2_LEDGER_MAX_AGE_HOURS:-24}"`.
71
+ */
72
+ export const HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT = 24;
54
73
  /**
55
74
  * Faithfully reproduce `core/scripts/check-hq-hooks.sh --require-ledger` in
56
75
  * TypeScript. The checks, in the script's order:
@@ -110,6 +129,117 @@ export function reproduceCheckHqHooks(opts) {
110
129
  }
111
130
  return { ok: issues.length === 0, ledgerState, issues };
112
131
  }
132
+ /**
133
+ * Whether the runtime is agents-v2. Two signals, either sufficient — the same
134
+ * two `hq_runtime_mode` uses: the runtime marker
135
+ * (`HQ_RUNTIME_MARKER_FILE`, default {@link HQ_RUNTIME_MARKER_DEFAULT}) reads
136
+ * `runtimeMode == "agents-v2"`, OR the on-box adapter is installed under the
137
+ * tree at {@link AGENTS_V2_ADAPTER_RELPATH}.
138
+ */
139
+ export function isAgentsV2Runtime(hqRoot, env = process.env) {
140
+ const markerPath = env.HQ_RUNTIME_MARKER_FILE?.trim() || HQ_RUNTIME_MARKER_DEFAULT;
141
+ if (readRuntimeMarkerMode(markerPath) === "agents-v2")
142
+ return true;
143
+ return isFile(path.join(hqRoot, ...AGENTS_V2_ADAPTER_RELPATH.split("/")));
144
+ }
145
+ /** The `runtimeMode` field of the runtime marker JSON, or null when unreadable. */
146
+ function readRuntimeMarkerMode(markerPath) {
147
+ let raw;
148
+ try {
149
+ raw = fs.readFileSync(markerPath, "utf8");
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ try {
155
+ const parsed = JSON.parse(raw);
156
+ return typeof parsed.runtimeMode === "string" ? parsed.runtimeMode : null;
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ }
162
+ /**
163
+ * Whether `.claude/settings.json` wires the on-box agents-v2 hook adapter — a
164
+ * raw substring match on the file, exactly like the shell's
165
+ * `grep -q 'hq-agents-v2-hook-adapter\.sh'` in `hq_settings_wires_v2_adapter`.
166
+ */
167
+ export function settingsWireV2Adapter(hqRoot) {
168
+ let raw;
169
+ try {
170
+ raw = fs.readFileSync(path.join(hqRoot, ".claude", "settings.json"), "utf8");
171
+ }
172
+ catch {
173
+ return false;
174
+ }
175
+ return raw.includes("hq-agents-v2-hook-adapter.sh");
176
+ }
177
+ /**
178
+ * Whether a policy-trigger ledger evidencing a live agents-v2 turn is present:
179
+ * the exact session's ledger when a session id is given (session identity
180
+ * implies freshness), otherwise any ledger modified within the freshness window
181
+ * so a long-dead tree cannot self-attest off a stale file. Mirrors
182
+ * `hq_v2_ledger_present`.
183
+ */
184
+ export function v2LedgerPresent(opts) {
185
+ const env = opts.env ?? process.env;
186
+ const dir = path.join(opts.hqRoot, ...POLICY_TRIGGER_LEDGER_RELPATH.split("/"));
187
+ if (!isDir(dir))
188
+ return false;
189
+ if (opts.sessionId) {
190
+ return isFile(path.join(dir, `${opts.sessionId}.txt`));
191
+ }
192
+ const cutoffMs = Date.now() - resolveMaxLedgerAgeHours(env) * 60 * 60 * 1000;
193
+ return ledgerDirHasFreshTxt(dir, cutoffMs);
194
+ }
195
+ /** The freshness window in hours, from the env override or the default. */
196
+ function resolveMaxLedgerAgeHours(env) {
197
+ const raw = env.HQ_V2_LEDGER_MAX_AGE_HOURS?.trim();
198
+ if (!raw)
199
+ return HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT;
200
+ const parsed = Number.parseInt(raw, 10);
201
+ return Number.isFinite(parsed) && parsed >= 0
202
+ ? parsed
203
+ : HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT;
204
+ }
205
+ /** True when any `*.txt` under `dir` (recursive) was modified at/after `cutoffMs`. */
206
+ function ledgerDirHasFreshTxt(dir, cutoffMs) {
207
+ let entries;
208
+ try {
209
+ entries = fs.readdirSync(dir, { withFileTypes: true });
210
+ }
211
+ catch {
212
+ return false;
213
+ }
214
+ for (const entry of entries) {
215
+ const full = path.join(dir, entry.name);
216
+ if (entry.isDirectory()) {
217
+ if (ledgerDirHasFreshTxt(full, cutoffMs))
218
+ return true;
219
+ }
220
+ else if (entry.isFile() && entry.name.endsWith(".txt")) {
221
+ try {
222
+ if (fs.statSync(full).mtimeMs >= cutoffMs)
223
+ return true;
224
+ }
225
+ catch {
226
+ // Unreadable entry: ignore, keep scanning.
227
+ }
228
+ }
229
+ }
230
+ return false;
231
+ }
232
+ /**
233
+ * All three agents-v2 self-attestation conditions. Used only to GRANT PASS to a
234
+ * hermes box that host detection leaves platform-unknown; never to withhold it.
235
+ * The exact conjunction of `agents_v2_attested` in `check-hq-hooks.sh`.
236
+ */
237
+ export function agentsV2Attested(opts) {
238
+ const env = opts.env ?? process.env;
239
+ return (isAgentsV2Runtime(opts.hqRoot, env) &&
240
+ settingsWireV2Adapter(opts.hqRoot) &&
241
+ v2LedgerPresent({ ...opts, env }));
242
+ }
113
243
  /**
114
244
  * The runtime-probe check family entry. Reproduces the script verdict, then
115
245
  * renders it as a single platform-aware doctor result. See the module header for
@@ -122,6 +252,26 @@ export function checkRuntimeProbe(context) {
122
252
  const checkId = `${RUNTIME_PROBE_PREFIX}.enforcement`;
123
253
  const target = POLICY_TRIGGER_LEDGER_RELPATH;
124
254
  const scope = sessionId ? ` for session ${sessionId}` : "";
255
+ // agents-v2 (hermes) self-attestation. hq doctor leaves the hermes host
256
+ // platform-unknown and, on an unknown host, the probe would report UNKNOWN
257
+ // below. But the on-box adapter provably wrote the ledger through the same
258
+ // .claude hooks, so grant PASS — and report platform "agents-v2" — when, and
259
+ // only when, the runtime is agents-v2, settings wire the on-box adapter, and a
260
+ // ledger exists (the exact session's under --session-id; otherwise any ledger
261
+ // fresh within the window). Requires the on-box marker/adapter, so this never
262
+ // changes the verdict for any other host. See agentsV2Attested().
263
+ if (agentsV2Attested({ hqRoot, sessionId })) {
264
+ return [
265
+ {
266
+ status: "PASS",
267
+ checkId,
268
+ target,
269
+ message: `Host platform is agents-v2 (hermes fleet): the on-box adapter wrote the ` +
270
+ `policy-trigger ledger through the same .claude hooks, so hook dispatch was ` +
271
+ `observed this session — the ledger has an entry${scope}.`,
272
+ },
273
+ ];
274
+ }
125
275
  const repro = reproduceCheckHqHooks({ hqRoot, sessionId });
126
276
  // Unknown host: the label cannot be trusted, so live enforcement cannot be
127
277
  // verified in either direction. UNKNOWN (never PASS or FAIL) is the honest
@@ -332,4 +482,12 @@ function isFile(file) {
332
482
  return false;
333
483
  }
334
484
  }
485
+ function isDir(dir) {
486
+ try {
487
+ return fs.statSync(dir).isDirectory();
488
+ }
489
+ catch {
490
+ return false;
491
+ }
492
+ }
335
493
  //# sourceMappingURL=runtime-probe.js.map
@@ -74,6 +74,22 @@ export interface DeriveVerdictOptions {
74
74
  * inline script treats a missing ledger — regardless of platform.
75
75
  */
76
76
  requireLedger?: boolean;
77
+ /**
78
+ * Whether this is an attested agents-v2 (hermes) box: the runtime is
79
+ * agents-v2, `.claude/settings.json` wires the on-box adapter, and a
80
+ * policy-trigger ledger exists (the exact session's when a session id is
81
+ * given; otherwise a ledger fresh within the freshness window). When true,
82
+ * OBSERVED is granted even though the doctor's runtime check did not report
83
+ * PASS — because hq doctor leaves the hermes host platform-unknown, yet the
84
+ * on-box adapter provably wrote the ledger through the same .claude hooks.
85
+ *
86
+ * This is the exact twin of the `agents_v2_attested` override in
87
+ * `render_from_doctor` (`core/scripts/check-hq-hooks.sh`). Establish it with
88
+ * {@link agentsV2Attested} in `checks/runtime-probe.ts` — the same three
89
+ * signals the shell's `agents_v2_attested` reads. It can only GRANT OBSERVED,
90
+ * never withhold it, so every non-agents-v2 caller is unaffected.
91
+ */
92
+ agentsV2Attested?: boolean;
77
93
  }
78
94
  /**
79
95
  * Derive the check-hq-hooks.sh verdict from a `hq doctor --json` document,
@@ -80,7 +80,18 @@ export function deriveCheckHqHooksVerdict(doc, options = {}) {
80
80
  // for --require-ledger, matching the inline script's "fail on a missing
81
81
  // ledger" contract.
82
82
  const runtimeResult = doc.results.find((result) => result.checkId === RUNTIME_ENFORCEMENT_CHECK_ID);
83
- const runtime = observeRuntime(runtimeResult?.status, requireLedger);
83
+ let runtime = observeRuntime(runtimeResult?.status, requireLedger);
84
+ // agents-v2 self-attestation: hq doctor leaves the hermes host
85
+ // platform-unknown and so does not report the runtime check as PASS, but the
86
+ // on-box adapter provably wrote the ledger through the same .claude hooks.
87
+ // Grant the identical OBSERVED verdict rather than relaying the host-unknown
88
+ // status as a failure. Mirrors render_from_doctor()'s agents_v2_attested
89
+ // override in check-hq-hooks.sh; only ever grants OBSERVED, never withholds it.
90
+ if (requireLedger &&
91
+ runtime !== "OBSERVED" &&
92
+ options.agentsV2Attested === true) {
93
+ runtime = "OBSERVED";
94
+ }
84
95
  if (requireLedger && runtime !== "OBSERVED") {
85
96
  messages.push(runtimeResult?.message ??
86
97
  "policy-trigger ledger was not found under workspace/orchestrator/policy-trigger-state");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.108.9",
3
+ "version": "5.108.10",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {