@indigoai-us/hq-cli 5.345.67 → 5.345.69
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/README.md +13 -0
- package/assets/scaffold/core/scripts/hq-dm-bind.sh +5 -0
- package/dist/command-catalog.generated.d.ts +8 -0
- package/dist/command-catalog.generated.js +10 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +13 -1
- package/dist/commands/core-delegate-w1b.js +2 -2
- package/dist/commands/core-delegate.js +2 -2
- package/dist/commands/core-dm-bind-native.d.ts +1 -1
- package/dist/commands/core-dm-bind-native.js +55 -52
- package/dist/commands/core-handoff-fast.js +13 -8
- package/dist/commands/core-handoff.js +10 -2
- package/dist/commands/core-native-family-flag.d.ts +7 -0
- package/dist/commands/core-native-family-flag.js +21 -0
- package/dist/commands/core-native-handoff-flag.js +2 -2
- package/dist/commands/core-native-hook-helpers-flag.js +2 -2
- package/dist/commands/core-native-jobs-flag.js +2 -2
- package/dist/commands/core-native-policy-flag.js +2 -2
- package/dist/commands/core-native-session-flag.js +2 -2
- package/dist/commands/core-native-utilities-flag.js +2 -2
- package/dist/commands/core-ontology-native.js +2 -2
- package/dist/commands/core-refresh-vault-access-native.js +14 -1
- package/dist/commands/pack-install.d.ts +5 -7
- package/dist/commands/pack-install.js +9 -5
- package/dist/commands/pkg-install.js +20 -1
- package/dist/fast-core.js +12 -2
- package/dist/fast-scaffold.d.ts +3 -0
- package/dist/fast-scaffold.js +14 -0
- package/dist/lib/flag-registry.d.ts +4 -1
- package/dist/lib/flag-registry.js +18 -6
- package/dist/lib/flag-snapshot-file.d.ts +2 -0
- package/dist/lib/flag-snapshot-file.js +30 -0
- package/dist/lib/pack-signing-keys.d.ts +22 -0
- package/dist/lib/pack-signing-keys.js +53 -0
- package/dist/lib/process-flag-company-context.d.ts +2 -0
- package/dist/lib/process-flag-company-context.js +15 -0
- package/dist/native-fast-route.d.ts +2 -2
- package/dist/native-fast-route.js +6 -8
- package/dist/utils/hq-spawn.d.ts +4 -0
- package/dist/utils/hq-spawn.js +11 -0
- package/dist/utils/resident-package-preload.js +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.69] — 2026-10-10
|
|
6
|
+
- Supported native core handlers now run by default without a flag snapshot. Explicit false values still turn them off; delegate and worker-skills remain default-off pending parity.
|
|
7
|
+
|
|
8
|
+
## [5.345.68] — 2026-10-09
|
|
9
|
+
|
|
10
|
+
- Marketplace pack installs now require a signature from a pinned platform key. `--allow-unsigned` is limited to local directory sources; `HQ_PACK_PUBLIC_KEY` is development-only.
|
|
11
|
+
|
|
5
12
|
## [5.345.67] — 2026-10-09
|
|
6
13
|
|
|
7
14
|
- Persist the Codex SessionStart model and effort for conduct lanes, with safe fallback defaults.
|
package/README.md
CHANGED
|
@@ -7,6 +7,19 @@ npm install -g @indigoai-us/hq-cli
|
|
|
7
7
|
hq --help
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
## Marketplace package verification
|
|
11
|
+
|
|
12
|
+
Marketplace packages must include an Ed25519 signature made with a platform
|
|
13
|
+
key embedded in the CLI. The installer rejects unsigned listings, unknown key
|
|
14
|
+
ids, and a listing public key that does not match the pinned platform key before
|
|
15
|
+
extracting any archive. `HQ_PACK_PUBLIC_KEY` is a development-only override for
|
|
16
|
+
the current platform key id; it is honored only with `NODE_ENV=development` or
|
|
17
|
+
`NODE_ENV=test` and is ignored in production.
|
|
18
|
+
|
|
19
|
+
`hq install --allow-unsigned ./local-pack` is accepted only for local directory
|
|
20
|
+
sources. Marketplace installs always require a platform signature, and the flag
|
|
21
|
+
is rejected for them.
|
|
22
|
+
|
|
10
23
|
The global HQ runtime installs hooks for supported agent runtimes. When the
|
|
11
24
|
`hq-anywhere-runtime` flag is enabled, the hooks use hqd and a per-user folder
|
|
12
25
|
registry. The flag defaults on when its registry row cannot be read, and an
|
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
# Need from you: line
|
|
54
54
|
set -euo pipefail
|
|
55
55
|
|
|
56
|
+
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
|
|
57
|
+
sed -n '2,/^set -euo pipefail$/ { /^set -euo pipefail$/!p; }' "$0"
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
56
61
|
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
57
62
|
SESSION_SH="$HQ_ROOT/core/scripts/hq-session.sh"
|
|
58
63
|
|
|
@@ -157,6 +157,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
157
157
|
readonly flags: "--allow-mcp";
|
|
158
158
|
readonly description: "Skip the MCP server confirmation prompt (content-pack flow)";
|
|
159
159
|
readonly hidden: false;
|
|
160
|
+
}, {
|
|
161
|
+
readonly flags: "--allow-unsigned";
|
|
162
|
+
readonly description: "Acknowledge an unsigned local directory source; marketplace installs always require a signature";
|
|
163
|
+
readonly hidden: false;
|
|
160
164
|
}, {
|
|
161
165
|
readonly flags: "--branch";
|
|
162
166
|
readonly description: "Follow a ref instead of SHA-pinning (git content-pack flow)";
|
|
@@ -543,6 +547,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
543
547
|
readonly flags: "--allow-mcp";
|
|
544
548
|
readonly description: "Skip the MCP server confirmation prompt (content-pack flow)";
|
|
545
549
|
readonly hidden: false;
|
|
550
|
+
}, {
|
|
551
|
+
readonly flags: "--allow-unsigned";
|
|
552
|
+
readonly description: "Acknowledge an unsigned local directory source; marketplace installs always require a signature";
|
|
553
|
+
readonly hidden: false;
|
|
546
554
|
}, {
|
|
547
555
|
readonly flags: "--branch";
|
|
548
556
|
readonly description: "Follow a ref instead of SHA-pinning (git content-pack flow)";
|
|
@@ -157,6 +157,11 @@ export const COMMAND_CATALOG = [
|
|
|
157
157
|
"description": "Skip the MCP server confirmation prompt (content-pack flow)",
|
|
158
158
|
"hidden": false
|
|
159
159
|
},
|
|
160
|
+
{
|
|
161
|
+
"flags": "--allow-unsigned",
|
|
162
|
+
"description": "Acknowledge an unsigned local directory source; marketplace installs always require a signature",
|
|
163
|
+
"hidden": false
|
|
164
|
+
},
|
|
160
165
|
{
|
|
161
166
|
"flags": "--branch",
|
|
162
167
|
"description": "Follow a ref instead of SHA-pinning (git content-pack flow)",
|
|
@@ -641,6 +646,11 @@ export const COMMAND_CATALOG = [
|
|
|
641
646
|
"description": "Skip the MCP server confirmation prompt (content-pack flow)",
|
|
642
647
|
"hidden": false
|
|
643
648
|
},
|
|
649
|
+
{
|
|
650
|
+
"flags": "--allow-unsigned",
|
|
651
|
+
"description": "Acknowledge an unsigned local directory source; marketplace installs always require a signature",
|
|
652
|
+
"hidden": false
|
|
653
|
+
},
|
|
644
654
|
{
|
|
645
655
|
"flags": "--branch",
|
|
646
656
|
"description": "Follow a ref instead of SHA-pinning (git content-pack flow)",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Native fast-route implementation of checkpoint-stop-gate.sh. */
|
|
2
2
|
import * as childProcess from "node:child_process";
|
|
3
|
+
import { planHqSpawn } from "../utils/hq-spawn.js";
|
|
3
4
|
import * as fs from "node:fs";
|
|
4
5
|
import * as os from "node:os";
|
|
5
6
|
import * as path from "node:path";
|
|
@@ -85,7 +86,18 @@ const defaults = () => ({
|
|
|
85
86
|
return false;
|
|
86
87
|
},
|
|
87
88
|
spawnHqDetached: (args, env) => {
|
|
88
|
-
const
|
|
89
|
+
const childEnv = env ?? process.env;
|
|
90
|
+
const spawnPlan = planHqSpawn(args, childEnv, process.cwd());
|
|
91
|
+
const child = childProcess.spawn(spawnPlan.cmd, spawnPlan.args, {
|
|
92
|
+
detached: true,
|
|
93
|
+
stdio: "ignore",
|
|
94
|
+
env: childEnv,
|
|
95
|
+
shell: spawnPlan.shell,
|
|
96
|
+
...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
97
|
+
});
|
|
98
|
+
child.once("error", (error) => {
|
|
99
|
+
process.stderr.write(`${error.message}\n`);
|
|
100
|
+
});
|
|
89
101
|
child.unref();
|
|
90
102
|
},
|
|
91
103
|
});
|
|
@@ -1215,8 +1215,8 @@ export function registerCoreDelegateW1b(delegate, core) {
|
|
|
1215
1215
|
const options = core.opts();
|
|
1216
1216
|
const root = resolveLiveRoot({ hqRoot: options.hqRoot });
|
|
1217
1217
|
const context = { cwd: root, root };
|
|
1218
|
-
const {
|
|
1219
|
-
if (!
|
|
1218
|
+
const { coreNativeDelegateEnabled } = await import("./core-native-family-flag.js");
|
|
1219
|
+
if (!await coreNativeDelegateEnabled()) {
|
|
1220
1220
|
const result = runBundledScript({ asset: ASSETS[name], args, cwd: root, hqRoot: root });
|
|
1221
1221
|
if (result.code !== 0)
|
|
1222
1222
|
process.exit(result.code);
|
|
@@ -1022,8 +1022,8 @@ export function registerCoreDelegateCommand(core, registerWave1b) {
|
|
|
1022
1022
|
const context = rootMode === "cwd"
|
|
1023
1023
|
? { cwd: process.cwd() }
|
|
1024
1024
|
: (() => { const root = resolveLiveRoot({ hqRoot: scope.hqRoot }); return { cwd: root, root }; })();
|
|
1025
|
-
const {
|
|
1026
|
-
if (!
|
|
1025
|
+
const { coreNativeDelegateEnabled } = await import("./core-native-family-flag.js");
|
|
1026
|
+
if (!await coreNativeDelegateEnabled()) {
|
|
1027
1027
|
const result = runBundledScript({ asset: SCRIPT_ASSETS[name], args, cwd: context.cwd, hqRoot: context.root });
|
|
1028
1028
|
if (result.code !== 0)
|
|
1029
1029
|
process.exit(result.code);
|
|
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import type { Command } from "commander";
|
|
4
4
|
import { type UtilityOutput } from "./core-native-utilities.js";
|
|
5
5
|
export declare const DM_BIND_ASSET = "core/scripts/hq-dm-bind.sh";
|
|
6
|
-
export declare const DM_BIND_HELP = "# hq-dm-bind.sh \u2014 bind the current session to one HQ DM channel, post\n# structured updates to it, and listen for replies (feedback / requests).\n#\n# Usage:\n# core/scripts/hq-dm-bind.sh bind <channel> # bind + set the read cursor to \"now\"\n# core/scripts/hq-dm-bind.sh status # show the binding and cursor\n# core/scripts/hq-dm-bind.sh post --title <t> [--state <s>] [--line <l>]... [--next <l>]... [--ask <l>]... [--to <name>]... [--topic <t>] [--under <id>] [--new-thread]\n# core/scripts/hq-dm-bind.sh post --title <t> - < body.md # body from stdin (already structured)\n# core/scripts/hq-dm-bind.sh threads # list the channel's recent top-level threads (id, replies, author, text)\n# core/scripts/hq-dm-bind.sh roster # print the channel members a post will @-mention\n# core/scripts/hq-dm-bind.sh poll # print new messages since the cursor, advance it\n# core/scripts/hq-dm-bind.sh listen [--interval 60] [--timeout 1800]\n# # block until a new message arrives (or timeout)\n# core/scripts/hq-dm-bind.sh unbind\n#\n# The binding lives in the session's meta.yaml (`dm_channel`) via hq-session.sh;\n# the cursor is `workspace/sessions/<sid>/dm-bind.cursor` (the last seen sort\n# key from `hq dm channel --json`). `listen` is meant to run detached\n# (Bash run_in_background): it exits 0 and prints the new messages the moment\n# one lands, exits 3 on timeout with nothing new.\n#\n# Every post opens with an @-mention of every other member of the channel, so\n# the update reaches people (HQ mentions are structured: the `hq` CLI resolves\n# the `@\"Display Name\"` tokens against the roster and sends the real mention\n";
|
|
6
|
+
export declare const DM_BIND_HELP = "# hq-dm-bind.sh \u2014 bind the current session to one HQ DM channel, post\n# structured updates to it, and listen for replies (feedback / requests).\n#\n# Usage:\n# core/scripts/hq-dm-bind.sh bind <channel> # bind + set the read cursor to \"now\"\n# core/scripts/hq-dm-bind.sh status # show the binding and cursor\n# core/scripts/hq-dm-bind.sh post --title <t> [--state <s>] [--line <l>]... [--next <l>]... [--ask <l>]... [--to <name>]... [--topic <t>] [--under <id>] [--new-thread]\n# core/scripts/hq-dm-bind.sh post --title <t> - < body.md # body from stdin (already structured)\n# core/scripts/hq-dm-bind.sh threads # list the channel's recent top-level threads (id, replies, author, text)\n# core/scripts/hq-dm-bind.sh roster # print the channel members a post will @-mention\n# core/scripts/hq-dm-bind.sh poll # print new messages since the cursor, advance it\n# core/scripts/hq-dm-bind.sh listen [--interval 60] [--timeout 1800]\n# # block until a new message arrives (or timeout)\n# core/scripts/hq-dm-bind.sh unbind\n#\n# The binding lives in the session's meta.yaml (`dm_channel`) via hq-session.sh;\n# the cursor is `workspace/sessions/<sid>/dm-bind.cursor` (the last seen sort\n# key from `hq dm channel --json`). `listen` is meant to run detached\n# (Bash run_in_background): it exits 0 and prints the new messages the moment\n# one lands, exits 3 on timeout with nothing new.\n#\n# Every post opens with an @-mention of every other member of the channel, so\n# the update reaches people (HQ mentions are structured: the `hq` CLI resolves\n# the `@\"Display Name\"` tokens against the roster and sends the real mention\n# list; an unresolvable name fails the post rather than posting text that looks\n# addressed but is not). The CLI rewrites a resolved `@\"Display Name\"` token to\n# `@Display Name`, so the room never shows the quotes.\n#\n# Posts thread by topic. The first post on a topic (`--topic`, else the title)\n# is a top-level root; every later post on the same topic in this session goes\n# out as a reply under it. `--under <id>` replies under an existing thread that\n# someone else (or an earlier session) started \u2014 a top-level post is only for a\n# subject the channel has no thread for yet. `--new-thread` starts a fresh root. The topic \u2192 root\n# map is `workspace/sessions/<sid>/dm-bind.threads`.\n#\n# A post ALWAYS mentions someone. `--to <name>` (repeatable) narrows the line to\n# the named members, for a reply to one person; without it the line names every\n# other human member (bots are left out unless named with --to, because a tagged\n# bot wakes and replies). There is no way to post unmentioned: an update nobody is\n# notified about does not get read. `post` refuses when the roster cannot be\n# read rather than sending a silent update.\n#\n# Post format (kept deliberately plain \u2014 see .claude/skills/dm-bind/SKILL.md):\n# @\"Ada Lovelace\" @\"Grace Hopper\"\n#\n# <title> \u2014 <state>\n#\n# \u2022 line\n# \u2022 line\n#\n# Next: line\n# Need from you: line\n";
|
|
7
7
|
export type DmBindDeps = {
|
|
8
8
|
root: string;
|
|
9
9
|
cwd: string;
|
|
@@ -3,54 +3,50 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
|
|
6
7
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
8
|
+
import { planHqSpawn } from "../utils/hq-spawn.js";
|
|
7
9
|
import { bundledShellUnboundValue, registerNativeUtility, utilityError } from "./core-native-utilities.js";
|
|
8
10
|
export const DM_BIND_ASSET = "core/scripts/hq-dm-bind.sh";
|
|
9
|
-
export const DM_BIND_HELP =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
process.stdin.on("data", (chunk) => { data += chunk; });
|
|
49
|
-
process.stdin.on("end", () => resolve(data));
|
|
50
|
-
process.stdin.on("error", reject);
|
|
51
|
-
}),
|
|
52
|
-
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
53
|
-
});
|
|
11
|
+
export const DM_BIND_HELP = "# hq-dm-bind.sh \u2014 bind the current session to one HQ DM channel, post\n# structured updates to it, and listen for replies (feedback / requests).\n#\n# Usage:\n# core/scripts/hq-dm-bind.sh bind <channel> # bind + set the read cursor to \"now\"\n# core/scripts/hq-dm-bind.sh status # show the binding and cursor\n# core/scripts/hq-dm-bind.sh post --title <t> [--state <s>] [--line <l>]... [--next <l>]... [--ask <l>]... [--to <name>]... [--topic <t>] [--under <id>] [--new-thread]\n# core/scripts/hq-dm-bind.sh post --title <t> - < body.md # body from stdin (already structured)\n# core/scripts/hq-dm-bind.sh threads # list the channel's recent top-level threads (id, replies, author, text)\n# core/scripts/hq-dm-bind.sh roster # print the channel members a post will @-mention\n# core/scripts/hq-dm-bind.sh poll # print new messages since the cursor, advance it\n# core/scripts/hq-dm-bind.sh listen [--interval 60] [--timeout 1800]\n# # block until a new message arrives (or timeout)\n# core/scripts/hq-dm-bind.sh unbind\n#\n# The binding lives in the session's meta.yaml (`dm_channel`) via hq-session.sh;\n# the cursor is `workspace/sessions/<sid>/dm-bind.cursor` (the last seen sort\n# key from `hq dm channel --json`). `listen` is meant to run detached\n# (Bash run_in_background): it exits 0 and prints the new messages the moment\n# one lands, exits 3 on timeout with nothing new.\n#\n# Every post opens with an @-mention of every other member of the channel, so\n# the update reaches people (HQ mentions are structured: the `hq` CLI resolves\n# the `@\"Display Name\"` tokens against the roster and sends the real mention\n# list; an unresolvable name fails the post rather than posting text that looks\n# addressed but is not). The CLI rewrites a resolved `@\"Display Name\"` token to\n# `@Display Name`, so the room never shows the quotes.\n#\n# Posts thread by topic. The first post on a topic (`--topic`, else the title)\n# is a top-level root; every later post on the same topic in this session goes\n# out as a reply under it. `--under <id>` replies under an existing thread that\n# someone else (or an earlier session) started \u2014 a top-level post is only for a\n# subject the channel has no thread for yet. `--new-thread` starts a fresh root. The topic \u2192 root\n# map is `workspace/sessions/<sid>/dm-bind.threads`.\n#\n# A post ALWAYS mentions someone. `--to <name>` (repeatable) narrows the line to\n# the named members, for a reply to one person; without it the line names every\n# other human member (bots are left out unless named with --to, because a tagged\n# bot wakes and replies). There is no way to post unmentioned: an update nobody is\n# notified about does not get read. `post` refuses when the roster cannot be\n# read rather than sending a silent update.\n#\n# Post format (kept deliberately plain \u2014 see .claude/skills/dm-bind/SKILL.md):\n# @\"Ada Lovelace\" @\"Grace Hopper\"\n#\n# <title> \u2014 <state>\n#\n# \u2022 line\n# \u2022 line\n#\n# Next: line\n# Need from you: line\n";
|
|
12
|
+
export const createDmBindDeps = (root, cwd, env, now, home = env.HOME || os.homedir()) => {
|
|
13
|
+
const d = {
|
|
14
|
+
root, cwd, env, home, now, fs,
|
|
15
|
+
runHq: (args, childEnv) => {
|
|
16
|
+
const envWithRoot = { ...childEnv, HQ_ROOT: root };
|
|
17
|
+
const spawnPlan = planHqSpawn(args, envWithRoot, cwd, d.platform ?? process.platform, (candidate) => {
|
|
18
|
+
try {
|
|
19
|
+
return d.fs.statSync(candidate).isFile();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const result = spawnSync(spawnPlan.cmd, spawnPlan.args, {
|
|
26
|
+
cwd,
|
|
27
|
+
env: envWithRoot,
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
shell: spawnPlan.shell,
|
|
30
|
+
...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
31
|
+
});
|
|
32
|
+
return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
|
|
33
|
+
},
|
|
34
|
+
runHqSession: (args, childEnv) => {
|
|
35
|
+
const result = spawnSync("bash", [path.join(root, "core/scripts/hq-session.sh"), ...args], { cwd, env: childEnv, encoding: "utf8" });
|
|
36
|
+
return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
|
|
37
|
+
},
|
|
38
|
+
fetch: globalThis.fetch,
|
|
39
|
+
readStdin: () => new Promise((resolve, reject) => {
|
|
40
|
+
let data = "";
|
|
41
|
+
process.stdin.setEncoding("utf8");
|
|
42
|
+
process.stdin.on("data", (chunk) => { data += chunk; });
|
|
43
|
+
process.stdin.on("end", () => resolve(data));
|
|
44
|
+
process.stdin.on("error", reject);
|
|
45
|
+
}),
|
|
46
|
+
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
47
|
+
};
|
|
48
|
+
return d;
|
|
49
|
+
};
|
|
54
50
|
const DM_BIND_API_TIMEOUT_MS = 15_000;
|
|
55
51
|
const fail = (message, code = 2) => utilityError(`hq-dm-bind: ${message}`, code);
|
|
56
52
|
const sessionDirectory = (d, sid) => path.join(d.root, "workspace/sessions", sid);
|
|
@@ -89,19 +85,24 @@ function ensureDir(d, sid) {
|
|
|
89
85
|
return directory;
|
|
90
86
|
}
|
|
91
87
|
function hasHqOnPath(d) {
|
|
88
|
+
if ((d.platform ?? process.platform) === "win32") {
|
|
89
|
+
return resolveWindowsExecutable("hq", d.env, (candidate) => {
|
|
90
|
+
try {
|
|
91
|
+
return d.fs.statSync(candidate).isFile();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}, d.cwd) !== null;
|
|
97
|
+
}
|
|
92
98
|
for (const directory of (d.env.PATH ?? "").split(path.delimiter)) {
|
|
93
99
|
if (!directory)
|
|
94
100
|
continue;
|
|
95
|
-
|
|
96
|
-
// .com files, and Windows file modes carry no execute bits, so accept exactly
|
|
97
|
-
// what the spawn can launch. npm's hq.cmd shim needs a shell and is not
|
|
98
|
-
// accepted here.
|
|
99
|
-
const windows = (d.platform ?? process.platform) === "win32";
|
|
100
|
-
const names = windows ? ["hq.exe", "hq.com"] : ["hq"];
|
|
101
|
+
const names = ["hq"];
|
|
101
102
|
for (const name of names) {
|
|
102
103
|
try {
|
|
103
104
|
const stat = d.fs.statSync(path.join(directory, name));
|
|
104
|
-
if (stat.isFile() && (
|
|
105
|
+
if (stat.isFile() && (stat.mode & 0o111) !== 0)
|
|
105
106
|
return true;
|
|
106
107
|
}
|
|
107
108
|
catch { /* PATH entry is absent */ }
|
|
@@ -646,6 +647,8 @@ async function listen(d, args) {
|
|
|
646
647
|
return { code: 3, stdout: output, stderr: "" };
|
|
647
648
|
}
|
|
648
649
|
export async function runDmBind(args, deps) {
|
|
650
|
+
if (args[0] === "-h" || args[0] === "--help")
|
|
651
|
+
return { code: 0, stdout: DM_BIND_HELP, stderr: "" };
|
|
649
652
|
if (!hasHqOnPath(deps))
|
|
650
653
|
return fail("missing dependency: hq");
|
|
651
654
|
const [command = "", ...rest] = args;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { planHqSpawn } from "../utils/hq-spawn.js";
|
|
5
6
|
/**
|
|
6
7
|
* `handoff finalize --help` is historically an unknown-argument error. Keep
|
|
7
8
|
* that fast error path out of the large handoff implementation module.
|
|
@@ -48,20 +49,24 @@ export function handoffFinalizeHelpResult(args, root, tmpRoot = "/tmp") {
|
|
|
48
49
|
return { code: 2, stdout: "", stderr: "Unknown arg: --help\n" };
|
|
49
50
|
}
|
|
50
51
|
async function launchHandoffPost(args, root) {
|
|
52
|
+
const childEnv = {
|
|
53
|
+
...process.env,
|
|
54
|
+
HQ_ROOT: root,
|
|
55
|
+
CLAUDE_PROJECT_DIR: root,
|
|
56
|
+
PWD: root,
|
|
57
|
+
HQ_NATIVE_HANDOFF_POST_CHILD: "1",
|
|
58
|
+
};
|
|
59
|
+
const spawnPlan = planHqSpawn(["core", "--hq-root", root, "handoff", "post", ...args], childEnv, root);
|
|
51
60
|
return await new Promise((resolve) => {
|
|
52
61
|
try {
|
|
53
|
-
const child = spawn(
|
|
62
|
+
const child = spawn(spawnPlan.cmd, spawnPlan.args, {
|
|
54
63
|
cwd: root,
|
|
55
|
-
env:
|
|
56
|
-
...process.env,
|
|
57
|
-
HQ_ROOT: root,
|
|
58
|
-
CLAUDE_PROJECT_DIR: root,
|
|
59
|
-
PWD: root,
|
|
60
|
-
HQ_NATIVE_HANDOFF_POST_CHILD: "1",
|
|
61
|
-
},
|
|
64
|
+
env: childEnv,
|
|
62
65
|
detached: true,
|
|
63
66
|
windowsHide: true,
|
|
64
67
|
stdio: "ignore",
|
|
68
|
+
shell: spawnPlan.shell,
|
|
69
|
+
...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
65
70
|
});
|
|
66
71
|
child.once("error", (error) => {
|
|
67
72
|
process.stderr.write(`${error.message}\n`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Native implementations of the handoff scaffold command family. */
|
|
2
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { planHqSpawn } from "../utils/hq-spawn.js";
|
|
3
4
|
import * as fs from "node:fs";
|
|
4
5
|
import * as os from "node:os";
|
|
5
6
|
import * as path from "node:path";
|
|
@@ -876,14 +877,17 @@ async function launchHandoffPost(args, root) {
|
|
|
876
877
|
delete childEnv[key];
|
|
877
878
|
childEnv.PWD = shellDisplayPath(root);
|
|
878
879
|
}
|
|
880
|
+
const spawnPlan = planHqSpawn(["core", "--hq-root", root, "handoff", "post", ...args], childEnv, root);
|
|
879
881
|
return await new Promise((resolve) => {
|
|
880
882
|
try {
|
|
881
|
-
const child = spawn(
|
|
883
|
+
const child = spawn(spawnPlan.cmd, spawnPlan.args, {
|
|
882
884
|
cwd: root,
|
|
883
885
|
env: childEnv,
|
|
884
886
|
detached: true,
|
|
885
887
|
windowsHide: true,
|
|
886
888
|
stdio: "ignore",
|
|
889
|
+
shell: spawnPlan.shell,
|
|
890
|
+
...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
887
891
|
});
|
|
888
892
|
child.once("error", (error) => resolve({ code: 127, stdout: "", stderr: `${error.message}\n` }));
|
|
889
893
|
child.once("spawn", () => {
|
|
@@ -1036,11 +1040,15 @@ export function runHandoffPostWorker(args, context) {
|
|
|
1036
1040
|
log(`workspace-sync: skipped (thread mirror missing for ${company})`);
|
|
1037
1041
|
continue;
|
|
1038
1042
|
}
|
|
1039
|
-
const
|
|
1043
|
+
const syncArgs = ["sync", "push", "--company", company, `companies/${company}/workspace`];
|
|
1044
|
+
const spawnPlan = planHqSpawn(syncArgs, process.env, root);
|
|
1045
|
+
const pushed = spawnSync(spawnPlan.cmd, spawnPlan.args, {
|
|
1040
1046
|
cwd: root,
|
|
1041
1047
|
env: process.env,
|
|
1042
1048
|
encoding: "utf8",
|
|
1043
1049
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1050
|
+
shell: spawnPlan.shell,
|
|
1051
|
+
...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
1044
1052
|
});
|
|
1045
1053
|
recordCommand(logMain, {
|
|
1046
1054
|
code: pushed.status ?? 1,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Shared fail-safe readers for shipped native core families. */
|
|
2
|
+
export declare function coreNativeFamilyEnabled(flagKey: string): Promise<boolean>;
|
|
3
|
+
export declare const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
|
|
4
|
+
export declare const CORE_NATIVE_ONTOLOGY_FLAG = "core-native-ontology";
|
|
5
|
+
export declare function coreNativeDelegateEnabled(): Promise<boolean>;
|
|
6
|
+
export declare function coreNativeOntologyEnabled(): Promise<boolean>;
|
|
7
|
+
//# sourceMappingURL=core-native-family-flag.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Shared fail-safe readers for shipped native core families. */
|
|
2
|
+
import { readLocalOverride } from "@indigoai-us/hq-flags-client";
|
|
3
|
+
export async function coreNativeFamilyEnabled(flagKey) {
|
|
4
|
+
const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
|
|
5
|
+
const override = readLocalOverride(flagKey, process.env);
|
|
6
|
+
if (override !== null)
|
|
7
|
+
return override;
|
|
8
|
+
return resolveProcessFlagGate(flagKey, { fallback: true }, () => true, undefined, {
|
|
9
|
+
acceptStaleSnapshot: true,
|
|
10
|
+
killSwitchWithoutCompany: true,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
|
|
14
|
+
export const CORE_NATIVE_ONTOLOGY_FLAG = "core-native-ontology";
|
|
15
|
+
export function coreNativeDelegateEnabled() {
|
|
16
|
+
return import("../lib/flag-registry.js").then(({ resolveProcessFlagGate }) => resolveProcessFlagGate(CORE_NATIVE_DELEGATE_FLAG, { fallback: false }, () => false));
|
|
17
|
+
}
|
|
18
|
+
export function coreNativeOntologyEnabled() {
|
|
19
|
+
return coreNativeFamilyEnabled(CORE_NATIVE_ONTOLOGY_FLAG);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=core-native-family-flag.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Default-off rollout gate for the native Wave 2 worktree/resume helpers. */
|
|
2
2
|
export const CORE_NATIVE_HANDOFF_FLAG = "core-native-handoff";
|
|
3
3
|
export async function coreNativeHandoffEnabled() {
|
|
4
|
-
const {
|
|
5
|
-
return
|
|
4
|
+
const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
|
|
5
|
+
return coreNativeFamilyEnabled(CORE_NATIVE_HANDOFF_FLAG);
|
|
6
6
|
}
|
|
7
7
|
//# sourceMappingURL=core-native-handoff-flag.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Default-off rollout gate for Wave 8 hook-time helper commands. */
|
|
2
2
|
export const CORE_NATIVE_HOOK_HELPERS_FLAG = "core-native-hook-helpers";
|
|
3
3
|
export async function coreNativeHookHelpersEnabled() {
|
|
4
|
-
const {
|
|
5
|
-
return
|
|
4
|
+
const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
|
|
5
|
+
return coreNativeFamilyEnabled(CORE_NATIVE_HOOK_HELPERS_FLAG);
|
|
6
6
|
}
|
|
7
7
|
//# sourceMappingURL=core-native-hook-helpers-flag.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Default-off rollout gate for the native scheduled-jobs helpers. */
|
|
2
2
|
export const CORE_NATIVE_JOBS_FLAG = "core-native-jobs";
|
|
3
3
|
export async function coreNativeJobsEnabled() {
|
|
4
|
-
const {
|
|
5
|
-
return
|
|
4
|
+
const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
|
|
5
|
+
return coreNativeFamilyEnabled(CORE_NATIVE_JOBS_FLAG);
|
|
6
6
|
}
|
|
7
7
|
//# sourceMappingURL=core-native-jobs-flag.js.map
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export const CORE_NATIVE_POLICY_FLAG = 'core-native-policy';
|
|
8
8
|
export async function coreNativePolicyEnabled() {
|
|
9
|
-
const {
|
|
10
|
-
return
|
|
9
|
+
const { coreNativeFamilyEnabled } = await import('./core-native-family-flag.js');
|
|
10
|
+
return coreNativeFamilyEnabled(CORE_NATIVE_POLICY_FLAG);
|
|
11
11
|
}
|
|
12
12
|
//# sourceMappingURL=core-native-policy-flag.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Default-off rollout gate for native session and Stop-hook plumbing. */
|
|
2
2
|
export const CORE_NATIVE_SESSION_FLAG = "core-native-session";
|
|
3
3
|
export async function coreNativeSessionEnabled() {
|
|
4
|
-
const {
|
|
5
|
-
return
|
|
4
|
+
const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
|
|
5
|
+
return coreNativeFamilyEnabled(CORE_NATIVE_SESSION_FLAG);
|
|
6
6
|
}
|
|
7
7
|
//# sourceMappingURL=core-native-session-flag.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Default-off rollout gate for native operator utility commands. */
|
|
2
2
|
export const CORE_NATIVE_UTILITIES_FLAG = "core-native-utilities";
|
|
3
3
|
export async function coreNativeUtilitiesEnabled() {
|
|
4
|
-
const {
|
|
5
|
-
return
|
|
4
|
+
const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
|
|
5
|
+
return coreNativeFamilyEnabled(CORE_NATIVE_UTILITIES_FLAG);
|
|
6
6
|
}
|
|
7
7
|
//# sourceMappingURL=core-native-utilities-flag.js.map
|
|
@@ -1170,8 +1170,8 @@ export function registerCoreOntologyNativeCommands(ontology, rootFor) {
|
|
|
1170
1170
|
const forwarded = commandIndex >= 0 ? argv.slice(commandIndex + 1)
|
|
1171
1171
|
: commandContext.args.length > 0 ? commandContext.args : args;
|
|
1172
1172
|
const liveRoot = entry.root === "live" ? rootFor() : process.cwd();
|
|
1173
|
-
const {
|
|
1174
|
-
if (!
|
|
1173
|
+
const { coreNativeOntologyEnabled } = await import("./core-native-family-flag.js");
|
|
1174
|
+
if (!await coreNativeOntologyEnabled()) {
|
|
1175
1175
|
const result = runBundledScript({
|
|
1176
1176
|
asset: `${ASSET}${entry.asset}`,
|
|
1177
1177
|
args: forwarded,
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
|
|
5
6
|
import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
|
|
6
7
|
import { registerNativeUtility } from "./core-native-utilities.js";
|
|
8
|
+
import { planHqSpawn } from "../utils/hq-spawn.js";
|
|
7
9
|
export const REFRESH_VAULT_ACCESS_ASSET = "core/scripts/refresh-vault-access.sh";
|
|
8
10
|
export const REFRESH_VAULT_ACCESS_HELP = `# hq-core: public
|
|
9
11
|
# refresh-vault-access.sh — build/refresh the local vault-access manifest that
|
|
@@ -39,7 +41,15 @@ export const REFRESH_VAULT_ACCESS_HELP = `# hq-core: public
|
|
|
39
41
|
#
|
|
40
42
|
`.replace(/^# ?/gm, "");
|
|
41
43
|
function defaultHq(args, cwd, env) {
|
|
42
|
-
const
|
|
44
|
+
const spawnPlan = planHqSpawn(args, env, cwd);
|
|
45
|
+
const result = spawnSync(spawnPlan.cmd, spawnPlan.args, {
|
|
46
|
+
cwd,
|
|
47
|
+
env,
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
50
|
+
shell: spawnPlan.shell,
|
|
51
|
+
...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
52
|
+
});
|
|
43
53
|
if (result.error)
|
|
44
54
|
return { code: 127, stdout: "", stderr: result.error.message, missing: result.error.code === "ENOENT" };
|
|
45
55
|
return { code: result.status ?? 128, stdout: String(result.stdout ?? ""), stderr: String(result.stderr ?? "") };
|
|
@@ -47,6 +57,9 @@ function defaultHq(args, cwd, env) {
|
|
|
47
57
|
function executableOnPath(name, value) {
|
|
48
58
|
if (!value)
|
|
49
59
|
return false;
|
|
60
|
+
if (process.platform === "win32" && name === "hq") {
|
|
61
|
+
return resolveWindowsExecutable(name, { PATH: value }) !== null;
|
|
62
|
+
}
|
|
50
63
|
for (const directory of value.split(path.delimiter)) {
|
|
51
64
|
if (!directory)
|
|
52
65
|
continue;
|
|
@@ -178,11 +178,10 @@ export declare function defaultMarketplaceDeps(): MarketplaceDeps;
|
|
|
178
178
|
* resolves the tarball from the marketplace and ALWAYS verifies it against the
|
|
179
179
|
* listing's approved hash/signature BEFORE extraction (US-021 → US-020).
|
|
180
180
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
181
|
+
* Marketplace listings are always signature-verified. Legacy unsigned
|
|
182
|
+
* listings deliberately fail before any bytes are extracted.
|
|
183
183
|
*/
|
|
184
184
|
export declare function fetchMarketplace(source: string, tmpDir: string, deps: MarketplaceDeps, opts?: {
|
|
185
|
-
requireSignature?: boolean;
|
|
186
185
|
company?: string;
|
|
187
186
|
}): Promise<FetchResult>;
|
|
188
187
|
export interface LatestResult {
|
|
@@ -446,11 +445,10 @@ export interface InstallPackOptions {
|
|
|
446
445
|
*/
|
|
447
446
|
marketplaceDeps?: MarketplaceDeps;
|
|
448
447
|
/**
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
* verified); set true once platform signing keys are provisioned.
|
|
448
|
+
* Acknowledge that a local directory source is unsigned. This has no effect
|
|
449
|
+
* for marketplace sources, which always require a platform signature.
|
|
452
450
|
*/
|
|
453
|
-
|
|
451
|
+
allowUnsigned?: boolean;
|
|
454
452
|
}
|
|
455
453
|
export declare function runPackInstallWithLease<T>(run: () => Promise<T>, leaseOptions?: ResidentCommandLeaseOptions): Promise<T>;
|
|
456
454
|
export declare function installPack(source: string, opts?: InstallPackOptions): Promise<void>;
|
|
@@ -39,6 +39,7 @@ import * as path from 'path';
|
|
|
39
39
|
import * as readline from 'readline';
|
|
40
40
|
import { parseUserYaml } from '../utils/user-yaml-error.js';
|
|
41
41
|
import { createHash, createPublicKey, verify as cryptoVerify, } from 'node:crypto';
|
|
42
|
+
import { trustedMarketplaceSigningKey } from '../lib/pack-signing-keys.js';
|
|
42
43
|
import { execFileSync, spawnSync } from 'child_process';
|
|
43
44
|
import chalk from 'chalk';
|
|
44
45
|
import semverSatisfies from 'semver/functions/satisfies.js';
|
|
@@ -414,8 +415,8 @@ export function defaultMarketplaceDeps() {
|
|
|
414
415
|
* resolves the tarball from the marketplace and ALWAYS verifies it against the
|
|
415
416
|
* listing's approved hash/signature BEFORE extraction (US-021 → US-020).
|
|
416
417
|
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
418
|
+
* Marketplace listings are always signature-verified. Legacy unsigned
|
|
419
|
+
* listings deliberately fail before any bytes are extracted.
|
|
419
420
|
*/
|
|
420
421
|
export async function fetchMarketplace(source, tmpDir, deps, opts = {}) {
|
|
421
422
|
const { slug, version } = parseMarketplaceSource(source);
|
|
@@ -448,12 +449,13 @@ export async function fetchMarketplace(source, tmpDir, deps, opts = {}) {
|
|
|
448
449
|
// downloaded bytes BEFORE extraction. A hash/signature mismatch throws
|
|
449
450
|
// `ArtifactVerificationError` and nothing is unpacked or wired. The hash is
|
|
450
451
|
// ALWAYS verified when present; signature is enforced when requireSignature.
|
|
452
|
+
const publicKey = trustedMarketplaceSigningKey(listing.signingKeyId, listing.publicKey);
|
|
451
453
|
verifyArtifact({
|
|
452
454
|
tarballBytes,
|
|
453
455
|
expectedHash: listing.contentHash,
|
|
454
456
|
signature: listing.signature,
|
|
455
|
-
publicKey
|
|
456
|
-
requireSignature:
|
|
457
|
+
publicKey,
|
|
458
|
+
requireSignature: true,
|
|
457
459
|
});
|
|
458
460
|
// Write to a tmp tarball and run it through the SAME hardened extractor
|
|
459
461
|
// (US-020) as every other tarball transport.
|
|
@@ -1697,6 +1699,9 @@ async function installPackWithoutLease(source, opts) {
|
|
|
1697
1699
|
// entrypoint keeps `hq packs update` fail-closed at its own call site
|
|
1698
1700
|
// instead (checked before it un-wires anything).
|
|
1699
1701
|
const transport = classify(source);
|
|
1702
|
+
if (opts.allowUnsigned && transport !== 'local') {
|
|
1703
|
+
throw new Error('--allow-unsigned is only valid for local directory sources; marketplace installs always require a platform signature.');
|
|
1704
|
+
}
|
|
1700
1705
|
const hqRoot = findHqRoot();
|
|
1701
1706
|
// `readHqVersion` (shared with `hq packs`) reads the CANONICAL
|
|
1702
1707
|
// `core/core.yaml` (hq-core ≥v14), falling back to the legacy
|
|
@@ -1724,7 +1729,6 @@ async function installPackWithoutLease(source, opts) {
|
|
|
1724
1729
|
break;
|
|
1725
1730
|
case 'marketplace':
|
|
1726
1731
|
fetched = await fetchMarketplace(source, tmpDir, opts.marketplaceDeps ?? defaultMarketplaceDeps(), {
|
|
1727
|
-
requireSignature: opts.requireSignature,
|
|
1728
1732
|
...(opts.company !== undefined ? { company: opts.company } : {}),
|
|
1729
1733
|
});
|
|
1730
1734
|
break;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { peekHqApiKey, requireApiKeyCapability, } from '../utils/resolve-vault-credential.js';
|
|
3
|
-
import { MARKETPLACE_PREFIX, PINNED_HQ_ANYWHERE_SOURCE, installPack, sourceMatchesPackPattern, } from './pack-install.js';
|
|
3
|
+
import { MARKETPLACE_PREFIX, PINNED_HQ_ANYWHERE_SOURCE, classify, installPack, sourceMatchesPackPattern, } from './pack-install.js';
|
|
4
4
|
import { anywhereRuntimeAllowed } from '../lib/anywhere-person-setting.js';
|
|
5
5
|
/**
|
|
6
6
|
* True when `source` installs through the marketplace — an explicit
|
|
@@ -43,6 +43,7 @@ export function registerPackageInstallCommand(parent) {
|
|
|
43
43
|
.option('--company <co>', 'Scope package secret authorization to a specific company')
|
|
44
44
|
.option('--allow-hooks', 'Skip the hooks confirmation prompt (content-pack flow)')
|
|
45
45
|
.option('--allow-mcp', 'Skip the MCP server confirmation prompt (content-pack flow)')
|
|
46
|
+
.option('--allow-unsigned', 'Acknowledge an unsigned local directory source; marketplace installs always require a signature')
|
|
46
47
|
.option('--branch', 'Follow a ref instead of SHA-pinning (git content-pack flow)')
|
|
47
48
|
.option('--global', 'Install HQ into an agent runtime at user scope (requires the hq-anywhere-runtime flag)')
|
|
48
49
|
.option('--runtime <runtime>', 'Runtime for --global (claude | codex | chatgpt | grok)', 'claude')
|
|
@@ -87,6 +88,21 @@ export function registerPackageInstallCommand(parent) {
|
|
|
87
88
|
process.exitCode = 1;
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
91
|
+
if (opts.allowUnsigned) {
|
|
92
|
+
let transport;
|
|
93
|
+
try {
|
|
94
|
+
transport = classify(source);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Bare slugs resolve through the marketplace below, where this
|
|
98
|
+
// option is never permitted.
|
|
99
|
+
}
|
|
100
|
+
if (transport !== 'local') {
|
|
101
|
+
console.error('error: --allow-unsigned is only valid for local directory sources; marketplace installs always require a platform signature.');
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
90
106
|
try {
|
|
91
107
|
// Gate the top-level `hq install` entrypoint only — `installPack`
|
|
92
108
|
// itself is also called by `hq packs update` (packs.ts), which must
|
|
@@ -106,6 +122,7 @@ export function registerPackageInstallCommand(parent) {
|
|
|
106
122
|
company: opts.company,
|
|
107
123
|
allowHooks: opts.allowHooks,
|
|
108
124
|
allowMcp: opts.allowMcp,
|
|
125
|
+
allowUnsigned: opts.allowUnsigned,
|
|
109
126
|
followBranch: opts.branch,
|
|
110
127
|
allowPinnedHqAnywhereName: true,
|
|
111
128
|
});
|
|
@@ -116,6 +133,7 @@ export function registerPackageInstallCommand(parent) {
|
|
|
116
133
|
company: opts.company,
|
|
117
134
|
allowHooks: opts.allowHooks,
|
|
118
135
|
allowMcp: opts.allowMcp,
|
|
136
|
+
allowUnsigned: opts.allowUnsigned,
|
|
119
137
|
followBranch: opts.branch,
|
|
120
138
|
});
|
|
121
139
|
}
|
|
@@ -128,6 +146,7 @@ export function registerPackageInstallCommand(parent) {
|
|
|
128
146
|
company: opts.company,
|
|
129
147
|
allowHooks: opts.allowHooks,
|
|
130
148
|
allowMcp: opts.allowMcp,
|
|
149
|
+
allowUnsigned: opts.allowUnsigned,
|
|
131
150
|
followBranch: opts.branch,
|
|
132
151
|
});
|
|
133
152
|
}
|
package/dist/fast-core.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { parseFastCore } from "./fast-scaffold.js";
|
|
12
|
+
import { isDefaultOnFastFamily, parseFastCore } from "./fast-scaffold.js";
|
|
13
13
|
const FAST_ROUTE_PROFILE_TOKEN = "hq-fast-route";
|
|
14
14
|
function wantsFastRouteProfile(env) {
|
|
15
15
|
return env.NODE_DEBUG === FAST_ROUTE_PROFILE_TOKEN;
|
|
@@ -50,7 +50,16 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
|
|
|
50
50
|
let familyEnabled = true;
|
|
51
51
|
if (entry.kind === "native" && entry.familyFlagKey) {
|
|
52
52
|
const envOverride = localBooleanFlagOverride(entry.familyFlagKey, process.env);
|
|
53
|
-
|
|
53
|
+
if (envOverride !== null) {
|
|
54
|
+
familyEnabled = envOverride;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const { fastFlagEnabled, fastFlagEnabledDefaultOn } = await import("./native-fast-route.js");
|
|
58
|
+
const readFamilyFlag = isDefaultOnFastFamily(entry.familyFlagKey)
|
|
59
|
+
? fastFlagEnabledDefaultOn
|
|
60
|
+
: fastFlagEnabled;
|
|
61
|
+
familyEnabled = await readFamilyFlag(entry.familyFlagKey, process.env, { numericEnvironmentOverride: false });
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
if (entry.kind === "native" && entry.fallbackToCommandGraph && !familyEnabled) {
|
|
56
65
|
const { runCli } = await import("./main.js");
|
|
@@ -164,6 +173,7 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
|
|
|
164
173
|
args: parsed.args,
|
|
165
174
|
cwd,
|
|
166
175
|
...(injectRoot ? { hqRoot } : {}),
|
|
176
|
+
msysScriptPath: process.platform === "win32",
|
|
167
177
|
...(Object.keys(childEnv).length ? { env: childEnv } : {}),
|
|
168
178
|
});
|
|
169
179
|
// Propagate verbatim: these scripts use their exit code as their interface.
|
package/dist/fast-scaffold.d.ts
CHANGED
|
@@ -49,6 +49,9 @@ export type FastScaffoldScript = {
|
|
|
49
49
|
/** Whether the bundled fast-route module closure needs Node's network timeout patch. */
|
|
50
50
|
requiresNodeNetworkCompatibility?: boolean;
|
|
51
51
|
};
|
|
52
|
+
/** Family kill switches whose owner-approved default is enabled. */
|
|
53
|
+
export declare const CORE_NATIVE_DEFAULT_ON_FAMILY_FLAGS: readonly ["core-native-session", "core-native-hook-helpers", "core-native-policy", "core-native-utilities", "core-native-jobs", "core-native-ontology", "core-native-handoff"];
|
|
54
|
+
export declare function isDefaultOnFastFamily(flagKey: string): boolean;
|
|
52
55
|
/** The relocated scripts that qualify for the entrypoint fast path. */
|
|
53
56
|
export declare const FAST_SCAFFOLD_SCRIPTS: readonly FastScaffoldScript[];
|
|
54
57
|
export type ParsedFastCore = {
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -9,6 +9,20 @@
|
|
|
9
9
|
* on every turn's Stop. The entrypoint routes eligible commands before loading
|
|
10
10
|
* the graph, and this module holds the parser + table that routing needs.
|
|
11
11
|
*/
|
|
12
|
+
/** Family kill switches whose owner-approved default is enabled. */
|
|
13
|
+
export const CORE_NATIVE_DEFAULT_ON_FAMILY_FLAGS = [
|
|
14
|
+
"core-native-session",
|
|
15
|
+
"core-native-hook-helpers",
|
|
16
|
+
"core-native-policy",
|
|
17
|
+
"core-native-utilities",
|
|
18
|
+
"core-native-jobs",
|
|
19
|
+
"core-native-ontology",
|
|
20
|
+
"core-native-handoff",
|
|
21
|
+
];
|
|
22
|
+
const defaultOnFamilyFlags = new Set(CORE_NATIVE_DEFAULT_ON_FAMILY_FLAGS);
|
|
23
|
+
export function isDefaultOnFastFamily(flagKey) {
|
|
24
|
+
return defaultOnFamilyFlags.has(flagKey);
|
|
25
|
+
}
|
|
12
26
|
/** The relocated scripts that qualify for the entrypoint fast path. */
|
|
13
27
|
export const FAST_SCAFFOLD_SCRIPTS = [
|
|
14
28
|
{
|
|
@@ -67,7 +67,10 @@ export declare function resolveFlagGate(reader: FlagReader | null | undefined, f
|
|
|
67
67
|
/** Resolve one explicit company's flag without relying on process-wide binding. */
|
|
68
68
|
export declare function resolveCompanyFlagGate(companyUid: string, idToken: string, flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean, dependencies?: CompanyFlagGateDependencies): Promise<boolean>;
|
|
69
69
|
/** Resolve a process gate against the shared snapshot reader, when configured. */
|
|
70
|
-
export declare function resolveProcessFlagGate(flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean, companyUid?: string
|
|
70
|
+
export declare function resolveProcessFlagGate(flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean, companyUid?: string, options?: {
|
|
71
|
+
acceptStaleSnapshot?: boolean;
|
|
72
|
+
killSwitchWithoutCompany?: boolean;
|
|
73
|
+
}): boolean;
|
|
71
74
|
/**
|
|
72
75
|
* The registry snapshot held by this process, or null when none has arrived
|
|
73
76
|
* (no endpoint configured, offline, or still fetching). Callers that must
|
|
@@ -9,9 +9,9 @@ import { Worker } from "node:worker_threads";
|
|
|
9
9
|
import { createFlagClient, isFlagSnapshot, readLocalOverride, resolveFlag, } from "@indigoai-us/hq-flags-client";
|
|
10
10
|
import { resolveFlagRegistryEndpoint, } from "./flag-registry-endpoint.js";
|
|
11
11
|
import { FLAG_REGISTRY_WORKER_SOURCE } from "./flag-registry-worker-source.generated.js";
|
|
12
|
-
import { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
12
|
+
import { resolveFlagSnapshotCompanyUid, resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
13
13
|
import { registerFlagRegistryRefreshWaiter } from "./flag-registry-refresh-wait.js";
|
|
14
|
-
import { flagSnapshotWriteFailureTags, readFlagSnapshotFile, writePersonFlagSnapshotFile, writeFlagSnapshotFile, } from "./flag-snapshot-file.js";
|
|
14
|
+
import { flagSnapshotWriteFailureTags, readDefaultOnFlagSnapshot, readFlagSnapshotFile, writePersonFlagSnapshotFile, writeFlagSnapshotFile, } from "./flag-snapshot-file.js";
|
|
15
15
|
function captureFlagRegistryException(error, tags, sentry) {
|
|
16
16
|
if (!sentry)
|
|
17
17
|
return;
|
|
@@ -348,10 +348,20 @@ export async function resolveCompanyFlagGate(companyUid, idToken, flagKey, looku
|
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
/** Resolve a process gate against the shared snapshot reader, when configured. */
|
|
351
|
-
export function resolveProcessFlagGate(flagKey, lookup, legacyFallback, companyUid) {
|
|
351
|
+
export function resolveProcessFlagGate(flagKey, lookup, legacyFallback, companyUid, options = {}) {
|
|
352
|
+
const defaultOnKillSwitch = options.killSwitchWithoutCompany === true;
|
|
353
|
+
const resolvedCompanyUid = defaultOnKillSwitch
|
|
354
|
+
? resolveFlagSnapshotCompanyUid()
|
|
355
|
+
: resolveProcessFlagCompanyContext().companyUid;
|
|
352
356
|
const targetCompanyUid = companyUid ?? (processReader
|
|
353
|
-
? refreshCompanyUid
|
|
354
|
-
:
|
|
357
|
+
? refreshCompanyUid ?? (defaultOnKillSwitch ? resolvedCompanyUid : undefined)
|
|
358
|
+
: resolvedCompanyUid);
|
|
359
|
+
if (!targetCompanyUid && defaultOnKillSwitch) {
|
|
360
|
+
const override = readLocalOverride(flagKey, process.env);
|
|
361
|
+
if (override !== null)
|
|
362
|
+
return override;
|
|
363
|
+
return readDefaultOnFlagSnapshot(flagKey);
|
|
364
|
+
}
|
|
355
365
|
const processSnapshotMatchesTarget = refreshCompanyUid === targetCompanyUid;
|
|
356
366
|
if (processReader && heldSnapshot && processSnapshotMatchesTarget) {
|
|
357
367
|
return resolveFlagGate(processReader, flagKey, lookup, legacyFallback);
|
|
@@ -360,7 +370,9 @@ export function resolveProcessFlagGate(flagKey, lookup, legacyFallback, companyU
|
|
|
360
370
|
// Use the last validated, fresh company snapshot in that window, and when
|
|
361
371
|
// the process reader was initialized for a different company scope.
|
|
362
372
|
if (targetCompanyUid) {
|
|
363
|
-
const cached = readFlagSnapshotFile(targetCompanyUid
|
|
373
|
+
const cached = readFlagSnapshotFile(targetCompanyUid, options.acceptStaleSnapshot
|
|
374
|
+
? { maxAgeMs: Number.POSITIVE_INFINITY }
|
|
375
|
+
: {});
|
|
364
376
|
if (cached) {
|
|
365
377
|
const snapshot = { version: 0, flags: cached.flags };
|
|
366
378
|
const reader = createProcessSnapshotReader([targetCompanyUid], () => snapshot);
|
|
@@ -37,4 +37,6 @@ export declare function readFlagSnapshotFile(companyUid: string, options?: {
|
|
|
37
37
|
now?: number;
|
|
38
38
|
maxAgeMs?: number;
|
|
39
39
|
}): PersistedFlagSnapshot | null;
|
|
40
|
+
/** Resolve a default-on kill switch from offline snapshots, allowing stale decisions. */
|
|
41
|
+
export declare function readDefaultOnFlagSnapshot(flagKey: string, companyUid?: string, env?: NodeJS.ProcessEnv): boolean;
|
|
40
42
|
//# sourceMappingURL=flag-snapshot-file.d.ts.map
|
|
@@ -372,4 +372,34 @@ export function readFlagSnapshotFile(companyUid, options = {}) {
|
|
|
372
372
|
}
|
|
373
373
|
return primary;
|
|
374
374
|
}
|
|
375
|
+
/** Resolve a default-on kill switch from offline snapshots, allowing stale decisions. */
|
|
376
|
+
export function readDefaultOnFlagSnapshot(flagKey, companyUid, env = process.env) {
|
|
377
|
+
const options = { env, maxAgeMs: Number.POSITIVE_INFINITY };
|
|
378
|
+
if (companyUid)
|
|
379
|
+
return readFlagSnapshotFile(companyUid, options)?.flags[flagKey] !== false;
|
|
380
|
+
const directories = new Set([path.dirname(personFlagSnapshotPath(env))]);
|
|
381
|
+
const fallbackProbe = fallbackSnapshotPath("cmp_000", false, env);
|
|
382
|
+
if (fallbackProbe)
|
|
383
|
+
directories.add(path.dirname(fallbackProbe));
|
|
384
|
+
for (const directory of directories) {
|
|
385
|
+
let names;
|
|
386
|
+
try {
|
|
387
|
+
names = fs.readdirSync(directory);
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
for (const name of names) {
|
|
393
|
+
if (name === "snapshot-person.json") {
|
|
394
|
+
if (readPersonFlagSnapshotFile(options)?.flags[flagKey] === false)
|
|
395
|
+
return false;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const match = /^snapshot-(cmp_[A-Za-z0-9]{3,128})\.json$/.exec(name);
|
|
399
|
+
if (match?.[1] && readFlagSnapshotFile(match[1], options)?.flags[flagKey] === false)
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
375
405
|
//# sourceMappingURL=flag-snapshot-file.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace artifact signing keys trusted by the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Public keys are intentionally embedded: authenticity must not depend on the
|
|
5
|
+
* marketplace response that carries the artifact. Add old keys here during a
|
|
6
|
+
* rotation so installs of previously signed listings continue to verify.
|
|
7
|
+
*
|
|
8
|
+
* `HQ_PACK_PUBLIC_KEY` is a development-only override for the current key id.
|
|
9
|
+
* It exists for local integration tests and non-production marketplace stacks;
|
|
10
|
+
* production installs must use the embedded platform key below.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MARKETPLACE_SIGNING_KEYS: {
|
|
13
|
+
readonly "mkt-sign-v1": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEALmDnMFl0vMytdx/TdP8+SmZjW2oES6B1nOESioUNc6k=\n-----END PUBLIC KEY-----";
|
|
14
|
+
};
|
|
15
|
+
export type MarketplaceSigningKeyId = keyof typeof MARKETPLACE_SIGNING_KEYS;
|
|
16
|
+
/**
|
|
17
|
+
* Return the trusted public key only when the listing names a pinned key and
|
|
18
|
+
* echoes the same public key. The echoed key is checked for API-contract drift,
|
|
19
|
+
* never trusted as the source of authenticity.
|
|
20
|
+
*/
|
|
21
|
+
export declare function trustedMarketplaceSigningKey(signingKeyId: unknown, listingPublicKey: unknown): string;
|
|
22
|
+
//# sourceMappingURL=pack-signing-keys.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace artifact signing keys trusted by the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Public keys are intentionally embedded: authenticity must not depend on the
|
|
5
|
+
* marketplace response that carries the artifact. Add old keys here during a
|
|
6
|
+
* rotation so installs of previously signed listings continue to verify.
|
|
7
|
+
*
|
|
8
|
+
* `HQ_PACK_PUBLIC_KEY` is a development-only override for the current key id.
|
|
9
|
+
* It exists for local integration tests and non-production marketplace stacks;
|
|
10
|
+
* production installs must use the embedded platform key below.
|
|
11
|
+
*/
|
|
12
|
+
export const MARKETPLACE_SIGNING_KEYS = {
|
|
13
|
+
"mkt-sign-v1": `-----BEGIN PUBLIC KEY-----
|
|
14
|
+
MCowBQYDK2VwAyEALmDnMFl0vMytdx/TdP8+SmZjW2oES6B1nOESioUNc6k=
|
|
15
|
+
-----END PUBLIC KEY-----`,
|
|
16
|
+
};
|
|
17
|
+
function normalizedPem(pem) {
|
|
18
|
+
return pem.replace(/\r\n/g, "\n").trim();
|
|
19
|
+
}
|
|
20
|
+
function developmentPublicKeyOverride() {
|
|
21
|
+
// Packaged production CLIs always use the embedded key. The override exists
|
|
22
|
+
// only for local development and test stacks.
|
|
23
|
+
if (process.env.NODE_ENV !== "development" && process.env.NODE_ENV !== "test") {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const override = process.env.HQ_PACK_PUBLIC_KEY;
|
|
27
|
+
return typeof override === "string" && override.trim().length > 0
|
|
28
|
+
? override
|
|
29
|
+
: undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Return the trusted public key only when the listing names a pinned key and
|
|
33
|
+
* echoes the same public key. The echoed key is checked for API-contract drift,
|
|
34
|
+
* never trusted as the source of authenticity.
|
|
35
|
+
*/
|
|
36
|
+
export function trustedMarketplaceSigningKey(signingKeyId, listingPublicKey) {
|
|
37
|
+
if (typeof signingKeyId !== "string" || signingKeyId.length === 0) {
|
|
38
|
+
throw new Error("Marketplace listing did not provide a signing key id — refusing to install unsigned or legacy listings.");
|
|
39
|
+
}
|
|
40
|
+
if (!Object.hasOwn(MARKETPLACE_SIGNING_KEYS, signingKeyId)) {
|
|
41
|
+
throw new Error(`Marketplace listing uses unpinned signing key "${signingKeyId}" — refusing to install.`);
|
|
42
|
+
}
|
|
43
|
+
if (typeof listingPublicKey !== "string" || listingPublicKey.trim().length === 0) {
|
|
44
|
+
throw new Error("Marketplace listing did not provide the public key for its signing key id — refusing to install.");
|
|
45
|
+
}
|
|
46
|
+
const keyId = signingKeyId;
|
|
47
|
+
const pinnedKey = developmentPublicKeyOverride() ?? MARKETPLACE_SIGNING_KEYS[keyId];
|
|
48
|
+
if (normalizedPem(listingPublicKey) !== normalizedPem(pinnedKey)) {
|
|
49
|
+
throw new Error(`Marketplace listing public key does not match pinned signing key "${keyId}" — refusing to install.`);
|
|
50
|
+
}
|
|
51
|
+
return pinnedKey;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=pack-signing-keys.js.map
|
|
@@ -3,4 +3,6 @@ import { type ProcessFlagCompanyContext } from "./process-flag-company-context-l
|
|
|
3
3
|
export type { ProcessFlagCompanyContext } from "./process-flag-company-context-light.js";
|
|
4
4
|
/** Resolve the same company identity the process-wide flag reader uses. */
|
|
5
5
|
export declare function resolveProcessFlagCompanyContext(env?: NodeJS.ProcessEnv): ProcessFlagCompanyContext;
|
|
6
|
+
/** Resolve the process scope, then the explicitly selected device default, without network access. */
|
|
7
|
+
export declare function resolveFlagSnapshotCompanyUid(env?: NodeJS.ProcessEnv): string | undefined;
|
|
6
8
|
//# sourceMappingURL=process-flag-company-context.d.ts.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Company scope shared by the full flag registry and its fast-path reader. */
|
|
2
2
|
import { readCompaniesManifestMap } from "./work-context/repo-remote.js";
|
|
3
|
+
import { readDeviceConfig } from "./work-context/config.js";
|
|
4
|
+
import { workContextRoot } from "./work-context/paths.js";
|
|
3
5
|
import { resolveDefaultHqRoot } from "../utils/hq-root-resolver.js";
|
|
4
6
|
import { normalizedCompanyUid, resolveLightProcessFlagCompanyContext, } from "./process-flag-company-context-light.js";
|
|
5
7
|
function companyUidForContext(context, env = process.env) {
|
|
@@ -20,4 +22,17 @@ export function resolveProcessFlagCompanyContext(env = process.env) {
|
|
|
20
22
|
...(context.companySlug ? { companySlug: context.companySlug } : {}),
|
|
21
23
|
};
|
|
22
24
|
}
|
|
25
|
+
/** Resolve the process scope, then the explicitly selected device default, without network access. */
|
|
26
|
+
export function resolveFlagSnapshotCompanyUid(env = process.env) {
|
|
27
|
+
const contextUid = resolveProcessFlagCompanyContext(env).companyUid;
|
|
28
|
+
if (contextUid)
|
|
29
|
+
return contextUid;
|
|
30
|
+
try {
|
|
31
|
+
const defaultCompany = readDeviceConfig({ root: workContextRoot(undefined, env) }).defaultCompany;
|
|
32
|
+
return defaultCompany?.enabled ? normalizedCompanyUid(defaultCompany.uid) : undefined;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
23
38
|
//# sourceMappingURL=process-flag-company-context.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Fast-route flag check for native entrypoint routes. */
|
|
2
2
|
export declare function nativeFastRouteEnabled(argv: readonly string[], env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
3
3
|
/** Reads a default-off fast-route gate from its local override or fresh snapshot. */
|
|
4
4
|
export declare function fastFlagEnabled(flagKey: string, env?: NodeJS.ProcessEnv, options?: {
|
|
5
5
|
numericEnvironmentOverride?: boolean;
|
|
6
6
|
}): Promise<boolean>;
|
|
7
|
-
/** Reads
|
|
7
|
+
/** Reads an enabled-by-default flag; any validated explicit false stays a kill switch, even when stale. */
|
|
8
8
|
export declare function fastFlagEnabledDefaultOn(flagKey: string, env?: NodeJS.ProcessEnv, options?: {
|
|
9
9
|
numericEnvironmentOverride?: boolean;
|
|
10
10
|
}): Promise<boolean>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Fast-route flag check for native entrypoint routes. */
|
|
2
2
|
import { localOverrideEnvVar, readLocalOverride } from "@indigoai-us/hq-flags-client";
|
|
3
3
|
import { parseFastCore } from "./fast-scaffold.js";
|
|
4
4
|
export async function nativeFastRouteEnabled(argv, env = process.env) {
|
|
5
5
|
const parsed = parseFastCore(argv);
|
|
6
6
|
if (!parsed || parsed.entry.kind !== "native")
|
|
7
7
|
return false;
|
|
8
|
-
return
|
|
8
|
+
return fastFlagEnabledDefaultOn(parsed.entry.flagKey, env);
|
|
9
9
|
}
|
|
10
10
|
/** Reads a default-off fast-route gate from its local override or fresh snapshot. */
|
|
11
11
|
export async function fastFlagEnabled(flagKey, env = process.env, options = {}) {
|
|
@@ -28,20 +28,18 @@ export async function fastFlagEnabled(flagKey, env = process.env, options = {})
|
|
|
28
28
|
return false;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
/** Reads
|
|
31
|
+
/** Reads an enabled-by-default flag; any validated explicit false stays a kill switch, even when stale. */
|
|
32
32
|
export async function fastFlagEnabledDefaultOn(flagKey, env = process.env, options = {}) {
|
|
33
33
|
try {
|
|
34
34
|
const override = nativeRouteOverride(flagKey, env, options.numericEnvironmentOverride !== false);
|
|
35
35
|
if (override !== null)
|
|
36
36
|
return override;
|
|
37
|
-
const [{
|
|
37
|
+
const [{ resolveFlagSnapshotCompanyUid }, { readDefaultOnFlagSnapshot }] = await Promise.all([
|
|
38
38
|
import("./lib/process-flag-company-context.js"),
|
|
39
39
|
import("./lib/flag-snapshot-file.js"),
|
|
40
40
|
]);
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
return true;
|
|
44
|
-
return readFlagSnapshotFile(companyUid, { env })?.flags[flagKey] !== false;
|
|
41
|
+
const companyUid = resolveFlagSnapshotCompanyUid(env);
|
|
42
|
+
return readDefaultOnFlagSnapshot(flagKey, companyUid, env);
|
|
45
43
|
}
|
|
46
44
|
catch {
|
|
47
45
|
// This flag is a kill switch: missing scope or cache data must leave the feature enabled.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type SpawnPlan } from "./windows-spawn.js";
|
|
2
|
+
/** Resolve the installed hq executable before applying the platform spawn plan. */
|
|
3
|
+
export declare function planHqSpawn(args: readonly string[], env: NodeJS.ProcessEnv, cwd: string, platform?: NodeJS.Platform, existsSync?: (candidate: string) => boolean): SpawnPlan;
|
|
4
|
+
//# sourceMappingURL=hq-spawn.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
|
|
3
|
+
import { planResolvedCommandSpawn } from "./windows-spawn.js";
|
|
4
|
+
/** Resolve the installed hq executable before applying the platform spawn plan. */
|
|
5
|
+
export function planHqSpawn(args, env, cwd, platform = process.platform, existsSync = fs.existsSync) {
|
|
6
|
+
const executable = platform === "win32"
|
|
7
|
+
? resolveWindowsExecutable("hq", env, existsSync, cwd) ?? "hq"
|
|
8
|
+
: "hq";
|
|
9
|
+
return planResolvedCommandSpawn(executable, args, env, platform);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=hq-spawn.js.map
|
|
@@ -42,7 +42,7 @@ export const RESIDENT_PACKAGE_MODULES = {
|
|
|
42
42
|
"bot-logs-follow": [],
|
|
43
43
|
mcp: ["../lib/mcp/tools.js", "../lib/mcp/prompts.js", "../lib/anywhere-person-setting.js", "../utils/cognito-session.js", "@indigoai-us/hq-cloud", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client"],
|
|
44
44
|
childWrapper: ["../main.js", "../lib/service-manager/startup-migration.js"],
|
|
45
|
-
coreJobRun: ["../lib/flag-registry.js"],
|
|
45
|
+
coreJobRun: ["../commands/core-native-jobs-flag.js", "../commands/core-native-family-flag.js", "../lib/flag-registry.js", "../lib/flag-snapshot-file.js"],
|
|
46
46
|
"daemon-call": ["../lib/daemon/protocol.js"],
|
|
47
47
|
daemon: [
|
|
48
48
|
"../sentry.js",
|
|
@@ -203,7 +203,10 @@ const LOADERS = {
|
|
|
203
203
|
() => import("../lib/service-manager/startup-migration.js"),
|
|
204
204
|
],
|
|
205
205
|
coreJobRun: [
|
|
206
|
+
() => import("../commands/core-native-jobs-flag.js"),
|
|
207
|
+
() => import("../commands/core-native-family-flag.js"),
|
|
206
208
|
() => import("../lib/flag-registry.js"),
|
|
209
|
+
() => import("../lib/flag-snapshot-file.js"),
|
|
207
210
|
],
|
|
208
211
|
"daemon-call": [
|
|
209
212
|
() => import("../lib/daemon/protocol.js"),
|