@indigoai-us/hq-cli 5.310.0 → 5.311.1
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 +20 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +1 -1
- package/dist/command-catalog.generated.d.ts +2 -2
- package/dist/command-catalog.generated.js +2 -2
- package/dist/commands/channels.d.ts +1 -9
- package/dist/commands/channels.js +2 -2
- package/dist/commands/company-transfer.d.ts +4 -28
- package/dist/commands/company-transfer.js +5 -49
- package/dist/commands/core-checkpoint-stop-gate-native.d.ts +34 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +713 -0
- package/dist/commands/core-checkpoint.d.ts +0 -24
- package/dist/commands/core-checkpoint.js +4 -4
- package/dist/commands/core-command-catalog.js +1 -1
- package/dist/commands/core-delegate-w1b.js +111 -14
- package/dist/commands/core-delegate.d.ts +9 -0
- package/dist/commands/core-delegate.js +44 -23
- package/dist/commands/core-hq-session-native.d.ts +39 -0
- package/dist/commands/core-hq-session-native.js +579 -0
- package/dist/commands/core-jobs-native.js +1 -4
- package/dist/commands/core-native-session-flag.d.ts +4 -0
- package/dist/commands/core-native-session-flag.js +7 -0
- package/dist/commands/core-native-worker-skills-flag.d.ts +4 -0
- package/dist/commands/core-native-worker-skills-flag.js +7 -0
- package/dist/commands/core-worker-lint-native.d.ts +4 -0
- package/dist/commands/core-worker-lint-native.js +160 -0
- package/dist/commands/core-worker-share-native.d.ts +5 -0
- package/dist/commands/core-worker-share-native.js +260 -0
- package/dist/commands/core-worker-skills-command.d.ts +8 -0
- package/dist/commands/core-worker-skills-command.js +33 -0
- package/dist/commands/core-worker-skills-runtime.d.ts +33 -0
- package/dist/commands/core-worker-skills-runtime.js +86 -0
- package/dist/commands/core.js +4 -2
- package/dist/commands/dm.d.ts +3 -7
- package/dist/commands/dm.js +3 -3
- package/dist/commands/integrations-core.d.ts +2 -0
- package/dist/commands/integrations-core.js +4 -0
- package/dist/commands/integrations-import.d.ts +1 -1
- package/dist/commands/integrations-import.js +34 -19
- package/dist/commands/integrations.js +2 -2
- package/dist/commands/lanes-accounts.js +3 -1
- package/dist/commands/lanes-message.js +84 -10
- package/dist/commands/lanes-shared.d.ts +4 -1
- package/dist/commands/lanes.d.ts +4 -1
- package/dist/commands/lanes.js +48 -7
- package/dist/commands/personal-connector-settings-flag.d.ts +4 -0
- package/dist/commands/personal-connector-settings-flag.js +7 -0
- package/dist/commands/report.d.ts +1 -26
- package/dist/commands/report.js +6 -6
- package/dist/fast-core.js +15 -2
- package/dist/fast-scaffold.js +25 -2
- package/dist/index.js +8 -0
- package/dist/lib/bot/continuity.d.ts +2 -1
- package/dist/lib/bot/job-completions.d.ts +0 -2
- package/dist/lib/bot/job-completions.js +2 -2
- package/dist/lib/bot/local-config.d.ts +0 -10
- package/dist/lib/bot/local-config.js +3 -9
- package/dist/lib/bot/owner-context.d.ts +2 -6
- package/dist/lib/bot/owner-context.js +3 -3
- package/dist/lib/bot/setup-explain.d.ts +0 -4
- package/dist/lib/bot/setup-explain.js +2 -2
- package/dist/lib/daemon/catalog.d.ts +3 -2
- package/dist/lib/daemon/hq-cloud.d.ts +2 -2
- package/dist/lib/daemon/hq-cloud.js +1 -1
- package/dist/lib/daemon/machine-type.d.ts +3 -4
- package/dist/lib/daemon/machine-type.js +2 -2
- package/dist/lib/daemon/remote-control.d.ts +0 -1
- package/dist/lib/daemon/remote-control.js +1 -1
- package/dist/lib/daemon/supervisor.d.ts +4 -9
- package/dist/lib/daemon/supervisor.js +5 -5
- package/dist/lib/lanes/admission-host.d.ts +3 -0
- package/dist/lib/lanes/admission-host.js +4 -0
- package/dist/lib/lanes/autopilot-registry.d.ts +62 -0
- package/dist/lib/lanes/autopilot-registry.js +192 -0
- package/dist/lib/lanes/autopilot.d.ts +2 -0
- package/dist/lib/lanes/autopilot.js +18 -5
- package/dist/lib/lanes/claude-balance.d.ts +6 -0
- package/dist/lib/lanes/claude-balance.js +17 -2
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/reconcile.js +14 -6
- package/dist/lib/lanes/spawn.js +2 -0
- package/dist/lib/mesh/live/daemon/credentials.d.ts +2 -17
- package/dist/lib/mesh/live/daemon/credentials.js +9 -9
- package/dist/lib/mesh/live/daemon/index.d.ts +5 -5
- package/dist/lib/mesh/live/daemon/index.js +2 -2
- package/dist/lib/mesh/live/daemon/state.d.ts +1 -1
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +4 -11
- package/dist/lib/mesh/live/daemon/transcript-watch.js +5 -5
- package/dist/lib/work-context/company.d.ts +2 -25
- package/dist/lib/work-context/company.js +3 -3
- package/dist/lib/work-context/config.d.ts +3 -8
- package/dist/lib/work-context/config.js +2 -13
- package/dist/lib/work-context/state.d.ts +4 -13
- package/dist/lib/work-context/state.js +2 -30
- package/dist/utils/run-bundled-script.d.ts +6 -0
- package/dist/utils/run-bundled-script.js +14 -3
- package/package.json +1 -1
|
@@ -36,21 +36,6 @@ export declare const CHECKPOINT_REPLY_REMINDER: string;
|
|
|
36
36
|
* demands the findings), so the conditional half still points there.
|
|
37
37
|
*/
|
|
38
38
|
export declare const CHECKPOINT_IDLE_REPLY_REMINDER: string;
|
|
39
|
-
/**
|
|
40
|
-
* Kept in TypeScript rather than in a bundled asset: it is an instruction to
|
|
41
|
-
* a locally-installed agent, not a scaffold script that should be packaged.
|
|
42
|
-
*/
|
|
43
|
-
export declare const SIBLING_PROMPT_TEMPLATE = "You are the HQ checkpoint sibling \u2014 a background maintenance agent for this\nHQ install. Your parent session's state is in <payloadPath>. Work\nquietly and do not ask questions; if something is ambiguous, record it in the\nreport instead of guessing.\n\n1. Read the payload. If it lists a transcript path that exists, read its tail (~400 lines)\n both for session context and to extract additional reusable learnings/insights\n the parent did not pass explicitly. Never quote secrets\n or tokens from the transcript. If .claude/skills/checkpoint/SKILL.md exists\n under this HQ root, read it and follow it wherever it goes beyond these instructions;\n the write bounds below always win over the skill text.\n2. Upgrade the thread file named in the payload IN PLACE: verify/repair its\n JSON; fill git.remote_url, git.initial_commit, git.commits_made, and\n git.knowledge_repos by scanning core/knowledge/public/*,\n core/knowledge/private/*, personal/knowledge/*, and companies/*/knowledge\n for symlinks or directories containing .git, recording dirty repositories\n as {\"<name>\": {\"commit\": \"<short>\", \"dirty\": true}}. Fill worker,\n next_steps, and insights; set type to \"checkpoint\"; then rename the file to\n drop -auto- from its filename. Use the renamed path in every reference you\n write afterwards.\n3. For every explicit or transcript-derived learning that is a reusable rule,\n FIRST search the existing policies for one the learning refines, contradicts\n or duplicates. Then take exactly one of these actions and name it in the\n report \u2014 the policy set is curated, not append-only:\n - AMEND an existing policy in place when the learning sharpens it, narrows\n its scope, or adds a case, and the rule as written is still correct.\n - SUPERSEDE it when the learning contradicts it: rewrite the rule to what is\n now true and record inside the file what changed and why.\n - MERGE near-duplicates into the single best-named file, then delete the\n files you merged away.\n - CREATE a new policy only when no existing policy covers the rule.\n Follow core/knowledge/public/hq-core/policies-spec.md. Write under\n personal/policies/ or, only when the payload names a company and the rule is\n company-specific, companies/<company>/policies/.\n DELETION BOUNDS: delete a policy only as the MERGE or SUPERSEDE step above,\n only inside those two directories, and never one whose body marks it HARD \u2014\n if a hard policy now looks wrong, leave it untouched and flag it in the\n report for a human to decide. Never delete a file you have not read.\n Apply the same curation to durable facts (not rules) under\n personal/knowledge/ or companies/<company>/knowledge/: correct a stale fact\n in place rather than appending a second, contradictory copy of it.\n Store up to two explicit or transcript-derived insights per\n core/knowledge/public/hq-core/insights-spec.md when present, otherwise\n workspace/insights/.\n4. Close an active session journal fail-soft with\n bash .claude/skills/_shared/journal.sh close \"<project_dir>\" \"<one-line synthesis>\".\n Write a legacy checkpoint JSON under workspace/checkpoints/<id>.json with\n id, created_at, summary, files, and next_steps for backward compatibility.\n5. Update workspace/threads/recent.md and regenerate\n workspace/threads/INDEX.md. For each company whose knowledge path appears\n in files_touched, regenerate companies/<company>/knowledge/INDEX.md under\n core/knowledge/public/hq-core/index-md-spec.md. Mechanical index generation\n is allowed for those companies, but knowledge/policy content writes remain\n restricted to the payload's named company.\n<ontologyStep>6. Run .claude/skills/document-release/SKILL.md best-effort when it exists;\n skip silently on any failure. Hook or automation improvements go ONLY under\n personal/hooks/ as proposals.\n7. WRITE BOUNDS: you may write only under personal/, workspace/, and companies/<company>/ as constrained above. You must NEVER write into .claude/, core/, .agents/, .codex/, repos/, or anywhere outside the HQ root.\n8. Write <runDir>/report.md \u2014 full prose: what you read, what you changed\n (paths), and what you skipped and why. List every policy or knowledge file\n you amended, superseded, merged or deleted with the reason, so a human can\n audit and reverse it; a deletion you do not name in the report is a defect.\n Then drain the queue: while\n workspace/checkpoints/sibling/pending.jsonl exists and is non-empty, claim\n it atomically by renaming it aside \u2014\n mv workspace/checkpoints/sibling/pending.jsonl <runDir>/pending-claimed-N.jsonl\n (N counting up from 2) \u2014 and process the claimed payloads with this same\n flow. Repeat until a claim finds nothing left, then update the report.\n NEVER read the queue and truncate it in place: a payload appended between\n your read and the truncate is lost, and its checkpoint is never enriched.\n";
|
|
44
|
-
/**
|
|
45
|
-
* Rendered into the sibling prompt only when the payload names a company. The
|
|
46
|
-
* flag check happens inside `hq core ontology plan`, in the sibling, because
|
|
47
|
-
* the checkpoint parent performs no network I/O. Only this run's own payload
|
|
48
|
-
* gets the step. A queued payload's company files are recorded as queued
|
|
49
|
-
* touches when it is queued (CO-59), and a later plan for that company takes
|
|
50
|
-
* them while checkpoint.ontology.queued-touches is on; a run with no company
|
|
51
|
-
* of its own gets the step for the company whose touches waited longest.
|
|
52
|
-
*/
|
|
53
|
-
export declare const SIBLING_ONTOLOGY_STEP = "5b. ONTOLOGY (this run's own payload only; skip it for claimed queue payloads).\n Run: hq core ontology plan --run-dir \"<runDir>\" --hq-root \"<hqRoot>\"\n If it prints \"ontology: skipped\", do nothing more for this step. Otherwise\n read <runDir>/ontology-plan.json, follow its \"instructions\" exactly, and\n write <runDir>/ontology-candidates.json. Read only the files and line\n ranges the plan lists. Then run:\n hq core ontology apply --run-dir \"<runDir>\" --hq-root \"<hqRoot>\"\n Never write under the company's ontology/ folder yourself, and never edit\n payload.json or ontology-plan.json. Copy the command's one-line output into\n the report. If either command fails, record the error and continue.\n";
|
|
54
39
|
export declare function renderSiblingPrompt(runDir: string, payloadPath: string, options?: {
|
|
55
40
|
company?: string | null;
|
|
56
41
|
hqRoot?: string;
|
|
@@ -84,15 +69,6 @@ export declare function autoBackendPreference(preferGrok?: boolean): SpawnableBa
|
|
|
84
69
|
* registry; a flag change takes effect from the checkpoint after the next plan.
|
|
85
70
|
*/
|
|
86
71
|
export declare function companyPrefersGrok(liveRoot: string, companySlug: string | null | undefined): boolean;
|
|
87
|
-
/**
|
|
88
|
-
* Whether the maintenance sibling may run at all. The process environment wins
|
|
89
|
-
* over the settings file, so a one-off `HQ_CHECKPOINT_AGENT=0 hq core
|
|
90
|
-
* checkpoint …` still works and a host that exports the settings `env` block
|
|
91
|
-
* (Claude Code does) agrees with a host that does not.
|
|
92
|
-
*/
|
|
93
|
-
export declare function siblingEnabledBySetting(liveRoot: string): boolean;
|
|
94
|
-
/** The sibling's environment: the caller's, minus lane identity. */
|
|
95
|
-
export declare function siblingEnv(base: NodeJS.ProcessEnv, backend: SpawnableBackend): NodeJS.ProcessEnv;
|
|
96
72
|
export declare function siblingArgs(backend: SpawnableBackend, prompt: string): string[];
|
|
97
73
|
/** Attach the native checkpoint command to the hidden `hq core` group. */
|
|
98
74
|
export declare function registerCoreCheckpointCommand(core: Command): void;
|
|
@@ -115,7 +115,7 @@ function printError(line) {
|
|
|
115
115
|
* Kept in TypeScript rather than in a bundled asset: it is an instruction to
|
|
116
116
|
* a locally-installed agent, not a scaffold script that should be packaged.
|
|
117
117
|
*/
|
|
118
|
-
|
|
118
|
+
const SIBLING_PROMPT_TEMPLATE = `You are the HQ checkpoint sibling — a background maintenance agent for this
|
|
119
119
|
HQ install. Your parent session's state is in <payloadPath>. Work
|
|
120
120
|
quietly and do not ask questions; if something is ambiguous, record it in the
|
|
121
121
|
report instead of guessing.
|
|
@@ -195,7 +195,7 @@ report instead of guessing.
|
|
|
195
195
|
* them while checkpoint.ontology.queued-touches is on; a run with no company
|
|
196
196
|
* of its own gets the step for the company whose touches waited longest.
|
|
197
197
|
*/
|
|
198
|
-
|
|
198
|
+
const SIBLING_ONTOLOGY_STEP = `5b. ONTOLOGY (this run's own payload only; skip it for claimed queue payloads).
|
|
199
199
|
Run: hq core ontology plan --run-dir "<runDir>" --hq-root "<hqRoot>"
|
|
200
200
|
If it prints "ontology: skipped", do nothing more for this step. Otherwise
|
|
201
201
|
read <runDir>/ontology-plan.json, follow its "instructions" exactly, and
|
|
@@ -784,7 +784,7 @@ function resolveSwitchSetting(liveRoot, key) {
|
|
|
784
784
|
* checkpoint …` still works and a host that exports the settings `env` block
|
|
785
785
|
* (Claude Code does) agrees with a host that does not.
|
|
786
786
|
*/
|
|
787
|
-
|
|
787
|
+
function siblingEnabledBySetting(liveRoot) {
|
|
788
788
|
return resolveSwitchSetting(liveRoot, SIBLING_SETTING) ?? true;
|
|
789
789
|
}
|
|
790
790
|
/** Rejects an unknown `--backend` without paying for the probe that resolves one. */
|
|
@@ -814,7 +814,7 @@ function resolveBackend(requested, liveRoot, company) {
|
|
|
814
814
|
*/
|
|
815
815
|
const LANE_ENV_KEYS = ["HQ_LANE_ID", "HQ_PROJECT_ID", "HQ_STORY_ID", "HQ_POLICY_WORKER_DIR"];
|
|
816
816
|
/** The sibling's environment: the caller's, minus lane identity. */
|
|
817
|
-
|
|
817
|
+
function siblingEnv(base, backend) {
|
|
818
818
|
const vars = { ...base };
|
|
819
819
|
for (const name of LANE_ENV_KEYS)
|
|
820
820
|
delete vars[name];
|
|
@@ -46,7 +46,7 @@ export function fastScaffoldListingMismatches(listing, fastScripts) {
|
|
|
46
46
|
else if (fast.kind === "shell" && listed.asset !== fast.asset) {
|
|
47
47
|
mismatches.push(`${fast.name} lists ${listed.asset ?? "no asset"}, fast path uses ${fast.asset ?? "no asset"}`);
|
|
48
48
|
}
|
|
49
|
-
else if (fast.kind === "native" && (listed.execution !== "native" ||
|
|
49
|
+
else if (fast.kind === "native" && ((listed.execution !== "native" && listed.execution !== "bundled-shell") ||
|
|
50
50
|
(fast.asset === undefined ? listed.asset !== undefined : listed.asset !== fast.asset))) {
|
|
51
51
|
mismatches.push(`${fast.name} has fast-route metadata that differs from the native command catalog`);
|
|
52
52
|
}
|
|
@@ -6,7 +6,7 @@ import * as path from "node:path";
|
|
|
6
6
|
import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
|
|
7
7
|
import { matchingSecretPatterns } from "../lib/core-utils/secret-patterns.js";
|
|
8
8
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
9
|
-
import { isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
|
|
9
|
+
import { commandExists, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
|
|
10
10
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
11
11
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
12
12
|
const FLAG = "core-native-delegate";
|
|
@@ -61,6 +61,15 @@ function jsonFile(file) {
|
|
|
61
61
|
return undefined;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
function malformedJsonFile(file) {
|
|
65
|
+
try {
|
|
66
|
+
JSON.parse(fs.readFileSync(file, "utf8"));
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return error instanceof SyntaxError;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
64
73
|
function jsonText(value) { return serializeJqJson(value, "pretty"); }
|
|
65
74
|
function checksum(file) {
|
|
66
75
|
return createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
@@ -125,7 +134,21 @@ function parseFlags(args, allowed) {
|
|
|
125
134
|
}
|
|
126
135
|
function usage(name) { return USAGE[name] ?? ""; }
|
|
127
136
|
function usageFailure(name) { err(usage(name)); return 1; }
|
|
128
|
-
function
|
|
137
|
+
function runShellFallback(name, args, context) {
|
|
138
|
+
const result = runBundledScript({
|
|
139
|
+
asset: ASSETS[name],
|
|
140
|
+
args,
|
|
141
|
+
cwd: context.cwd,
|
|
142
|
+
hqRoot: context.root ?? context.cwd,
|
|
143
|
+
captureOutput: true,
|
|
144
|
+
});
|
|
145
|
+
if (result.stdout?.length)
|
|
146
|
+
process.stdout.write(result.stdout);
|
|
147
|
+
if (result.stderr?.length)
|
|
148
|
+
process.stderr.write(result.stderr);
|
|
149
|
+
return result.code;
|
|
150
|
+
}
|
|
151
|
+
function runBundle(args, context, onMalformedWhoami) {
|
|
129
152
|
const root = context.root ?? context.cwd;
|
|
130
153
|
if (args[0] !== "build")
|
|
131
154
|
return usageFailure("bundle");
|
|
@@ -175,7 +198,9 @@ function runBundle(args, context) {
|
|
|
175
198
|
try {
|
|
176
199
|
identity = JSON.parse(who.stdout ?? "");
|
|
177
200
|
}
|
|
178
|
-
catch {
|
|
201
|
+
catch (error) {
|
|
202
|
+
if (error instanceof SyntaxError)
|
|
203
|
+
return onMalformedWhoami ? onMalformedWhoami() : runShellFallback("bundle", args, context);
|
|
179
204
|
identity = undefined;
|
|
180
205
|
}
|
|
181
206
|
const identityValue = isRecord(identity) ? identity : {};
|
|
@@ -291,6 +316,32 @@ function runBundle(args, context) {
|
|
|
291
316
|
function runHq(args, context, stdout = "capture") {
|
|
292
317
|
return subprocess("hq", args, context.cwd, { stdout });
|
|
293
318
|
}
|
|
319
|
+
function commandSubstitutionBytes(value) {
|
|
320
|
+
const bytes = value.stdoutBytes ?? Buffer.from(value.stdout ?? "");
|
|
321
|
+
let end = bytes.length;
|
|
322
|
+
while (end > 0 && bytes[end - 1] === 0x0a)
|
|
323
|
+
end -= 1;
|
|
324
|
+
return bytes.subarray(0, end);
|
|
325
|
+
}
|
|
326
|
+
function emitJqParseDiagnostic(input, args, context) {
|
|
327
|
+
if (!commandExists("jq", context.cwd))
|
|
328
|
+
return;
|
|
329
|
+
const result = subprocess("jq", args, context.cwd, { input, stdout: "ignore", stderr: "pipe" });
|
|
330
|
+
const diagnostic = result.stderrBytes ?? Buffer.from(result.stderr ?? "");
|
|
331
|
+
if (diagnostic.length)
|
|
332
|
+
err(diagnostic);
|
|
333
|
+
}
|
|
334
|
+
const RESOLVE_FOUND_FILTER = '.status == "found" and (.person.personUid | type == "string") and (.person.companyUid | type == "string")';
|
|
335
|
+
const RESOLVE_NOT_FOUND_FILTER = '.status == "not_found"';
|
|
336
|
+
const ACL_FILTER = `
|
|
337
|
+
.schemaVersion == 1 and
|
|
338
|
+
(
|
|
339
|
+
.prefix == $prefix or
|
|
340
|
+
(($prefix | endswith("/*")) and .prefix == ($prefix | sub("\\\\*$"; "")))
|
|
341
|
+
) and
|
|
342
|
+
(.companyUid | type == "string") and ($company == "" or .companyUid == $company) and
|
|
343
|
+
(.direct | type == "array") and (.exists | type == "boolean")
|
|
344
|
+
`;
|
|
294
345
|
function runGrant(args, context) {
|
|
295
346
|
const parsed = parseFlags(args, ["--manifest", "--yes"]);
|
|
296
347
|
if (parsed.help) {
|
|
@@ -397,7 +448,12 @@ function runGrant(args, context) {
|
|
|
397
448
|
try {
|
|
398
449
|
response = JSON.parse(resolved.stdout ?? "");
|
|
399
450
|
}
|
|
400
|
-
catch {
|
|
451
|
+
catch (error) {
|
|
452
|
+
if (error instanceof SyntaxError) {
|
|
453
|
+
const input = commandSubstitutionBytes(resolved);
|
|
454
|
+
emitJqParseDiagnostic(input, ["-e", RESOLVE_FOUND_FILTER], context);
|
|
455
|
+
emitJqParseDiagnostic(input, ["-e", RESOLVE_NOT_FOUND_FILTER], context);
|
|
456
|
+
}
|
|
401
457
|
response = undefined;
|
|
402
458
|
}
|
|
403
459
|
if (isRecord(response) && response.status === "found" && isRecord(response.person) && typeof response.person.personUid === "string" && typeof response.person.companyUid === "string") {
|
|
@@ -426,7 +482,11 @@ function runGrant(args, context) {
|
|
|
426
482
|
try {
|
|
427
483
|
value = JSON.parse(response.stdout ?? "");
|
|
428
484
|
}
|
|
429
|
-
catch {
|
|
485
|
+
catch (error) {
|
|
486
|
+
if (error instanceof SyntaxError) {
|
|
487
|
+
const input = commandSubstitutionBytes(response);
|
|
488
|
+
emitJqParseDiagnostic(input, ["-e", "--arg", "prefix", prefix, "--arg", "company", expectedCompany, ACL_FILTER], context);
|
|
489
|
+
}
|
|
430
490
|
value = undefined;
|
|
431
491
|
}
|
|
432
492
|
const rawPrefix = isRecord(value) ? value.prefix : undefined;
|
|
@@ -501,6 +561,22 @@ function runPublish(args, context) {
|
|
|
501
561
|
if (args[0] !== "--manifest" || !args[1] || !fs.existsSync(args[1]) || !fs.statSync(args[1]).isFile())
|
|
502
562
|
return fail("publish", "usage: --manifest <path>");
|
|
503
563
|
const manifestPath = args[1];
|
|
564
|
+
if (malformedJsonFile(manifestPath))
|
|
565
|
+
return runShellFallback("publish", args, context);
|
|
566
|
+
let preflightManifest;
|
|
567
|
+
try {
|
|
568
|
+
preflightManifest = readJsonFile(manifestPath);
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
preflightManifest = undefined;
|
|
572
|
+
}
|
|
573
|
+
if (isRecord(preflightManifest) && jqRawPath(preflightManifest, "mode") === "transfer") {
|
|
574
|
+
const root = context.root ?? context.cwd;
|
|
575
|
+
const prd = path.join(root, "companies", jqRawPath(preflightManifest, "company"), "projects", jqRawPath(preflightManifest, "project.name"), "prd.json");
|
|
576
|
+
if (fs.existsSync(prd) && fs.statSync(prd).isFile() && malformedJsonFile(prd)) {
|
|
577
|
+
return runShellFallback("publish", args, context);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
504
580
|
const lock = `${manifestPath}.send-lock`;
|
|
505
581
|
const lockHeld = args[2] === "--lock-held";
|
|
506
582
|
let ownLock = false;
|
|
@@ -658,6 +734,12 @@ function runSend(args, context) {
|
|
|
658
734
|
const manifest = jsonFile(manifestPath);
|
|
659
735
|
if (!manifest)
|
|
660
736
|
return fail("send", `manifest is not valid JSON: ${manifestPath}`);
|
|
737
|
+
if (send) {
|
|
738
|
+
const publishedPath = path.join(context.root ?? context.cwd, jqRawPath(manifest, "publication.manifestPath"));
|
|
739
|
+
if (fs.existsSync(publishedPath) && fs.statSync(publishedPath).isFile() && malformedJsonFile(publishedPath)) {
|
|
740
|
+
return runShellFallback("send", args, context);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
661
743
|
const lock = `${manifestPath}.send-lock`;
|
|
662
744
|
if (send) {
|
|
663
745
|
try {
|
|
@@ -819,14 +901,14 @@ function sleepNative(milliseconds) {
|
|
|
819
901
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
820
902
|
}
|
|
821
903
|
function captureRun(run) {
|
|
822
|
-
|
|
823
|
-
|
|
904
|
+
const stdout = [];
|
|
905
|
+
const stderr = [];
|
|
824
906
|
const stdoutWrite = process.stdout.write.bind(process.stdout);
|
|
825
907
|
const stderrWrite = process.stderr.write.bind(process.stderr);
|
|
826
|
-
process.stdout.write = ((chunk) => { stdout
|
|
827
|
-
process.stderr.write = ((chunk) => { stderr
|
|
908
|
+
process.stdout.write = ((chunk) => { stdout.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk)); return true; });
|
|
909
|
+
process.stderr.write = ((chunk) => { stderr.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk)); return true; });
|
|
828
910
|
try {
|
|
829
|
-
return { code: run(), stdout, stderr };
|
|
911
|
+
return { code: run(), stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr) };
|
|
830
912
|
}
|
|
831
913
|
finally {
|
|
832
914
|
process.stdout.write = stdoutWrite;
|
|
@@ -854,16 +936,29 @@ function runVerify(args, context) {
|
|
|
854
936
|
return fail("verify", "--dry-run requires --project");
|
|
855
937
|
if (!recipient)
|
|
856
938
|
return fail("verify", "--dry-run requires --to");
|
|
857
|
-
|
|
858
|
-
|
|
939
|
+
let malformedWhoamiFallbackCode;
|
|
940
|
+
const plan = captureRun(() => runBundle(["build", "--company", company, "--project", project, "--to", recipient, "--mode", mode, "--dry-run"], context, () => {
|
|
941
|
+
malformedWhoamiFallbackCode = runShellFallback("verify", args, context);
|
|
942
|
+
return malformedWhoamiFallbackCode;
|
|
943
|
+
}));
|
|
944
|
+
if (malformedWhoamiFallbackCode !== undefined) {
|
|
945
|
+
if (plan.stdout.length)
|
|
946
|
+
out(plan.stdout);
|
|
947
|
+
if (plan.stderr.length)
|
|
948
|
+
err(plan.stderr);
|
|
949
|
+
return malformedWhoamiFallbackCode;
|
|
950
|
+
}
|
|
951
|
+
if (plan.stderr.length)
|
|
859
952
|
err(plan.stderr);
|
|
860
953
|
if (plan.code !== 0)
|
|
861
954
|
return fail("verify", "could not derive the delegation plan (see builder error above)");
|
|
862
955
|
let manifest;
|
|
863
956
|
try {
|
|
864
|
-
manifest = JSON.parse(plan.stdout);
|
|
957
|
+
manifest = JSON.parse(plan.stdout.toString("utf8"));
|
|
865
958
|
}
|
|
866
|
-
catch {
|
|
959
|
+
catch (error) {
|
|
960
|
+
if (error instanceof SyntaxError)
|
|
961
|
+
return runShellFallback("verify", args, context);
|
|
867
962
|
return fail("verify", "could not derive the delegation plan (see builder error above)");
|
|
868
963
|
}
|
|
869
964
|
const repoRelative = jqRawPath(manifest, "repo.path");
|
|
@@ -893,6 +988,8 @@ function runVerify(args, context) {
|
|
|
893
988
|
return fail("verify", "--manifest is required (or --dry-run with --company/--project/--to)");
|
|
894
989
|
if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
|
|
895
990
|
return fail("verify", `manifest not found: ${manifestPath}`);
|
|
991
|
+
if (malformedJsonFile(manifestPath))
|
|
992
|
+
return runShellFallback("verify", args, context);
|
|
896
993
|
const manifest = jsonFile(manifestPath);
|
|
897
994
|
if (!manifest)
|
|
898
995
|
return fail("verify", `jq: error: ${manifestPath}: Invalid JSON`);
|
|
@@ -9,12 +9,21 @@ type CommandResult = {
|
|
|
9
9
|
code: number;
|
|
10
10
|
stdout?: string;
|
|
11
11
|
stderr?: string;
|
|
12
|
+
stdoutBytes?: Buffer;
|
|
13
|
+
stderrBytes?: Buffer;
|
|
12
14
|
};
|
|
15
|
+
export type CommandExistsOptions = {
|
|
16
|
+
platform?: NodeJS.Platform;
|
|
17
|
+
env?: NodeJS.ProcessEnv;
|
|
18
|
+
existsSync?: (candidate: string) => boolean;
|
|
19
|
+
};
|
|
20
|
+
export declare function commandExists(name: string, cwd?: string, options?: CommandExistsOptions): boolean;
|
|
13
21
|
export declare function subprocess(command: string, args: string[], cwd: string, options?: {
|
|
14
22
|
input?: Buffer | string;
|
|
15
23
|
stdinFd?: number;
|
|
16
24
|
stderr?: "inherit" | "ignore" | "pipe";
|
|
17
25
|
stdout?: "capture" | "inherit" | "ignore";
|
|
26
|
+
maxBuffer?: number;
|
|
18
27
|
}): CommandResult;
|
|
19
28
|
type JsonRecord = Record<string, unknown>;
|
|
20
29
|
export declare function isRecord(value: unknown): value is JsonRecord;
|
|
@@ -4,6 +4,7 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
|
|
7
|
+
import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
|
|
7
8
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
8
9
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
9
10
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
@@ -28,6 +29,20 @@ function writeOut(value) {
|
|
|
28
29
|
function writeErr(value) {
|
|
29
30
|
process.stderr.write(value);
|
|
30
31
|
}
|
|
32
|
+
function runCapturedShell(asset, args, context) {
|
|
33
|
+
const result = runBundledScript({
|
|
34
|
+
asset,
|
|
35
|
+
args,
|
|
36
|
+
cwd: context.cwd,
|
|
37
|
+
...(context.root ? { hqRoot: context.root } : {}),
|
|
38
|
+
captureOutput: true,
|
|
39
|
+
});
|
|
40
|
+
if (result.stdout?.length)
|
|
41
|
+
process.stdout.write(result.stdout);
|
|
42
|
+
if (result.stderr?.length)
|
|
43
|
+
process.stderr.write(result.stderr);
|
|
44
|
+
return result.code;
|
|
45
|
+
}
|
|
31
46
|
const USAGE_TEXT = {
|
|
32
47
|
resolve: "hq-delegate-resolve.sh — resolve a delegation recipient to a confirmed\nprincipal (person email / prs_ uid, or fleet-agent agt_ uid). Read-only.\n\nUsage:\n core/scripts/hq-delegate-resolve.sh --company <slug> --to <token>\n\nOutput (stdout, JSON): {kind, principal, displayName, source}\n kind — \"person\" | \"agent\"\n principal — confirmed email, prs_… or agt_… uid\n source — \"verbatim\" | \"people-resolve\" | \"agents-list\"\n\nExit codes:\n 0 — resolved\n 1 — usage error (missing/invalid arguments)\n 3 — ambiguous: candidate list printed to stdout as JSON\n {status:\"ambiguous\", matches:[…]} — caller must present a picker\n 4 — not found (or found with no email): plain message on stderr\n\nTenancy: every lookup is scoped with an explicit --company. This helper\nnever enumerates or falls back across companies — single-company by\nconstruction, matching the /dm skill's resolution contract.\n\nFast path: a token containing \"@\" or starting with prs_ / agt_ passes\nthrough verbatim with NO lookup, preserving existing invocations exactly.\n",
|
|
33
48
|
// Preserve the companion usage function’s historical raw source lines.
|
|
@@ -39,8 +54,20 @@ const USAGE_TEXT = {
|
|
|
39
54
|
function usage(name) {
|
|
40
55
|
return USAGE_TEXT[name];
|
|
41
56
|
}
|
|
42
|
-
function commandPath(name, cwd = process.cwd()) {
|
|
43
|
-
const
|
|
57
|
+
function commandPath(name, cwd = process.cwd(), options = {}) {
|
|
58
|
+
const env = options.env ?? process.env;
|
|
59
|
+
if ((options.platform ?? process.platform) === "win32") {
|
|
60
|
+
const existsSync = options.existsSync ?? ((candidate) => {
|
|
61
|
+
try {
|
|
62
|
+
return fs.statSync(candidate).isFile();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return resolveWindowsExecutable(name, env, existsSync, cwd) ?? undefined;
|
|
69
|
+
}
|
|
70
|
+
const directories = (env.PATH ?? "").split(path.delimiter);
|
|
44
71
|
for (const directory of directories) {
|
|
45
72
|
const candidate = path.resolve(cwd, directory || ".", name);
|
|
46
73
|
try {
|
|
@@ -56,24 +83,30 @@ function commandPath(name, cwd = process.cwd()) {
|
|
|
56
83
|
}
|
|
57
84
|
return undefined;
|
|
58
85
|
}
|
|
59
|
-
function commandExists(name, cwd = process.cwd()) {
|
|
60
|
-
return commandPath(name, cwd) !== undefined;
|
|
86
|
+
export function commandExists(name, cwd = process.cwd(), options = {}) {
|
|
87
|
+
return commandPath(name, cwd, options) !== undefined;
|
|
61
88
|
}
|
|
62
89
|
export function subprocess(command, args, cwd, options = {}) {
|
|
63
90
|
const result = spawnSync(command, args, {
|
|
64
91
|
cwd,
|
|
65
92
|
env: process.env,
|
|
66
93
|
input: options.input,
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
maxBuffer: options.maxBuffer,
|
|
95
|
+
encoding: "buffer",
|
|
96
|
+
stdio: [options.input === undefined ? options.stdinFd ?? "inherit" : "pipe", options.stdout === "inherit" ? "inherit" : options.stdout === "ignore" ? "ignore" : "pipe", options.stderr === "pipe" ? "pipe" : options.stderr ?? "inherit"],
|
|
69
97
|
});
|
|
70
98
|
if (result.error) {
|
|
71
|
-
|
|
99
|
+
const stderr = Buffer.from(result.error.message);
|
|
100
|
+
return { code: 127, stderr: stderr.toString("utf8"), stderrBytes: stderr, stdout: "", stdoutBytes: Buffer.alloc(0) };
|
|
72
101
|
}
|
|
102
|
+
const stdoutBytes = Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.alloc(0);
|
|
103
|
+
const stderrBytes = Buffer.isBuffer(result.stderr) ? result.stderr : Buffer.alloc(0);
|
|
73
104
|
return {
|
|
74
105
|
code: result.status ?? (result.signal ? 128 : 1),
|
|
75
|
-
stdout:
|
|
76
|
-
stderr:
|
|
106
|
+
stdout: stdoutBytes.toString("utf8"),
|
|
107
|
+
stderr: stderrBytes.toString("utf8"),
|
|
108
|
+
stdoutBytes,
|
|
109
|
+
stderrBytes,
|
|
77
110
|
};
|
|
78
111
|
}
|
|
79
112
|
export function isRecord(value) {
|
|
@@ -82,15 +115,6 @@ export function isRecord(value) {
|
|
|
82
115
|
export function readJsonText(text) {
|
|
83
116
|
return JSON.parse(text);
|
|
84
117
|
}
|
|
85
|
-
function jqParseDiagnostic(text) {
|
|
86
|
-
const bareToken = /^\s*([A-Za-z][A-Za-z0-9_-]*)\s*$/.exec(text);
|
|
87
|
-
if (bareToken && !["null", "true", "false"].includes(bareToken[1] ?? "")) {
|
|
88
|
-
const line = text.split("\n").length;
|
|
89
|
-
const column = Buffer.byteLength(text.slice(text.lastIndexOf("\n") + 1));
|
|
90
|
-
return `jq: parse error: Invalid numeric literal at line ${line}, column ${column}\n`;
|
|
91
|
-
}
|
|
92
|
-
return "jq: parse error: Invalid JSON text\n";
|
|
93
|
-
}
|
|
94
118
|
export function readJsonFile(file) {
|
|
95
119
|
return readJsonText(fs.readFileSync(file, "utf8"));
|
|
96
120
|
}
|
|
@@ -373,11 +397,8 @@ function runPickup(args, context) {
|
|
|
373
397
|
document = readJsonFile(file);
|
|
374
398
|
}
|
|
375
399
|
catch (error) {
|
|
376
|
-
if (error instanceof SyntaxError)
|
|
377
|
-
|
|
378
|
-
for (let index = 0; index < 4; index += 1)
|
|
379
|
-
writeErr(diagnostic);
|
|
380
|
-
}
|
|
400
|
+
if (error instanceof SyntaxError)
|
|
401
|
+
return runCapturedShell(SCRIPT_ASSETS.pickup, args, context);
|
|
381
402
|
document = {};
|
|
382
403
|
}
|
|
383
404
|
const status = jqRawPath(document, "status");
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const HQ_SESSION_ASSET = "core/scripts/hq-session.sh";
|
|
2
|
+
export declare const HQ_SESSION_HELP: string;
|
|
3
|
+
export type HqSessionResult = {
|
|
4
|
+
code: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
};
|
|
8
|
+
export type HqSessionDependencies = {
|
|
9
|
+
env: NodeJS.ProcessEnv;
|
|
10
|
+
now: () => Date;
|
|
11
|
+
readFile: (filePath: string, encoding: BufferEncoding) => string;
|
|
12
|
+
exists: (filePath: string) => boolean;
|
|
13
|
+
isFile: (filePath: string) => boolean;
|
|
14
|
+
isDirectory: (filePath: string) => boolean;
|
|
15
|
+
isSymlink: (filePath: string) => boolean;
|
|
16
|
+
listDirectory: (directory: string) => string[];
|
|
17
|
+
mkdir: (directory: string) => void;
|
|
18
|
+
writeFile: (filePath: string, contents: string, mode?: number) => void;
|
|
19
|
+
rename: (from: string, to: string) => void;
|
|
20
|
+
remove: (filePath: string) => void;
|
|
21
|
+
makeTempPath: () => string;
|
|
22
|
+
umask: () => number;
|
|
23
|
+
findExecutable: (name: string, env?: NodeJS.ProcessEnv) => string | undefined;
|
|
24
|
+
spawnDetached: (command: string, args: string[], cwd: string, env: NodeJS.ProcessEnv, logFile: string) => void;
|
|
25
|
+
runCommand: (command: string, args: string[], cwd: string, env: NodeJS.ProcessEnv) => {
|
|
26
|
+
status: number | null;
|
|
27
|
+
stdout: string;
|
|
28
|
+
stderr: string;
|
|
29
|
+
error?: Error;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/** In-process command body, with injectable file access for parity tests. */
|
|
33
|
+
export declare function executeHqSession(argv: readonly string[], context: {
|
|
34
|
+
cwd: string;
|
|
35
|
+
hqRoot?: string;
|
|
36
|
+
}, overrides?: Partial<HqSessionDependencies>): HqSessionResult;
|
|
37
|
+
/** Signature consumed by the lightweight fast-core dispatcher. */
|
|
38
|
+
export declare function runHqSessionFast(argv: readonly string[], _stdin: string, cwd: string, hqRoot: string | undefined): Promise<number>;
|
|
39
|
+
//# sourceMappingURL=core-hq-session-native.d.ts.map
|