@indigoai-us/hq-cli 5.342.6 → 5.342.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.
- package/CHANGELOG.md +7 -0
- package/assets/scaffold/core/scripts/read-policy-frontmatter.sh +15 -8
- package/dist/commands/core-policy-frontmatter.js +47 -26
- package/dist/commands/daemon.d.ts +2 -2
- package/dist/commands/daemon.js +16 -3
- package/dist/lib/daemon/run.d.ts +2 -0
- package/dist/lib/daemon/run.js +3 -0
- package/dist/lib/lanes/watcher.js +12 -15
- package/dist/sentry.d.ts +2 -0
- package/dist/sentry.js +4 -0
- package/dist/utils/resident-package-preload.d.ts +1 -1
- package/dist/utils/resident-package-preload.js +25 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.342.8] — 2026-10-04
|
|
6
|
+
- Keep blocker escalation failures visible on the lane and stop retrying the same failed fallback every watcher turn.
|
|
7
|
+
- Release the daemon's hq-cli package-use lease after all reachable lazy runtime modules preload successfully, allowing CLI updates while preserving the existing restart-on-update path.
|
|
8
|
+
|
|
9
|
+
## [5.342.7] — 2026-10-04
|
|
10
|
+
- Batch policy frontmatter reads into one `hq core policy frontmatter` invocation while preserving single-file output.
|
|
11
|
+
|
|
5
12
|
## [5.342.6] — 2026-10-04
|
|
6
13
|
- Add `hq mesh project member add` to add an active company member to a project.
|
|
7
14
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# read-policy-frontmatter.sh — Extract YAML frontmatter from a policy file
|
|
3
3
|
#
|
|
4
4
|
# Usage: bash core/scripts/read-policy-frontmatter.sh <policy-file>
|
|
5
|
+
# bash core/scripts/read-policy-frontmatter.sh <policy-file> <policy-file> ...
|
|
5
6
|
#
|
|
6
7
|
# Returns only the YAML frontmatter block (between the first two --- markers),
|
|
7
8
|
# skipping the policy body. Used by /startwork, /plan, /brainstorm, /run commands
|
|
@@ -20,13 +21,19 @@ if [ $# -lt 1 ]; then
|
|
|
20
21
|
exit 1
|
|
21
22
|
fi
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
INDEX=0
|
|
25
|
+
for FILE in "$@"; do
|
|
26
|
+
if [ ! -r "$FILE" ]; then
|
|
27
|
+
echo "ERROR: File not readable: $FILE" >&2
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
24
30
|
|
|
25
|
-
if [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
if [ "$INDEX" -gt 0 ]; then
|
|
32
|
+
printf '\n# --- policy-file: %s ---\n' "$FILE"
|
|
33
|
+
fi
|
|
34
|
+
INDEX=$((INDEX + 1))
|
|
29
35
|
|
|
30
|
-
# Extract frontmatter: lines between first two --- markers.
|
|
31
|
-
# c counts --- markers; print lines only while inside the frontmatter (c==1).
|
|
32
|
-
awk '/^---$/{c++; next} c==1' "$FILE"
|
|
36
|
+
# Extract frontmatter: lines between first two --- markers.
|
|
37
|
+
# c counts --- markers; print lines only while inside the frontmatter (c==1).
|
|
38
|
+
awk '/^---$/{c++; next} c==1' "$FILE"
|
|
39
|
+
done
|
|
@@ -2,37 +2,58 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { usesGnuAwk } from '../lib/core-utils/awk-diagnostics.js';
|
|
4
4
|
const USAGE = Buffer.from('USAGE: read-policy-frontmatter.sh <policy-file>\n');
|
|
5
|
+
const MULTI_FILE_SEPARATOR = (file) => Buffer.from(`\n# --- policy-file: ${file} ---\n`);
|
|
5
6
|
/** Byte-preserving port of core/scripts/read-policy-frontmatter.sh. */
|
|
6
7
|
export function runPolicyFrontmatter(args, cwd, pathValue = process.env.PATH, platform = process.platform) {
|
|
7
|
-
|
|
8
|
-
if (first === undefined)
|
|
8
|
+
if (args.length === 0)
|
|
9
9
|
return { code: 1, stdout: Buffer.alloc(0), stderr: USAGE };
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
:
|
|
25
|
-
|
|
10
|
+
const output = [];
|
|
11
|
+
const diagnostics = [];
|
|
12
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
13
|
+
const file = args[index];
|
|
14
|
+
const filePath = path.isAbsolute(file) ? file : path.resolve(cwd, file);
|
|
15
|
+
try {
|
|
16
|
+
fs.accessSync(filePath, fs.constants.R_OK);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
const code = error.code;
|
|
20
|
+
if (code === 'ENOENT' || code === 'ENOTDIR' || code === 'EACCES' || code === 'ELOOP') {
|
|
21
|
+
return {
|
|
22
|
+
code: 1,
|
|
23
|
+
stdout: Buffer.concat(output),
|
|
24
|
+
stderr: Buffer.concat([...diagnostics, Buffer.from(`ERROR: File not readable: ${file}\n`)]),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
26
28
|
}
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
29
|
+
if (index > 0)
|
|
30
|
+
output.push(MULTI_FILE_SEPARATOR(file));
|
|
31
|
+
let source;
|
|
32
|
+
try {
|
|
33
|
+
source = fs.readFileSync(filePath);
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
+
catch (error) {
|
|
36
|
+
const code = error.code;
|
|
37
|
+
if (code === 'EISDIR') {
|
|
38
|
+
if (usesGnuAwk(pathValue, platform)) {
|
|
39
|
+
diagnostics.push(Buffer.from(`awk: warning: command line argument \`${file}' is a directory: skipped\n`));
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (code === 'ENOENT' || code === 'ENOTDIR' || code === 'EACCES' || code === 'ELOOP') {
|
|
44
|
+
return {
|
|
45
|
+
code: 1,
|
|
46
|
+
stdout: Buffer.concat(output),
|
|
47
|
+
stderr: Buffer.concat([...diagnostics, Buffer.from(`ERROR: File not readable: ${file}\n`)]),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
output.push(extractFrontmatter(source, platform));
|
|
35
53
|
}
|
|
54
|
+
return { code: 0, stdout: Buffer.concat(output), stderr: Buffer.concat(diagnostics) };
|
|
55
|
+
}
|
|
56
|
+
function extractFrontmatter(source, platform) {
|
|
36
57
|
const output = [];
|
|
37
58
|
let markers = 0;
|
|
38
59
|
let start = 0;
|
|
@@ -53,6 +74,6 @@ export function runPolicyFrontmatter(args, cwd, pathValue = process.env.PATH, pl
|
|
|
53
74
|
break;
|
|
54
75
|
start = end + 1;
|
|
55
76
|
}
|
|
56
|
-
return
|
|
77
|
+
return Buffer.concat(output);
|
|
57
78
|
}
|
|
58
79
|
//# sourceMappingURL=core-policy-frontmatter.js.map
|
|
@@ -19,8 +19,8 @@ import type { HostApp } from "../lib/daemon/supervisor.js";
|
|
|
19
19
|
import { type SyncModeVaultClient } from "./sync-mode.js";
|
|
20
20
|
import type { RunCommand } from "../lib/service-manager/launchd.js";
|
|
21
21
|
type DaemonRunnerModule = typeof import("../lib/daemon/run.js");
|
|
22
|
-
/** Load
|
|
23
|
-
export declare function loadDaemonRunner(
|
|
22
|
+
/** Load every lazy daemon runtime module before releasing its package-use lease. */
|
|
23
|
+
export declare function loadDaemonRunner(release?: () => void, load?: () => Promise<DaemonRunnerModule>, preload?: () => Promise<void>, reportPreloadFailure?: (error: unknown) => void): Promise<DaemonRunnerModule["runDaemon"]>;
|
|
24
24
|
export interface DaemonCommandDeps {
|
|
25
25
|
env: NodeJS.ProcessEnv;
|
|
26
26
|
home: string;
|
package/dist/commands/daemon.js
CHANGED
|
@@ -35,10 +35,23 @@ import { requireAnywhereRuntime } from "./anywhere-runtime-flag.js";
|
|
|
35
35
|
import { hqdRequest, hqdSocketPath, HqdClientError } from "../lib/daemon/protocol.js";
|
|
36
36
|
import { parseStdinMachineCredentials } from "../lib/daemon/machine-login.js";
|
|
37
37
|
import { resolveDefaultHqRoot } from "../utils/cognito-session.js";
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload } from "../utils/package-use-lease.js";
|
|
39
|
+
import { preloadResidentPackageModules, reportResidentPackagePreloadFailure } from "../utils/resident-package-preload.js";
|
|
40
|
+
/** Load every lazy daemon runtime module before releasing its package-use lease. */
|
|
41
|
+
export async function loadDaemonRunner(release = releaseCurrentPackageUseLease, load = () => import("../lib/daemon/run.js"), preload = () => preloadResidentPackageModules("daemon"), reportPreloadFailure = reportResidentPackagePreloadFailure) {
|
|
40
42
|
const { runDaemon } = await load();
|
|
41
|
-
|
|
43
|
+
let preloadComplete = false;
|
|
44
|
+
await releasePackageUseLeaseAfterResidentPreload(preload, reportPreloadFailure, () => {
|
|
45
|
+
preloadComplete = true;
|
|
46
|
+
});
|
|
47
|
+
return (options) => runDaemon({
|
|
48
|
+
...options,
|
|
49
|
+
onStartupReadsComplete: () => {
|
|
50
|
+
options.onStartupReadsComplete?.();
|
|
51
|
+
if (preloadComplete)
|
|
52
|
+
release();
|
|
53
|
+
},
|
|
54
|
+
});
|
|
42
55
|
}
|
|
43
56
|
class DaemonCommandError extends Error {
|
|
44
57
|
}
|
package/dist/lib/daemon/run.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface RunDaemonOptions {
|
|
|
34
34
|
env?: NodeJS.ProcessEnv;
|
|
35
35
|
nodeBinary: string;
|
|
36
36
|
hqBinary: string;
|
|
37
|
+
/** Called after the initial supervisor plan has finished package-backed startup reads. */
|
|
38
|
+
onStartupReadsComplete?: () => void;
|
|
37
39
|
}
|
|
38
40
|
export declare function resolveSyncRunnerPath(): string | null;
|
|
39
41
|
/** The hosted desktop app uses this env value when planning daemon services. */
|
package/dist/lib/daemon/run.js
CHANGED
|
@@ -830,6 +830,9 @@ export async function runDaemon(opts) {
|
|
|
830
830
|
releaseDaemonLock(paths.lockPath);
|
|
831
831
|
resolve(1);
|
|
832
832
|
}
|
|
833
|
+
finally {
|
|
834
|
+
opts.onStartupReadsComplete?.();
|
|
835
|
+
}
|
|
833
836
|
});
|
|
834
837
|
}
|
|
835
838
|
/** Report an hqd op crash to the CLI's existing Sentry integration, tagged with the failing op. */
|
|
@@ -863,11 +863,6 @@ async function settleBlockerFreshness(hqRoot, lane, manifestTurnId, at, deps, me
|
|
|
863
863
|
retryUndeliverable = true;
|
|
864
864
|
escalationText = `Blocker freshness escalation for ${current.lane_id}: these blockers remain stale after consecutive turns: ${staleLabels.join(", ")}. Status file: ${resolution.statusPath}.`;
|
|
865
865
|
}
|
|
866
|
-
else if (failedFallback !== undefined) {
|
|
867
|
-
deliverFallback = true;
|
|
868
|
-
undeliverableReason = failedFallback.reason ?? prior?.undeliverable_reason ?? "The session senior inbox is unavailable";
|
|
869
|
-
escalationText = `Blocker freshness escalation for ${current.lane_id}: these blockers remain stale after consecutive turns: ${staleLabels.join(", ")}. Status file: ${resolution.statusPath}.`;
|
|
870
|
-
}
|
|
871
866
|
else if (prior !== undefined && blockerFreshnessFingerprint(prior.stale_keys.map((key, index) => ({ key, label: prior.stale_labels[index] ?? "" }))) !== fingerprint) {
|
|
872
867
|
escalatedFingerprint = undefined;
|
|
873
868
|
}
|
|
@@ -923,16 +918,18 @@ async function settleBlockerFreshness(hqRoot, lane, manifestTurnId, at, deps, me
|
|
|
923
918
|
logDecision({ event: "blocker_stale_escalation_fallback_failed", at, turn_id: turnId, fingerprint, error: message });
|
|
924
919
|
updated = retryUpdateLane(hqRoot, lane.lane_id, "watcher", (current) => {
|
|
925
920
|
const state = current.blocker_freshness_state;
|
|
926
|
-
const
|
|
927
|
-
if (retryableState !== undefined) {
|
|
928
|
-
delete retryableState.undeliverable_fingerprint;
|
|
929
|
-
delete retryableState.undeliverable_reason;
|
|
930
|
-
}
|
|
921
|
+
const failureReason = `${undeliverableReason ?? `Session ${lane.senior.id ?? "unknown"} has no monitor-session inbox`}; lane inbox fallback failed: ${message}`;
|
|
931
922
|
return {
|
|
932
923
|
...current,
|
|
933
|
-
...(
|
|
924
|
+
...(state !== undefined ? {
|
|
925
|
+
blocker_freshness_state: {
|
|
926
|
+
...state,
|
|
927
|
+
undeliverable_fingerprint: fingerprint,
|
|
928
|
+
undeliverable_reason: failureReason,
|
|
929
|
+
},
|
|
930
|
+
} : {}),
|
|
934
931
|
status_gate_history: (current.status_gate_history ?? []).map((entry) => entry.event === "blocker_stale_escalation_undeliverable" && entry.fingerprint === fingerprint
|
|
935
|
-
? { ...entry, delivery_failed: true }
|
|
932
|
+
? { ...entry, delivery_failed: true, reason: failureReason }
|
|
936
933
|
: entry),
|
|
937
934
|
};
|
|
938
935
|
});
|
|
@@ -1083,12 +1080,12 @@ function findBlockerFreshnessSessionInbox(hqRoot, sessionId) {
|
|
|
1083
1080
|
.filter((sessionDir) => fs.existsSync(sessionDir));
|
|
1084
1081
|
}
|
|
1085
1082
|
catch (error) {
|
|
1086
|
-
return { reason: `Could not resolve the monitor inbox for session
|
|
1083
|
+
return { reason: `Could not resolve the monitor inbox for session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
1087
1084
|
}
|
|
1088
1085
|
if (candidates.length !== 1) {
|
|
1089
1086
|
return { reason: candidates.length === 0
|
|
1090
|
-
?
|
|
1091
|
-
:
|
|
1087
|
+
? `No monitor-session inbox is registered for session ${sessionId}`
|
|
1088
|
+
: `Session ${sessionId} matches more than one monitor-session inbox` };
|
|
1092
1089
|
}
|
|
1093
1090
|
return { inbox: path.join(candidates[0], "dropbox") };
|
|
1094
1091
|
}
|
package/dist/sentry.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ErrorEvent, EventHint, NodeOptions } from "@sentry/node";
|
|
2
2
|
type SentryModule = typeof import("@sentry/node");
|
|
3
|
+
/** Load the SDK through the same resolver used by the lazy Sentry facade. */
|
|
4
|
+
export declare function preloadSentrySdk(): void;
|
|
3
5
|
/**
|
|
4
6
|
* The command graph touches the Sentry facade on normal startup for a
|
|
5
7
|
* breadcrumb and a final flush. Keep those operations inert until the SDK
|
package/dist/sentry.js
CHANGED
|
@@ -24,6 +24,10 @@ function loadSentryModule() {
|
|
|
24
24
|
sentryModule ??= requireSentry("@sentry/node");
|
|
25
25
|
return sentryModule;
|
|
26
26
|
}
|
|
27
|
+
/** Load the SDK through the same resolver used by the lazy Sentry facade. */
|
|
28
|
+
export function preloadSentrySdk() {
|
|
29
|
+
loadSentryModule();
|
|
30
|
+
}
|
|
27
31
|
/**
|
|
28
32
|
* The command graph touches the Sentry facade on normal startup for a
|
|
29
33
|
* breadcrumb and a final flush. Keep those operations inert until the SDK
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ResidentPackagePath = "bot" | "mcp";
|
|
1
|
+
export type ResidentPackagePath = "bot" | "mcp" | "daemon";
|
|
2
2
|
/** Package imports reachable lazily from each resident command's runtime graph. */
|
|
3
3
|
export declare const RESIDENT_PACKAGE_MODULES: Record<ResidentPackagePath, readonly string[]>;
|
|
4
4
|
export declare class ResidentPackagePreloadError extends Error {
|
|
@@ -2,18 +2,42 @@
|
|
|
2
2
|
export const RESIDENT_PACKAGE_MODULES = {
|
|
3
3
|
bot: ["../lib/flag-registry.js", "../commands/access.js", "../sentry.js", "open"],
|
|
4
4
|
mcp: ["../lib/mcp/tools.js", "../lib/mcp/prompts.js"],
|
|
5
|
+
daemon: [
|
|
6
|
+
"../sentry.js",
|
|
7
|
+
"../utils/cognito-session.js",
|
|
8
|
+
"../lib/daemon/mcp-oauth.js",
|
|
9
|
+
"@modelcontextprotocol/sdk/client/index.js",
|
|
10
|
+
"@modelcontextprotocol/sdk/client/streamableHttp.js",
|
|
11
|
+
"../utils/staged-npm-install.js",
|
|
12
|
+
"open",
|
|
13
|
+
],
|
|
5
14
|
};
|
|
6
15
|
const LOADERS = {
|
|
7
16
|
bot: [
|
|
8
17
|
() => import("../lib/flag-registry.js"),
|
|
9
18
|
() => import("../commands/access.js"),
|
|
10
|
-
() =>
|
|
19
|
+
async () => {
|
|
20
|
+
const { preloadSentrySdk } = await import("../sentry.js");
|
|
21
|
+
preloadSentrySdk();
|
|
22
|
+
},
|
|
11
23
|
() => import("open"),
|
|
12
24
|
],
|
|
13
25
|
mcp: [
|
|
14
26
|
() => import("../lib/mcp/tools.js"),
|
|
15
27
|
() => import("../lib/mcp/prompts.js"),
|
|
16
28
|
],
|
|
29
|
+
daemon: [
|
|
30
|
+
async () => {
|
|
31
|
+
const { preloadSentrySdk } = await import("../sentry.js");
|
|
32
|
+
preloadSentrySdk();
|
|
33
|
+
},
|
|
34
|
+
() => import("../utils/cognito-session.js"),
|
|
35
|
+
() => import("../lib/daemon/mcp-oauth.js"),
|
|
36
|
+
() => import("@modelcontextprotocol/sdk/client/index.js"),
|
|
37
|
+
() => import("@modelcontextprotocol/sdk/client/streamableHttp.js"),
|
|
38
|
+
() => import("../utils/staged-npm-install.js"),
|
|
39
|
+
() => import("open"),
|
|
40
|
+
],
|
|
17
41
|
};
|
|
18
42
|
export class ResidentPackagePreloadError extends Error {
|
|
19
43
|
moduleSpecifier;
|