@indigoai-us/hq-cli 5.345.22 → 5.345.24
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/ontology-garden.mjs +6 -0
- package/dist/commands/core-audit-log-fast.d.ts +2 -0
- package/dist/commands/core-audit-log-fast.js +12 -0
- package/dist/commands/core-delegate.d.ts +1 -0
- package/dist/commands/core-delegate.js +2 -2
- package/dist/commands/core-detect-stale-core-policy-mirror-fast.d.ts +2 -0
- package/dist/commands/core-detect-stale-core-policy-mirror-fast.js +5 -0
- package/dist/commands/core-dm-bind-fast.d.ts +2 -0
- package/dist/commands/core-dm-bind-fast.js +14 -0
- package/dist/commands/core-generate-workers-registry-fast.d.ts +2 -0
- package/dist/commands/core-generate-workers-registry-fast.js +5 -0
- package/dist/commands/core-git-pack-extension-check-fast.d.ts +2 -0
- package/dist/commands/core-git-pack-extension-check-fast.js +12 -0
- package/dist/commands/core-ontology-native.js +73 -5
- package/dist/commands/core-refresh-vault-access-fast.d.ts +2 -0
- package/dist/commands/core-refresh-vault-access-fast.js +12 -0
- package/dist/commands/core-refresh-vault-access-native.d.ts +1 -0
- package/dist/commands/core-refresh-vault-access-native.js +10 -5
- package/dist/commands/core-scan-packages-fast.d.ts +2 -0
- package/dist/commands/core-scan-packages-fast.js +5 -0
- package/dist/commands/core-share-suggestion-state-native.js +19 -2
- package/dist/commands/core-work-mesh-bind-trusted-fast.d.ts +2 -0
- package/dist/commands/core-work-mesh-bind-trusted-fast.js +12 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +21 -1
- package/dist/commands/core-work-mesh-bind-trusted-native.js +79 -7
- package/dist/fast-core.js +6 -3
- package/dist/fast-scaffold.d.ts +1 -1
- package/dist/fast-scaffold.js +101 -0
- package/dist/lib/daemon/flag.d.ts +2 -0
- package/dist/lib/daemon/flag.js +2 -1
- package/dist/lib/daemon/server.d.ts +2 -1
- package/dist/lib/daemon/server.js +38 -21
- package/dist/lib/daemon/session-registry.d.ts +1 -0
- package/dist/lib/daemon/session-registry.js +3 -0
- package/dist/lib/scan-packages/index.js +1 -1
- package/dist/utils/pack-contribution-links.d.ts +15 -0
- package/dist/utils/pack-contribution-links.js +33 -0
- package/dist/utils/pack-contributions.d.ts +2 -14
- package/dist/utils/pack-contributions.js +3 -55
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.24] — 2026-10-06
|
|
6
|
+
- Keep native core ontology, delegate-secret, share-suggestion-state, and trusted-bind behavior aligned with their scaffold scripts.
|
|
7
|
+
|
|
8
|
+
## [5.345.23] — 2026-10-06
|
|
9
|
+
- Route eight additional isolated native core commands through the fast CLI entry.
|
|
10
|
+
- hqd applies the runtime kill switch to new hook and MCP operations within the existing 60-second freshness bound. A local opt-out also disables the daemon, while a local opt-in cannot override an operator-off value.
|
|
11
|
+
|
|
5
12
|
## [5.345.22] — 2026-10-06
|
|
6
13
|
- Fix Claude quota holds so time-only reset messages keep accounts unavailable until the provider's stated UTC reset, and expose the hold state in host capacity output.
|
|
7
14
|
|
|
@@ -158,6 +158,12 @@ function listCandidates(dir) {
|
|
|
158
158
|
|
|
159
159
|
function main() {
|
|
160
160
|
const a = parseArgs(process.argv.slice(2));
|
|
161
|
+
const backrefThreshold = Number(process.env.HQ_ONTOLOGY_BACKREF_THRESHOLD || 50);
|
|
162
|
+
const backrefKeepInline = Number(process.env.HQ_ONTOLOGY_BACKREF_KEEP_INLINE || 25);
|
|
163
|
+
if (!(backrefThreshold > backrefKeepInline)) {
|
|
164
|
+
console.error(`ontology-garden: BACKREF_THRESHOLD (${backrefThreshold}) must be greater than BACKREF_KEEP_INLINE (${backrefKeepInline})`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
161
167
|
const co = path.join(a.root, "companies", a.company);
|
|
162
168
|
if (!fs.existsSync(co)) { console.error(`ontology-garden: no company ${a.company}`); process.exit(2); }
|
|
163
169
|
const ont = path.join(co, "ontology"), sig = path.join(co, "signals");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { runAuditLog } from "./core-audit-log-native.js";
|
|
2
|
+
export function runAuditLogFast(args, _stdin, _cwd, hqRoot) {
|
|
3
|
+
const result = runAuditLog(args, { root: hqRoot ?? process.cwd(), now: () => new Date() });
|
|
4
|
+
if (result.stdout)
|
|
5
|
+
process.stdout.write(result.stdout);
|
|
6
|
+
if (result.stderrBytes)
|
|
7
|
+
process.stderr.write(result.stderrBytes);
|
|
8
|
+
else if (result.stderr)
|
|
9
|
+
process.stderr.write(result.stderr);
|
|
10
|
+
return result.code;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=core-audit-log-fast.js.map
|
|
@@ -41,6 +41,7 @@ export declare function requireRecord(value: unknown, property: string): JsonRec
|
|
|
41
41
|
export declare function moveFile(source: string, target: string): void;
|
|
42
42
|
export declare function writeJsonAtomically(target: string, value: unknown, siblingTemp?: boolean): void;
|
|
43
43
|
export declare function delegateDiagnosticPath(argument: string): string;
|
|
44
|
+
export declare function scanSecretFiles(file: string): string[] | undefined;
|
|
44
45
|
/** Fast-entry adapter; family gating and shell fallback happen before import. */
|
|
45
46
|
export declare function runDelegateFast(args: readonly string[], _stdin: string, cwd: string, hqRoot: string | undefined, commandName: string): number;
|
|
46
47
|
/** Register the Wave 1a delegate commands and preserve the shell rollback path. */
|
|
@@ -703,7 +703,7 @@ function runRepo(args, context) {
|
|
|
703
703
|
writeOut(`${prefix} recorded branch state (pushed=${pushed}, dirty=${dirtyLines.length} files, accessVerified=${accessVerified})\n`);
|
|
704
704
|
return 0;
|
|
705
705
|
}
|
|
706
|
-
function scanSecretFiles(file) {
|
|
706
|
+
export function scanSecretFiles(file) {
|
|
707
707
|
let schema;
|
|
708
708
|
try {
|
|
709
709
|
schema = fs.readFileSync(file, "utf8");
|
|
@@ -715,7 +715,7 @@ function scanSecretFiles(file) {
|
|
|
715
715
|
throw error;
|
|
716
716
|
}
|
|
717
717
|
return [...new Set(schema.split(/\r?\n/)
|
|
718
|
-
.filter((line) => /^[A-
|
|
718
|
+
.filter((line) => /^[A-Z][A-Z0-9_]*(\/[A-Z][A-Z0-9_]+)*=/.test(line))
|
|
719
719
|
.map((line) => line.slice(0, line.indexOf("="))))].sort();
|
|
720
720
|
}
|
|
721
721
|
function isRegularFile(file) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { detectStaleCorePolicyMirror } from "../lib/core-utils/detect-stale-core-policy-mirror.js";
|
|
2
|
+
export function runDetectStaleCorePolicyMirrorFast(args, _stdin, _cwd, hqRoot) {
|
|
3
|
+
return detectStaleCorePolicyMirror(args, { root: hqRoot ?? process.cwd() });
|
|
4
|
+
}
|
|
5
|
+
//# sourceMappingURL=core-detect-stale-core-policy-mirror-fast.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createDmBindDeps, runDmBind } from "./core-dm-bind-native.js";
|
|
2
|
+
export async function runDmBindFast(args, stdin, cwd, hqRoot) {
|
|
3
|
+
const deps = createDmBindDeps(hqRoot ?? cwd, cwd, process.env, () => new Date());
|
|
4
|
+
deps.readStdin = async () => stdin;
|
|
5
|
+
const result = await runDmBind(args, deps);
|
|
6
|
+
if (result.stdout)
|
|
7
|
+
process.stdout.write(result.stdout);
|
|
8
|
+
if (result.stderrBytes)
|
|
9
|
+
process.stderr.write(result.stderrBytes);
|
|
10
|
+
else if (result.stderr)
|
|
11
|
+
process.stderr.write(result.stderr);
|
|
12
|
+
return result.code;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=core-dm-bind-fast.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { generateWorkersRegistry } from "../lib/workers-registry/index.js";
|
|
2
|
+
export function runGenerateWorkersRegistryFast(_args, _stdin, _cwd, hqRoot) {
|
|
3
|
+
return generateWorkersRegistry(hqRoot ?? process.cwd()).status;
|
|
4
|
+
}
|
|
5
|
+
//# sourceMappingURL=core-generate-workers-registry-fast.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { runGitPackExtensionCheck } from "./core-git-pack-extension-check-native.js";
|
|
2
|
+
export function runGitPackExtensionCheckFast(args, _stdin, cwd) {
|
|
3
|
+
const result = runGitPackExtensionCheck(args, { cwd, env: process.env });
|
|
4
|
+
if (result.stdout)
|
|
5
|
+
process.stdout.write(result.stdout);
|
|
6
|
+
if (result.stderrBytes)
|
|
7
|
+
process.stderr.write(result.stderrBytes);
|
|
8
|
+
else if (result.stderr)
|
|
9
|
+
process.stderr.write(result.stderr);
|
|
10
|
+
return result.code;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=core-git-pack-extension-check-fast.js.map
|
|
@@ -397,6 +397,52 @@ function writeGardenDoc(file, fm, body, dryRun) {
|
|
|
397
397
|
fs.writeFileSync(`${file}.tmp`, lines.join("\n"));
|
|
398
398
|
fs.renameSync(`${file}.tmp`, file);
|
|
399
399
|
}
|
|
400
|
+
const BACKREF_SUMMARY_MARKER = "<!-- backref-archive -->";
|
|
401
|
+
function readGardenArchiveIndex(file) {
|
|
402
|
+
if (!fs.existsSync(file))
|
|
403
|
+
return { signalIds: [], unreadable: false, stderr: "" };
|
|
404
|
+
try {
|
|
405
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
406
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && Array.isArray(parsed.signalIds)) {
|
|
407
|
+
return {
|
|
408
|
+
signalIds: (parsed.signalIds).filter((item) => typeof item === "string"),
|
|
409
|
+
unreadable: false,
|
|
410
|
+
stderr: "",
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
416
|
+
return { signalIds: [], unreadable: true, stderr: `ontology-garden: archive index unreadable at ${file}: ${detail}; skipping compaction\n` };
|
|
417
|
+
}
|
|
418
|
+
return { signalIds: [], unreadable: true, stderr: "" };
|
|
419
|
+
}
|
|
420
|
+
function compactGardenFactBody(body, slug, threshold, keepInline, archivedTotalBefore) {
|
|
421
|
+
const lines = body.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0);
|
|
422
|
+
const isBackref = (line) => /^- \[[a-z_]+\] .+ \(signal [0-9a-f]{12}\)\s*$/.test(line);
|
|
423
|
+
const backrefLines = lines.filter(isBackref);
|
|
424
|
+
const other = lines.filter((line) => !isBackref(line) && !line.includes(BACKREF_SUMMARY_MARKER));
|
|
425
|
+
if (backrefLines.length <= threshold)
|
|
426
|
+
return { body, archivedLines: [], archivedIds: [], stamp: "", changed: false };
|
|
427
|
+
const archiveCount = backrefLines.length - keepInline;
|
|
428
|
+
const archivedLines = backrefLines.slice(0, archiveCount);
|
|
429
|
+
const inlineLines = backrefLines.slice(archiveCount);
|
|
430
|
+
const archivedIds = archivedLines.map((line) => line.match(/\(signal ([0-9a-f]{12})\)$/)?.[1]).filter((id) => Boolean(id));
|
|
431
|
+
const total = archivedTotalBefore + archivedLines.length;
|
|
432
|
+
const stamp = new Date().toISOString().slice(0, 7);
|
|
433
|
+
const summary = `- ${BACKREF_SUMMARY_MARKER} _${total} older mention${total === 1 ? "" : "s"} archived in [history](./${slug}.history/${stamp}.md)_`;
|
|
434
|
+
return { body: [summary, ...other, ...inlineLines].join("\n"), archivedLines, archivedIds, stamp, changed: true };
|
|
435
|
+
}
|
|
436
|
+
function appendGardenArchive(file, isoDate, lines, dryRun) {
|
|
437
|
+
if (dryRun)
|
|
438
|
+
return;
|
|
439
|
+
const batch = `\n<!-- archived ${isoDate} -->\n${lines.join("\n")}\n`;
|
|
440
|
+
const previous = fs.existsSync(file) ? fs.readFileSync(file, "utf8").replace(/\s+$/, "") : "";
|
|
441
|
+
const next = previous ? `${previous}\n${batch}` : `# Back-reference archive\n${batch}`;
|
|
442
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
443
|
+
fs.writeFileSync(`${file}.tmp`, next);
|
|
444
|
+
fs.renameSync(`${file}.tmp`, file);
|
|
445
|
+
}
|
|
400
446
|
const slugify = (value) => value.toLowerCase().normalize("NFKD").replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
401
447
|
const isoNow = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
402
448
|
function listCandidates(directory) {
|
|
@@ -436,6 +482,11 @@ export function gardenCommand(args, defaultRoot) {
|
|
|
436
482
|
}
|
|
437
483
|
if (!company)
|
|
438
484
|
return err(2, "usage: ontology-garden.mjs --company <co> [--hq-root <p>] [--dry-run] [--json]\n");
|
|
485
|
+
const threshold = Number(process.env.HQ_ONTOLOGY_BACKREF_THRESHOLD || 50);
|
|
486
|
+
const keepInline = Number(process.env.HQ_ONTOLOGY_BACKREF_KEEP_INLINE || 25);
|
|
487
|
+
if (!(threshold > keepInline)) {
|
|
488
|
+
return err(1, `ontology-garden: BACKREF_THRESHOLD (${threshold}) must be greater than BACKREF_KEEP_INLINE (${keepInline})\n`);
|
|
489
|
+
}
|
|
439
490
|
const hqRoot = root || process.env.HQ_ROOT || defaultRoot;
|
|
440
491
|
const companyDir = path.join(hqRoot, "companies", company);
|
|
441
492
|
if (!fs.existsSync(companyDir))
|
|
@@ -445,6 +496,7 @@ export function gardenCommand(args, defaultRoot) {
|
|
|
445
496
|
const report = { company, entities_created: 0, entities_updated: 0, signals_promoted: 0, facts_written: 0, rejected: 0, candidates_processed: 0, dry_run: dryRun };
|
|
446
497
|
const rejected = [];
|
|
447
498
|
const moves = [];
|
|
499
|
+
let stderr = "";
|
|
448
500
|
const entities = new Map();
|
|
449
501
|
const nameIndex = new Map();
|
|
450
502
|
const entityDirectory = path.join(ontology, "entities");
|
|
@@ -563,8 +615,24 @@ export function gardenCommand(args, defaultRoot) {
|
|
|
563
615
|
const previous = fs.existsSync(factFile)
|
|
564
616
|
? readGardenDoc(factFile)
|
|
565
617
|
: { fm: { entity: slug, type: entity.type, audience_key: key }, body: "" };
|
|
566
|
-
|
|
567
|
-
|
|
618
|
+
const indexFile = path.join(path.dirname(factFile), `${slug}.history`, "_index.json");
|
|
619
|
+
const archiveIndex = readGardenArchiveIndex(indexFile);
|
|
620
|
+
stderr += archiveIndex.stderr;
|
|
621
|
+
const signalId = id.slice(0, 12);
|
|
622
|
+
if (!previous.body.includes(`signal ${signalId}`) && !archiveIndex.signalIds.includes(signalId)) {
|
|
623
|
+
let nextBody = [previous.body, line].filter(Boolean).join("\n");
|
|
624
|
+
const compact = compactGardenFactBody(nextBody, slug, threshold, keepInline, archiveIndex.signalIds.length);
|
|
625
|
+
if (compact.changed && !archiveIndex.unreadable) {
|
|
626
|
+
const archiveFile = path.join(path.dirname(factFile), `${slug}.history`, `${compact.stamp}.md`);
|
|
627
|
+
appendGardenArchive(archiveFile, new Date().toISOString().slice(0, 10), compact.archivedLines, dryRun);
|
|
628
|
+
if (!dryRun) {
|
|
629
|
+
const nextIndex = { version: 1, signalIds: [...archiveIndex.signalIds, ...compact.archivedIds] };
|
|
630
|
+
fs.mkdirSync(path.dirname(indexFile), { recursive: true });
|
|
631
|
+
fs.writeFileSync(indexFile, `${JSON.stringify(nextIndex)}\n`);
|
|
632
|
+
}
|
|
633
|
+
nextBody = compact.body;
|
|
634
|
+
}
|
|
635
|
+
writeGardenDoc(factFile, previous.fm, nextBody, dryRun);
|
|
568
636
|
report.facts_written += 1;
|
|
569
637
|
}
|
|
570
638
|
if (key === "company") {
|
|
@@ -598,9 +666,9 @@ export function gardenCommand(args, defaultRoot) {
|
|
|
598
666
|
fs.writeFileSync(path.join(ontology, ".last-run"), `${Date.now()}\n`);
|
|
599
667
|
}
|
|
600
668
|
}
|
|
601
|
-
return
|
|
602
|
-
|
|
603
|
-
|
|
669
|
+
return { code: 0, stdout: json
|
|
670
|
+
? `${JSON.stringify(report)}\n`
|
|
671
|
+
: `garden ${company}: +${report.entities_created} entities, ~${report.entities_updated} updated, +${report.signals_promoted} signals, +${report.facts_written} facts, ${report.rejected} rejected, ${report.candidates_processed} candidates${dryRun ? " (dry run)" : ""}\n`, stderr };
|
|
604
672
|
}
|
|
605
673
|
export function audienceGrant(args, root) {
|
|
606
674
|
let company = "";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createRefreshVaultAccessDependencies, runRefreshVaultAccess } from "./core-refresh-vault-access-native.js";
|
|
2
|
+
export function runRefreshVaultAccessFast(args, _stdin, cwd) {
|
|
3
|
+
const result = runRefreshVaultAccess(args, createRefreshVaultAccessDependencies(cwd, process.env, () => new Date()));
|
|
4
|
+
if (result.stdout)
|
|
5
|
+
process.stdout.write(result.stdout);
|
|
6
|
+
if (result.stderrBytes)
|
|
7
|
+
process.stderr.write(result.stderrBytes);
|
|
8
|
+
else if (result.stderr)
|
|
9
|
+
process.stderr.write(result.stderr);
|
|
10
|
+
return result.code;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=core-refresh-vault-access-fast.js.map
|
|
@@ -15,6 +15,7 @@ export type RefreshVaultAccessDependencies = {
|
|
|
15
15
|
hqAvailable: boolean;
|
|
16
16
|
runHq?: (args: string[], cwd: string) => HqResult;
|
|
17
17
|
};
|
|
18
|
+
export declare function createRefreshVaultAccessDependencies(cwd: string, env: NodeJS.ProcessEnv, now: () => Date): RefreshVaultAccessDependencies;
|
|
18
19
|
export declare function runRefreshVaultAccess(args: string[], deps: RefreshVaultAccessDependencies): UtilityOutput;
|
|
19
20
|
export declare function registerCoreRefreshVaultAccessNative(core: Command): void;
|
|
20
21
|
//# sourceMappingURL=core-refresh-vault-access-native.d.ts.map
|
|
@@ -59,6 +59,15 @@ function executableOnPath(name, value) {
|
|
|
59
59
|
}
|
|
60
60
|
return false;
|
|
61
61
|
}
|
|
62
|
+
export function createRefreshVaultAccessDependencies(cwd, env, now) {
|
|
63
|
+
return {
|
|
64
|
+
cwd,
|
|
65
|
+
env,
|
|
66
|
+
now,
|
|
67
|
+
hqAvailable: executableOnPath("hq", env.PATH),
|
|
68
|
+
runHq: (args, directory) => defaultHq(args, directory, env),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
62
71
|
function isHqRoot(root) {
|
|
63
72
|
const candidate = root.endsWith(path.sep) ? root.slice(0, -1) : root;
|
|
64
73
|
if (!candidate)
|
|
@@ -219,11 +228,7 @@ export function registerCoreRefreshVaultAccessNative(core) {
|
|
|
219
228
|
registerNativeUtility(core, {
|
|
220
229
|
name: "refresh-vault-access", asset: REFRESH_VAULT_ACCESS_ASSET, root: "cwd", summary: "Refresh the local vault access manifest",
|
|
221
230
|
resolveRoot: (cwd) => cwd,
|
|
222
|
-
run: (args, context) => runRefreshVaultAccess(args,
|
|
223
|
-
cwd: context.cwd, env: context.env, now: context.now,
|
|
224
|
-
hqAvailable: executableOnPath("hq", context.env.PATH),
|
|
225
|
-
runHq: (args, cwd) => defaultHq(args, cwd, context.env),
|
|
226
|
-
}),
|
|
231
|
+
run: (args, context) => runRefreshVaultAccess(args, createRefreshVaultAccessDependencies(context.cwd, context.env, context.now)),
|
|
227
232
|
});
|
|
228
233
|
}
|
|
229
234
|
//# sourceMappingURL=core-refresh-vault-access-native.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { scanPackages } from "../lib/scan-packages/index.js";
|
|
2
|
+
export function runScanPackagesFast(_args, _stdin, _cwd, hqRoot) {
|
|
3
|
+
return scanPackages(hqRoot ?? process.cwd(), { quiet: process.env.HQ_SCAN_QUIET === "1" }).status;
|
|
4
|
+
}
|
|
5
|
+
//# sourceMappingURL=core-scan-packages-fast.js.map
|
|
@@ -209,8 +209,14 @@ export function runShareSuggestionState(args, stdin, deps) {
|
|
|
209
209
|
try {
|
|
210
210
|
return JSON.parse(f.readFileSync(file, "utf8"));
|
|
211
211
|
}
|
|
212
|
-
catch {
|
|
213
|
-
|
|
212
|
+
catch (cause) {
|
|
213
|
+
const failure = new Error("share-suggestion-state JSON read failed");
|
|
214
|
+
failure.name = "ShareSuggestionStateReadError";
|
|
215
|
+
Object.assign(failure, {
|
|
216
|
+
shareSuggestionStateReadPath: file,
|
|
217
|
+
shareSuggestionStateCauseName: cause instanceof Error && cause.name ? cause.name : "Error",
|
|
218
|
+
});
|
|
219
|
+
throw failure;
|
|
214
220
|
}
|
|
215
221
|
};
|
|
216
222
|
const writeJson = (file, value) => {
|
|
@@ -454,6 +460,17 @@ export function runShareSuggestionState(args, stdin, deps) {
|
|
|
454
460
|
return { code: 0, stdout: "", stderr: "" };
|
|
455
461
|
}
|
|
456
462
|
catch (error) {
|
|
463
|
+
if (error instanceof Error && "shareSuggestionStateReadPath" in error) {
|
|
464
|
+
const failure = error;
|
|
465
|
+
const readPath = platform === "win32"
|
|
466
|
+
? path.win32.resolve(failure.shareSuggestionStateReadPath).replaceAll("\\", "/")
|
|
467
|
+
: failure.shareSuggestionStateReadPath;
|
|
468
|
+
return {
|
|
469
|
+
code: 3,
|
|
470
|
+
stdout: "",
|
|
471
|
+
stderr: `share-suggestion-state: unable to read ${readPath} (${failure.shareSuggestionStateCauseName})\n`,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
457
474
|
const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
458
475
|
return { code: 0, stdout: "", stderr: `share-suggestion-state: internal error\n${detail}\n` };
|
|
459
476
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createWorkMeshBindDeps, runWorkMeshBindTrusted } from "./core-work-mesh-bind-trusted-native.js";
|
|
2
|
+
export function runWorkMeshBindTrustedFast(args, _stdin, cwd, hqRoot) {
|
|
3
|
+
const result = runWorkMeshBindTrusted(args, createWorkMeshBindDeps(hqRoot ?? cwd, cwd, process.env, () => new Date()));
|
|
4
|
+
if (result.stdout)
|
|
5
|
+
process.stdout.write(result.stdout);
|
|
6
|
+
if (result.stderrBytes)
|
|
7
|
+
process.stderr.write(result.stderrBytes);
|
|
8
|
+
else if (result.stderr)
|
|
9
|
+
process.stderr.write(result.stderr);
|
|
10
|
+
return result.code;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=core-work-mesh-bind-trusted-fast.js.map
|
|
@@ -15,6 +15,7 @@ export type WorkMeshBindDeps = {
|
|
|
15
15
|
stdout: string;
|
|
16
16
|
stderr: string;
|
|
17
17
|
};
|
|
18
|
+
daemonWarningEnabled: boolean;
|
|
18
19
|
runHqSession: (root: string, args: string[], env: NodeJS.ProcessEnv) => {
|
|
19
20
|
code: number;
|
|
20
21
|
stdout: string;
|
|
@@ -25,8 +26,27 @@ export type WorkMeshBindDeps = {
|
|
|
25
26
|
tmpDir: string;
|
|
26
27
|
ulid: (now: Date) => string;
|
|
27
28
|
};
|
|
28
|
-
export declare const createWorkMeshBindDeps: (root: string, cwd: string, env: NodeJS.ProcessEnv, now: () => Date) => WorkMeshBindDeps;
|
|
29
|
+
export declare const createWorkMeshBindDeps: (root: string, cwd: string, env: NodeJS.ProcessEnv, now: () => Date, daemonWarningEnabled?: boolean) => WorkMeshBindDeps;
|
|
30
|
+
type DetachedSpawnOptions = {
|
|
31
|
+
cwd: string;
|
|
32
|
+
root: string;
|
|
33
|
+
env: NodeJS.ProcessEnv;
|
|
34
|
+
platform?: NodeJS.Platform;
|
|
35
|
+
existsSync?: (candidate: string) => boolean;
|
|
36
|
+
spawnProcess?: (command: string, args: string[], options: {
|
|
37
|
+
cwd: string;
|
|
38
|
+
env: NodeJS.ProcessEnv;
|
|
39
|
+
detached: boolean;
|
|
40
|
+
stdio: "ignore";
|
|
41
|
+
shell: boolean;
|
|
42
|
+
windowsVerbatimArguments?: boolean;
|
|
43
|
+
}) => {
|
|
44
|
+
unref: () => void;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export declare function spawnWorkMeshHqDetached(args: string[], options: DetachedSpawnOptions): void;
|
|
29
48
|
export declare function resolveWorkMeshBindSessionId(root: string, env: NodeJS.ProcessEnv, fileSystem?: typeof fs): string;
|
|
30
49
|
export declare function runWorkMeshBindTrusted(args: string[], deps: WorkMeshBindDeps): UtilityOutput;
|
|
31
50
|
export declare function registerCoreWorkMeshBindTrustedNative(core: Command): void;
|
|
51
|
+
export {};
|
|
32
52
|
//# sourceMappingURL=core-work-mesh-bind-trusted-native.d.ts.map
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
5
|
+
import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
|
|
5
6
|
import { generateUlid } from "../lib/mesh/live/ulid.js";
|
|
6
7
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
8
|
+
import { planResolvedCommandSpawn } from "../utils/windows-spawn.js";
|
|
7
9
|
import { registerNativeUtility } from "./core-native-utilities.js";
|
|
8
10
|
export const WORK_MESH_BIND_TRUSTED_ASSET = "core/scripts/work-mesh-live-bind-trusted.sh";
|
|
11
|
+
const WORK_MESH_DAEMON_WARNING_FLAG = "hooks.work-mesh-daemon-not-loaded-warning";
|
|
9
12
|
export const WORK_MESH_BIND_TRUSTED_HELP = `hq-core: public
|
|
10
13
|
US-011 — write session meta + reconcile with observation.trustedContext.
|
|
11
14
|
No --trusted CLI flag in hq-cli; trusted bind is the observation field.
|
|
@@ -14,21 +17,61 @@ set -euo pipefail
|
|
|
14
17
|
ROOT="\${HQ_ROOT:-\${CLAUDE_PROJECT_DIR:-}}"
|
|
15
18
|
if [ -z "$ROOT" ]; then
|
|
16
19
|
`;
|
|
17
|
-
export const createWorkMeshBindDeps = (root, cwd, env, now) => ({
|
|
20
|
+
export const createWorkMeshBindDeps = (root, cwd, env, now, daemonWarningEnabled = false) => ({
|
|
18
21
|
root, cwd, env, now, fs, pid: process.pid, tmpDir: env.TMPDIR || "/tmp", ulid: (date) => generateUlid(date.getTime()),
|
|
22
|
+
daemonWarningEnabled: daemonWarningEnabled
|
|
23
|
+
&& Boolean(env.HQ_FLAGS_API_URL?.trim())
|
|
24
|
+
&& /^cmp_[A-Za-z0-9]{3,128}$/.test(env.HQ_COMPANY_UID?.trim() ?? ""),
|
|
19
25
|
runHq: (args, childEnv) => {
|
|
20
|
-
const
|
|
26
|
+
const executable = "hq";
|
|
27
|
+
const resolvedExecutable = process.platform === "win32"
|
|
28
|
+
? resolveWindowsExecutable(executable, childEnv, (candidate) => {
|
|
29
|
+
try {
|
|
30
|
+
return fs.statSync(candidate).isFile();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}, cwd) ?? executable
|
|
36
|
+
: executable;
|
|
37
|
+
const plan = planResolvedCommandSpawn(resolvedExecutable, args, childEnv);
|
|
38
|
+
const result = spawnSync(plan.cmd, plan.args, {
|
|
39
|
+
cwd,
|
|
40
|
+
env: { ...childEnv, HQ_ROOT: root },
|
|
41
|
+
encoding: "utf8",
|
|
42
|
+
shell: plan.shell,
|
|
43
|
+
...(plan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
44
|
+
timeout: 2_000,
|
|
45
|
+
});
|
|
21
46
|
return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
|
|
22
47
|
},
|
|
23
48
|
runHqSession: (scriptRoot, args, childEnv) => {
|
|
24
49
|
const result = spawnSync("bash", [path.join(scriptRoot, "core/scripts/hq-session.sh"), ...args], { cwd, env: childEnv, encoding: "utf8" });
|
|
25
50
|
return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
|
|
26
51
|
},
|
|
27
|
-
spawnHq: (args, childEnv) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
spawnHq: (args, childEnv) => spawnWorkMeshHqDetached(args, {
|
|
53
|
+
cwd,
|
|
54
|
+
root,
|
|
55
|
+
env: childEnv,
|
|
56
|
+
}),
|
|
31
57
|
});
|
|
58
|
+
export function spawnWorkMeshHqDetached(args, options) {
|
|
59
|
+
const platform = options.platform ?? process.platform;
|
|
60
|
+
const childEnv = { ...options.env, HQ_ROOT: options.root };
|
|
61
|
+
const executable = platform === "win32"
|
|
62
|
+
? resolveWindowsExecutable("hq", childEnv, options.existsSync ?? fs.existsSync, options.cwd) ?? "hq"
|
|
63
|
+
: "hq";
|
|
64
|
+
const plan = planResolvedCommandSpawn(executable, args, childEnv, platform);
|
|
65
|
+
const child = (options.spawnProcess ?? spawn)(plan.cmd, plan.args, {
|
|
66
|
+
cwd: options.cwd,
|
|
67
|
+
env: childEnv,
|
|
68
|
+
detached: true,
|
|
69
|
+
stdio: "ignore",
|
|
70
|
+
shell: plan.shell,
|
|
71
|
+
...(plan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
72
|
+
});
|
|
73
|
+
child.unref();
|
|
74
|
+
}
|
|
32
75
|
export function resolveWorkMeshBindSessionId(root, env, fileSystem = fs) {
|
|
33
76
|
for (const key of ["HQ_SESSION_ID", "CLAUDE_CODE_SESSION_ID", "CLAUDE_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"]) {
|
|
34
77
|
const value = (env[key] ?? "").replace(/\s/g, "");
|
|
@@ -44,6 +87,16 @@ export function resolveWorkMeshBindSessionId(root, env, fileSystem = fs) {
|
|
|
44
87
|
}
|
|
45
88
|
}
|
|
46
89
|
function hasExecutable(deps, command) {
|
|
90
|
+
if (process.platform === "win32") {
|
|
91
|
+
return Boolean(resolveWindowsExecutable(command, deps.env, (candidate) => {
|
|
92
|
+
try {
|
|
93
|
+
return deps.fs.statSync(candidate).isFile();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}, deps.cwd));
|
|
99
|
+
}
|
|
47
100
|
for (const directory of (deps.env.PATH ?? "").split(path.delimiter)) {
|
|
48
101
|
if (!directory)
|
|
49
102
|
continue;
|
|
@@ -175,6 +228,21 @@ export function runWorkMeshBindTrusted(args, deps) {
|
|
|
175
228
|
}
|
|
176
229
|
if (deps.env.HQ_WORK_MESH_RECONCILE_STUB === "1")
|
|
177
230
|
return { code: 0, stdout: output.join(""), stderr };
|
|
231
|
+
if (deps.daemonWarningEnabled && hasExecutable(deps, "hq")) {
|
|
232
|
+
const status = deps.runHq(["mesh", "daemon", "status", "--json"], { ...deps.env, HQ_NO_UPDATE_CHECK: "1" });
|
|
233
|
+
if (status.code === 0) {
|
|
234
|
+
try {
|
|
235
|
+
const parsed = JSON.parse(status.stdout);
|
|
236
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
237
|
+
const daemon = parsed;
|
|
238
|
+
if (daemon.ok === true && daemon.running === false && typeof daemon.message === "string" && daemon.message.includes("installed (")) {
|
|
239
|
+
stderr += "Work Mesh daemon is installed but not loaded; start it with: hq mesh daemon install\n";
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch { /* the shell's jq test fails quiet on invalid status JSON */ }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
178
246
|
if (hasExecutable(deps, "hq") && deps.fs.existsSync(observationFile)) {
|
|
179
247
|
deps.spawnHq(["mesh", "context", "reconcile", "--observation-file", observationFile, "--machine"], childEnv);
|
|
180
248
|
}
|
|
@@ -184,7 +252,11 @@ export function registerCoreWorkMeshBindTrustedNative(core) {
|
|
|
184
252
|
registerNativeUtility(core, {
|
|
185
253
|
name: "work-mesh-bind-trusted", asset: WORK_MESH_BIND_TRUSTED_ASSET, root: "live", summary: "Bind trusted session context and reconcile it with Work Mesh",
|
|
186
254
|
resolveRoot: (_cwd, hqRoot) => resolveLiveRoot({ hqRoot }),
|
|
187
|
-
run: (args, context) =>
|
|
255
|
+
run: async (args, context) => {
|
|
256
|
+
const { fastFlagEnabled } = await import("../native-fast-route.js");
|
|
257
|
+
const daemonWarningEnabled = await fastFlagEnabled(WORK_MESH_DAEMON_WARNING_FLAG, context.env);
|
|
258
|
+
return runWorkMeshBindTrusted(args, createWorkMeshBindDeps(context.root, context.cwd, context.env, context.now, daemonWarningEnabled));
|
|
259
|
+
},
|
|
188
260
|
});
|
|
189
261
|
}
|
|
190
262
|
//# sourceMappingURL=core-work-mesh-bind-trusted-native.js.map
|
package/dist/fast-core.js
CHANGED
|
@@ -65,7 +65,7 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
|
|
|
65
65
|
worktreeScopeFixEnabled = await coreRrrWorktreeScopeFixFastFlagEnabled(process.env);
|
|
66
66
|
}
|
|
67
67
|
const shouldInjectRoot = useShell
|
|
68
|
-
? entry.shellHqRoot === "live" || (entry.shellHqRoot === "reconcile-default" &&
|
|
68
|
+
? entry.shellHqRoot === "cwd" || entry.shellHqRoot === "live" || (entry.shellHqRoot === "reconcile-default" &&
|
|
69
69
|
!parsed.args.includes("--hq-root") && !parsed.args.includes("--help") && !parsed.args.includes("-h"))
|
|
70
70
|
: entry.kind === "shell"
|
|
71
71
|
? true
|
|
@@ -76,7 +76,10 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
|
|
|
76
76
|
// its expected-user-error formatting remains authoritative.
|
|
77
77
|
let hqRoot;
|
|
78
78
|
const rootResolutionStartedAt = profileMark();
|
|
79
|
-
if (
|
|
79
|
+
if (useShell && entry.kind === "native" && entry.shellHqRoot === "cwd") {
|
|
80
|
+
hqRoot = process.cwd();
|
|
81
|
+
}
|
|
82
|
+
else if (shouldInjectRoot) {
|
|
80
83
|
const shortcutRoot = entry.name === "checkpoint-stop-gate" || entry.name === "session-journal"
|
|
81
84
|
? resolveExplicitFastLiveRoot(parsed.hqRootArg, process.env)
|
|
82
85
|
: undefined;
|
|
@@ -146,7 +149,7 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
|
|
|
146
149
|
? hqRoot ?? process.cwd()
|
|
147
150
|
: process.cwd();
|
|
148
151
|
const injectRoot = entry.kind === "shell" || entry.kind === "native" &&
|
|
149
|
-
(entry.shellHqRoot === "live" || entry.shellHqRoot === "reconcile-default" && shouldInjectRoot);
|
|
152
|
+
(entry.shellHqRoot === "cwd" || entry.shellHqRoot === "live" || entry.shellHqRoot === "reconcile-default" && shouldInjectRoot);
|
|
150
153
|
const childEnv = {
|
|
151
154
|
...(entry.name === "hq-session" ? { HQ_HQ_SESSION_NO_CLI: "1" } : {}),
|
|
152
155
|
...(entry.name === "migrate-policy-triggers"
|
package/dist/fast-scaffold.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export type FastScaffoldScript = {
|
|
|
39
39
|
/** Cwd used for a shell rollback companion. Defaults to the caller cwd. */
|
|
40
40
|
shellCwd?: "caller" | "live";
|
|
41
41
|
/** Root injection behavior for a shell rollback companion. */
|
|
42
|
-
shellHqRoot?: "none" | "live" | "reconcile-default";
|
|
42
|
+
shellHqRoot?: "none" | "cwd" | "live" | "reconcile-default";
|
|
43
43
|
/** Whether this handler consumes stdin. Shell companions inherit stdin directly. */
|
|
44
44
|
readsStdin?: boolean;
|
|
45
45
|
/** Bound bytes buffered from stdin; overflow is treated as empty input. */
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -432,6 +432,107 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
432
432
|
shellCwd: "caller",
|
|
433
433
|
shellHqRoot: name === "source-yaml-validate" ? "none" : "live",
|
|
434
434
|
})),
|
|
435
|
+
{
|
|
436
|
+
name: "audit-log",
|
|
437
|
+
kind: "native",
|
|
438
|
+
modulePath: "./commands/core-audit-log-fast.js",
|
|
439
|
+
exportName: "runAuditLogFast",
|
|
440
|
+
flagKey: "hq-core-native-fast-route",
|
|
441
|
+
familyFlagKey: "core-native-utilities",
|
|
442
|
+
asset: "core/scripts/audit-log.sh",
|
|
443
|
+
root: "live",
|
|
444
|
+
shellCwd: "live",
|
|
445
|
+
shellHqRoot: "live",
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "refresh-vault-access",
|
|
449
|
+
kind: "native",
|
|
450
|
+
modulePath: "./commands/core-refresh-vault-access-fast.js",
|
|
451
|
+
exportName: "runRefreshVaultAccessFast",
|
|
452
|
+
flagKey: "hq-core-native-fast-route",
|
|
453
|
+
familyFlagKey: "core-native-utilities",
|
|
454
|
+
asset: "core/scripts/refresh-vault-access.sh",
|
|
455
|
+
root: "cwd",
|
|
456
|
+
shellCwd: "caller",
|
|
457
|
+
shellHqRoot: "cwd",
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: "generate-workers-registry",
|
|
461
|
+
kind: "native",
|
|
462
|
+
modulePath: "./commands/core-generate-workers-registry-fast.js",
|
|
463
|
+
exportName: "runGenerateWorkersRegistryFast",
|
|
464
|
+
flagKey: "hq-core-native-fast-route",
|
|
465
|
+
root: "live",
|
|
466
|
+
asset: "core/scripts/generate-workers-registry.sh",
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: "dm-bind",
|
|
470
|
+
kind: "native",
|
|
471
|
+
modulePath: "./commands/core-dm-bind-fast.js",
|
|
472
|
+
exportName: "runDmBindFast",
|
|
473
|
+
flagKey: "hq-core-native-fast-route",
|
|
474
|
+
familyFlagKey: "core-native-utilities",
|
|
475
|
+
asset: "core/scripts/hq-dm-bind.sh",
|
|
476
|
+
root: "live",
|
|
477
|
+
shellCwd: "live",
|
|
478
|
+
shellHqRoot: "live",
|
|
479
|
+
readsStdinFor: (args) => {
|
|
480
|
+
if (args[0] !== "post")
|
|
481
|
+
return false;
|
|
482
|
+
const valueOptions = new Set(["--topic", "--under", "--to", "--title", "--state", "--line", "--next", "--ask"]);
|
|
483
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
484
|
+
if (args[index] === "-")
|
|
485
|
+
return true;
|
|
486
|
+
if (valueOptions.has(args[index] ?? ""))
|
|
487
|
+
index += 1;
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
},
|
|
491
|
+
requiresNodeNetworkCompatibility: true,
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
name: "git-pack-extension-check",
|
|
495
|
+
kind: "native",
|
|
496
|
+
modulePath: "./commands/core-git-pack-extension-check-fast.js",
|
|
497
|
+
exportName: "runGitPackExtensionCheckFast",
|
|
498
|
+
flagKey: "hq-core-native-fast-route",
|
|
499
|
+
familyFlagKey: "core-native-utilities",
|
|
500
|
+
asset: "core/scripts/git-pack-extension-check.sh",
|
|
501
|
+
root: "cwd",
|
|
502
|
+
shellCwd: "caller",
|
|
503
|
+
shellHqRoot: "cwd",
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "scan-packages",
|
|
507
|
+
kind: "native",
|
|
508
|
+
modulePath: "./commands/core-scan-packages-fast.js",
|
|
509
|
+
exportName: "runScanPackagesFast",
|
|
510
|
+
flagKey: "hq-core-native-fast-route",
|
|
511
|
+
asset: "core/scripts/scan-packages.sh",
|
|
512
|
+
root: "live",
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
name: "detect-stale-core-policy-mirror",
|
|
516
|
+
kind: "native",
|
|
517
|
+
modulePath: "./commands/core-detect-stale-core-policy-mirror-fast.js",
|
|
518
|
+
exportName: "runDetectStaleCorePolicyMirrorFast",
|
|
519
|
+
flagKey: "hq-core-native-fast-route",
|
|
520
|
+
asset: "core/scripts/detect-stale-core-policy-mirror.sh",
|
|
521
|
+
root: "live",
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: "work-mesh-bind-trusted",
|
|
525
|
+
kind: "native",
|
|
526
|
+
modulePath: "./commands/core-work-mesh-bind-trusted-fast.js",
|
|
527
|
+
exportName: "runWorkMeshBindTrustedFast",
|
|
528
|
+
flagKey: "hq-core-native-fast-route",
|
|
529
|
+
familyFlagKey: "core-native-utilities",
|
|
530
|
+
asset: "core/scripts/work-mesh-live-bind-trusted.sh",
|
|
531
|
+
root: "live",
|
|
532
|
+
shellCwd: "live",
|
|
533
|
+
shellHqRoot: "live",
|
|
534
|
+
requiresNodeNetworkCompatibility: true,
|
|
535
|
+
},
|
|
435
536
|
];
|
|
436
537
|
/**
|
|
437
538
|
* Parse the small, non-ambiguous `hq secrets env` surface for the gated entry
|
|
@@ -17,6 +17,8 @@ export interface DaemonFlagClient {
|
|
|
17
17
|
}
|
|
18
18
|
export interface ReadDaemonFlagOptions {
|
|
19
19
|
env?: NodeJS.ProcessEnv;
|
|
20
|
+
/** Controls how this read combines a local person setting with the registry value. */
|
|
21
|
+
localOverrideMode?: "respect" | "off-only" | "ignore";
|
|
20
22
|
createClient?: (options: FlagClientOptions) => DaemonFlagClient;
|
|
21
23
|
getToken?: () => Promise<string>;
|
|
22
24
|
onError?: (error: unknown) => void;
|
package/dist/lib/daemon/flag.js
CHANGED
|
@@ -15,7 +15,8 @@ const READ_TIMEOUT_MS = 5_000;
|
|
|
15
15
|
export async function readDaemonFlag(flagKey, options = {}) {
|
|
16
16
|
const env = options.env ?? process.env;
|
|
17
17
|
const override = readLocalOverride(flagKey, env);
|
|
18
|
-
|
|
18
|
+
const localOverrideMode = options.localOverrideMode ?? "respect";
|
|
19
|
+
if (override !== null && (localOverrideMode === "respect" || (localOverrideMode === "off-only" && !override)))
|
|
19
20
|
return override;
|
|
20
21
|
let failed = false;
|
|
21
22
|
let reportedError;
|
|
@@ -12,6 +12,7 @@ import { readCompaniesManifestMap } from "../work-context/repo-remote.js";
|
|
|
12
12
|
import { SessionRegistry } from "./session-registry.js";
|
|
13
13
|
import { HqMcpBridge, type RunWorkerRequest } from "./ops/mcp.js";
|
|
14
14
|
import { type StdinMachineCredentials } from "./machine-login.js";
|
|
15
|
+
import { type ReadDaemonFlagOptions } from "./flag.js";
|
|
15
16
|
import { type WindowsPipeAclReport, type WindowsPipeHost } from "./windows-pipe-host.js";
|
|
16
17
|
export interface HqdServerOptions {
|
|
17
18
|
socketPath: string;
|
|
@@ -41,7 +42,7 @@ export interface HqdServerOptions {
|
|
|
41
42
|
/** Test seam for the hq-anywhere-runtime default-off gate. */
|
|
42
43
|
mcpEnabled?: () => boolean | Promise<boolean>;
|
|
43
44
|
/** Test seam for the live hq-flags read used by the daemon's bounded runtime monitor. */
|
|
44
|
-
runtimeFlagReader?: () => Promise<boolean>;
|
|
45
|
+
runtimeFlagReader?: (options: ReadDaemonFlagOptions) => Promise<boolean>;
|
|
45
46
|
/** Test seam for the hosted Streamable HTTP MCP transport. */
|
|
46
47
|
mcpBridge?: HqMcpBridge;
|
|
47
48
|
/** Test seam for machine login while keeping production credentials off disk. */
|
|
@@ -396,14 +396,22 @@ export async function startHqdServer(opts) {
|
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
398
|
const runtimeFlagMonitor = opts.mcpEnabled ? undefined : createBoundedDaemonFlagMonitor({
|
|
399
|
-
read:
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
399
|
+
read: () => {
|
|
400
|
+
const options = {
|
|
401
|
+
env,
|
|
402
|
+
throwOnError: true,
|
|
403
|
+
// A local opt-out is honored, while a local opt-in cannot override the operator kill switch.
|
|
404
|
+
localOverrideMode: "off-only",
|
|
405
|
+
};
|
|
406
|
+
return opts.runtimeFlagReader
|
|
407
|
+
? opts.runtimeFlagReader(options)
|
|
408
|
+
: readDaemonFlag(HQ_ANYWHERE_RUNTIME_FLAG, options);
|
|
409
|
+
},
|
|
403
410
|
onChange: (enabled) => {
|
|
404
411
|
if (!enabled) {
|
|
405
412
|
flagRevocationGeneration += 1;
|
|
406
413
|
ephemeralMachineToken = undefined;
|
|
414
|
+
sessions.clear();
|
|
407
415
|
}
|
|
408
416
|
},
|
|
409
417
|
onReadError: (error) => {
|
|
@@ -426,8 +434,22 @@ export async function startHqdServer(opts) {
|
|
|
426
434
|
socket: healthSocket(socketPath, isPipe),
|
|
427
435
|
};
|
|
428
436
|
};
|
|
429
|
-
|
|
437
|
+
const alwaysAvailableOps = new Set(["ping", "health", "auth.status", "shutdown"]);
|
|
438
|
+
const allowedWhileDisabled = new Set(["session.get", "session.close"]);
|
|
439
|
+
/** Gate each external hqd request once, leaving diagnostics and cleanup available while disabled. */
|
|
430
440
|
const handle = (req) => {
|
|
441
|
+
const requestStartedAt = Date.now();
|
|
442
|
+
if (alwaysAvailableOps.has(req.op))
|
|
443
|
+
return dispatch(req, requestStartedAt);
|
|
444
|
+
return Promise.resolve(mcpEnabled()).then((enabled) => {
|
|
445
|
+
if (!enabled && !allowedWhileDisabled.has(req.op)) {
|
|
446
|
+
throw new OpError("anywhere_disabled", `HQ Anywhere is disabled by the ${HQ_ANYWHERE_RUNTIME_FLAG} flag.`);
|
|
447
|
+
}
|
|
448
|
+
return dispatch(req, requestStartedAt);
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
/** Internal MCP sub-operations reuse the already-gated external request. */
|
|
452
|
+
const dispatch = (req, requestStartedAt = Date.now()) => {
|
|
431
453
|
const args = req.args ?? {};
|
|
432
454
|
switch (req.op) {
|
|
433
455
|
case "ping":
|
|
@@ -565,7 +587,6 @@ export async function startHqdServer(opts) {
|
|
|
565
587
|
}, { hqRoot: opts.hqRoot, workMeshRoot: opts.workMeshRoot, env, now: clock });
|
|
566
588
|
}
|
|
567
589
|
case "mcp.call": {
|
|
568
|
-
const requestStartedAt = Date.now();
|
|
569
590
|
const name = stringArg(args, "name");
|
|
570
591
|
const raw = args.arguments ?? {};
|
|
571
592
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
@@ -581,7 +602,7 @@ export async function startHqdServer(opts) {
|
|
|
581
602
|
companyArgs.runtime = runtime;
|
|
582
603
|
if (sessionId !== undefined)
|
|
583
604
|
companyArgs.sessionId = sessionId;
|
|
584
|
-
const call = (op, a) =>
|
|
605
|
+
const call = (op, a) => dispatch({ id: req.id, op, args: a });
|
|
585
606
|
const asResult = (value) => (value instanceof Promise ? value.then(jsonResult) : jsonResult(value));
|
|
586
607
|
const policyGuardedCall = (run) => {
|
|
587
608
|
const company = companyFor(companyArgs);
|
|
@@ -601,7 +622,11 @@ export async function startHqdServer(opts) {
|
|
|
601
622
|
const reason = Array.isArray(decision.reasons) ? decision.reasons.filter((r) => typeof r === "string").join("; ") : "policy check did not allow this tool";
|
|
602
623
|
throw new OpError("policy_blocked", reason || "policy check did not allow this tool");
|
|
603
624
|
}
|
|
604
|
-
return
|
|
625
|
+
return Promise.resolve(mcpEnabled()).then((enabled) => {
|
|
626
|
+
if (!enabled)
|
|
627
|
+
throw new OpError("anywhere_disabled", `HQ Anywhere is disabled by the ${HQ_ANYWHERE_RUNTIME_FLAG} flag.`);
|
|
628
|
+
return run();
|
|
629
|
+
});
|
|
605
630
|
});
|
|
606
631
|
};
|
|
607
632
|
switch (name) {
|
|
@@ -628,10 +653,7 @@ export async function startHqdServer(opts) {
|
|
|
628
653
|
? Math.max(1, Math.min(args.timeoutMs, 120_000))
|
|
629
654
|
: HQD_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
630
655
|
const deadline = requestStartedAt + timeoutMs;
|
|
631
|
-
const forward = () =>
|
|
632
|
-
if (!enabled) {
|
|
633
|
-
throw new OpError("anywhere_disabled", `Hosted hq-mcp forwarding is disabled by the ${HQ_ANYWHERE_RUNTIME_FLAG} flag.`);
|
|
634
|
-
}
|
|
656
|
+
const forward = () => {
|
|
635
657
|
const remainingMs = Math.floor(deadline - Date.now());
|
|
636
658
|
if (remainingMs <= 0) {
|
|
637
659
|
throw new McpOpError("hq_mcp_timeout", `Hosted hq-mcp request exceeded its ${timeoutMs}ms end-to-end deadline before forwarding.`);
|
|
@@ -650,15 +672,10 @@ export async function startHqdServer(opts) {
|
|
|
650
672
|
},
|
|
651
673
|
});
|
|
652
674
|
return hqMcp.callTool(name, raw, remainingMs);
|
|
653
|
-
}
|
|
654
|
-
return
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
return !MCP_READ_ONLY_TOOL_NAMES.has(name)
|
|
659
|
-
? policyGuardedCall(forward)
|
|
660
|
-
: forward();
|
|
661
|
-
});
|
|
675
|
+
};
|
|
676
|
+
return !MCP_READ_ONLY_TOOL_NAMES.has(name)
|
|
677
|
+
? policyGuardedCall(forward)
|
|
678
|
+
: forward();
|
|
662
679
|
}
|
|
663
680
|
}
|
|
664
681
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import * as yaml from 'js-yaml';
|
|
5
|
-
import { contributionLinks } from '../../utils/pack-
|
|
5
|
+
import { contributionLinks } from '../../utils/pack-contribution-links.js';
|
|
6
6
|
import { CONTRIBUTION_TABLE } from '../../utils/contribution-table.js';
|
|
7
7
|
function isDirectory(target) { try {
|
|
8
8
|
return fs.statSync(target).isDirectory();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PackContributeKey } from '../types.js';
|
|
2
|
+
/** A single symlink a pack contributes: dst (host path) -> src (inside pack). */
|
|
3
|
+
export interface WiredLink {
|
|
4
|
+
key: PackContributeKey;
|
|
5
|
+
item: string;
|
|
6
|
+
src: string;
|
|
7
|
+
dst: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Every SYMLINK a pack's `contributes` block declares. Empty subfields and
|
|
11
|
+
* non-array values are ignored, mirroring scan-packages.sh. `wire: 'merge'`
|
|
12
|
+
* keys (e.g. `mcp`) produce no symlink and are skipped here.
|
|
13
|
+
*/
|
|
14
|
+
export declare function contributionLinks(hqRoot: string, packDir: string, contributes: Partial<Record<PackContributeKey, string[]>>): WiredLink[];
|
|
15
|
+
//# sourceMappingURL=pack-contribution-links.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { CONTRIBUTION_TABLE, payloadFor } from './contribution-table.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every SYMLINK a pack's `contributes` block declares. Empty subfields and
|
|
5
|
+
* non-array values are ignored, mirroring scan-packages.sh. `wire: 'merge'`
|
|
6
|
+
* keys (e.g. `mcp`) produce no symlink and are skipped here.
|
|
7
|
+
*/
|
|
8
|
+
export function contributionLinks(hqRoot, packDir, contributes) {
|
|
9
|
+
const links = [];
|
|
10
|
+
for (const [key, items] of Object.entries(contributes)) {
|
|
11
|
+
if (!(key in CONTRIBUTION_TABLE))
|
|
12
|
+
continue;
|
|
13
|
+
if (!Array.isArray(items))
|
|
14
|
+
continue;
|
|
15
|
+
if (CONTRIBUTION_TABLE[key].wire !== 'symlink')
|
|
16
|
+
continue;
|
|
17
|
+
for (const item of items) {
|
|
18
|
+
if (typeof item !== 'string' || item.length === 0)
|
|
19
|
+
continue;
|
|
20
|
+
const row = CONTRIBUTION_TABLE[key];
|
|
21
|
+
const payloadRel = payloadFor(key, item);
|
|
22
|
+
const dstRel = path.join(row.host, path.basename(payloadRel));
|
|
23
|
+
links.push({
|
|
24
|
+
key,
|
|
25
|
+
item,
|
|
26
|
+
src: path.join(packDir, payloadRel),
|
|
27
|
+
dst: path.join(hqRoot, dstRel),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return links;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=pack-contribution-links.js.map
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
*/
|
|
39
39
|
import type { PackManifest, PackContributeKey } from '../types.js';
|
|
40
40
|
import { type WireMode } from './contribution-table.js';
|
|
41
|
+
import { type WiredLink } from './pack-contribution-links.js';
|
|
42
|
+
export { contributionLinks, type WiredLink } from './pack-contribution-links.js';
|
|
41
43
|
import { type UnregisterServerResult, type SafeWriteEnv, type AcquireLockOptions } from '../commands/mcp-registration.js';
|
|
42
44
|
export { readHqVersion, readRecommendedPackages } from './hq-core-yaml.js';
|
|
43
45
|
export type { WireMode };
|
|
@@ -62,21 +64,7 @@ export declare function mcpServerNameForContribution(packName: string, contribut
|
|
|
62
64
|
* order, de-duplicated.
|
|
63
65
|
*/
|
|
64
66
|
export declare function mergeKeys(contributes: Partial<Record<PackContributeKey, string[]>>): PackContributeKey[];
|
|
65
|
-
/** A single symlink a pack contributes: dst (host path) -> src (inside pack). */
|
|
66
|
-
export interface WiredLink {
|
|
67
|
-
key: PackContributeKey;
|
|
68
|
-
item: string;
|
|
69
|
-
src: string;
|
|
70
|
-
dst: string;
|
|
71
|
-
}
|
|
72
67
|
export type LinkStatus = 'live' | 'broken' | 'missing' | 'foreign';
|
|
73
|
-
/**
|
|
74
|
-
* Every SYMLINK a pack's `contributes` block declares. Empty subfields and
|
|
75
|
-
* non-array values are ignored, mirroring scan-packages.sh. `wire: 'merge'`
|
|
76
|
-
* keys (e.g. `mcp`) produce no symlink and are skipped here -- their host
|
|
77
|
-
* effect (a config merge) is handled separately (US-004/US-005).
|
|
78
|
-
*/
|
|
79
|
-
export declare function contributionLinks(hqRoot: string, packDir: string, contributes: Partial<Record<PackContributeKey, string[]>>): WiredLink[];
|
|
80
68
|
/** Classify a host path against the link that should own it. */
|
|
81
69
|
export declare function linkStatus(link: WiredLink, packDir?: string): LinkStatus;
|
|
82
70
|
/** A content pack's manifest plus the install-time stamped source. */
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
import * as fs from 'fs';
|
|
40
40
|
import * as path from 'path';
|
|
41
41
|
import * as yaml from 'js-yaml';
|
|
42
|
-
import { CONTRIBUTION_TABLE
|
|
42
|
+
import { CONTRIBUTION_TABLE } from './contribution-table.js';
|
|
43
|
+
import { contributionLinks } from './pack-contribution-links.js';
|
|
44
|
+
export { contributionLinks } from './pack-contribution-links.js';
|
|
43
45
|
import { unregisterMcpServers, } from '../commands/mcp-registration.js';
|
|
44
46
|
export { readHqVersion, readRecommendedPackages } from './hq-core-yaml.js';
|
|
45
47
|
/**
|
|
@@ -78,60 +80,6 @@ export function mergeKeys(contributes) {
|
|
|
78
80
|
}
|
|
79
81
|
return out;
|
|
80
82
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Map one SYMLINK contributes entry to its source/host paths, READING the
|
|
83
|
-
* single-source `CONTRIBUTION_TABLE` (US-003). The src is the expanded pack
|
|
84
|
-
* payload; the dst is `<host-dir>/<payload-basename>` so per-key suffixes
|
|
85
|
-
* (`.md`, `.sh`, none) carry through.
|
|
86
|
-
*
|
|
87
|
-
* Returns `null` for a `wire: 'merge'` key (e.g. `mcp`): a merge contribution
|
|
88
|
-
* is not a symlink and has no `WiredLink`. Callers that only handle symlinks
|
|
89
|
-
* (contributionLinks/unwirePack) skip those keys.
|
|
90
|
-
*/
|
|
91
|
-
function linkFor(hqRoot, packDir, key, item) {
|
|
92
|
-
const row = CONTRIBUTION_TABLE[key];
|
|
93
|
-
if (row.wire !== 'symlink')
|
|
94
|
-
return null;
|
|
95
|
-
const payloadRel = payloadFor(key, item); // e.g. commands/foo.md
|
|
96
|
-
// dst keeps the payload's basename (so the suffix carries through) under the
|
|
97
|
-
// host directory declared in the table.
|
|
98
|
-
const dstRel = path.join(row.host, path.basename(payloadRel));
|
|
99
|
-
return {
|
|
100
|
-
key,
|
|
101
|
-
item,
|
|
102
|
-
src: path.join(packDir, payloadRel),
|
|
103
|
-
dst: path.join(hqRoot, dstRel),
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Every SYMLINK a pack's `contributes` block declares. Empty subfields and
|
|
108
|
-
* non-array values are ignored, mirroring scan-packages.sh. `wire: 'merge'`
|
|
109
|
-
* keys (e.g. `mcp`) produce no symlink and are skipped here -- their host
|
|
110
|
-
* effect (a config merge) is handled separately (US-004/US-005).
|
|
111
|
-
*/
|
|
112
|
-
export function contributionLinks(hqRoot, packDir, contributes) {
|
|
113
|
-
const links = [];
|
|
114
|
-
for (const [key, items] of Object.entries(contributes)) {
|
|
115
|
-
if (!(key in CONTRIBUTION_TABLE))
|
|
116
|
-
continue; // unknown key -> ignore
|
|
117
|
-
if (!Array.isArray(items))
|
|
118
|
-
continue;
|
|
119
|
-
// INVARIANT (US-005): merge keys are NEVER symlinked. They route to
|
|
120
|
-
// registerMcpServers (commands/mcp-registration.ts) instead. Skip them
|
|
121
|
-
// explicitly here -- not just via linkFor returning null -- so the symlink
|
|
122
|
-
// path provably excludes them.
|
|
123
|
-
if (routeContribution(key) === 'merge')
|
|
124
|
-
continue;
|
|
125
|
-
for (const item of items) {
|
|
126
|
-
if (typeof item !== 'string' || item.length === 0)
|
|
127
|
-
continue;
|
|
128
|
-
const link = linkFor(hqRoot, packDir, key, item);
|
|
129
|
-
if (link)
|
|
130
|
-
links.push(link); // null for merge keys -- skip
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return links;
|
|
134
|
-
}
|
|
135
83
|
/**
|
|
136
84
|
* Canonical absolute path — resolves symlinks and platform aliases such as
|
|
137
85
|
* macOS `/var` ↔ `/private/var` so equivalent paths compare equal.
|