@indigoai-us/hq-cli 5.345.190 → 5.345.191
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 +22 -0
- package/dist/commands/bot.d.ts +3 -1
- package/dist/commands/bot.js +28 -4
- package/dist/lib/bot/fleet-local.d.ts +21 -2
- package/dist/lib/bot/fleet-local.js +62 -12
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.191] — 2026-11-02
|
|
6
|
+
- `hq bot list` and `hq bot status --json` rows now carry `engine: "hq-fleet-runtime"` for a bot on
|
|
7
|
+
the HQ Fleet Runtime, so the desktop app can tell a Fleet bot from a model-CLI bot (Create brief
|
|
8
|
+
uses this to prefer a Fleet bot).
|
|
9
|
+
- `hq bot auth` on an HQ Fleet Runtime bot gives the restarted gateway up to 15 s to come up before
|
|
10
|
+
reporting; it used to say "signed in, but did not start" while the bot was in fact starting.
|
|
11
|
+
- `hq bot status` on an HQ Fleet Runtime bot shows whether its gateway is installed as a login
|
|
12
|
+
service instead of a "launchd: not installed" line that was about the legacy bot daemon.
|
|
13
|
+
- `hq bot create --no-auto-approve` now reaches an HQ Fleet Runtime bot's profile (`autoApprove:
|
|
14
|
+
false`); with hq-fleet-local 0.4.18 the default profile runs with command approvals off, like a
|
|
15
|
+
model-CLI bot, so a headless bot no longer stalls on an approval nobody can answer.
|
|
16
|
+
- HQ Fleet Runtime pin moves to hq-fleet-local 0.4.18, which stores the bot's
|
|
17
|
+
machine credential with Windows DPAPI under PowerShell 5.1 and no longer
|
|
18
|
+
flashes a console on Windows. The CLI now trusts two release signing keys
|
|
19
|
+
(the 2026-10-30 key and a second key added 2026-11-01); a manifest signed by
|
|
20
|
+
either verifies, and any other key is rejected.
|
|
21
|
+
- `hq bot create` / `hq bot start` (HQ Fleet Runtime): every helper, uv and
|
|
22
|
+
Python child process now always starts with `windowsHide`, which a caller's
|
|
23
|
+
options can no longer turn off, so no console window flashes on Windows.
|
|
24
|
+
A new test records every child process in a Windows create, start, stop and
|
|
25
|
+
status run and checks each one.
|
|
26
|
+
|
|
5
27
|
## [5.345.190] — 2026-11-01
|
|
6
28
|
- `hq files purge <path> --version-id <id>` permanently deletes one old version of a vault file, after a confirmation prompt.
|
|
7
29
|
- `hq bot create` / `hq bot start` on Windows: the shared HQ Fleet Runtime now
|
package/dist/commands/bot.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type RuntimeSignInCheck, type RuntimeSignInIssue } from "../lib/bot/run
|
|
|
3
3
|
import type { BotStatusFile } from "../lib/bot/status.js";
|
|
4
4
|
import { probeBotRuntime, type RuntimeProbeResult } from "../lib/bot/probe.js";
|
|
5
5
|
import { type SpawnInteractive } from "../lib/bot/runtime-auth.js";
|
|
6
|
-
import { type BotLocalConfig, type MyLocalBot, type BotKind, type BotMemoryMode, type BotNotRunnableReason, type RuntimeCrash, type BotConfig, type BotRuntimeId, type LaunchctlExec, type BotRole } from "../lib/bot/index.js";
|
|
6
|
+
import { type BotLocalConfig, type MyLocalBot, type BotKind, type BotMemoryMode, type BotNotRunnableReason, type RuntimeCrash, type BotConfig, type BotRuntimeId, type LaunchctlExec, HQ_FLEET_RUNTIME_ENGINE_ID, type BotRole } from "../lib/bot/index.js";
|
|
7
7
|
/** Real launchctl; every failure is a non-ok result, never a throw. */
|
|
8
8
|
export declare const defaultLaunchctl: LaunchctlExec;
|
|
9
9
|
/** The desktop app creates HQ's setup bot under this name, always from the `setup` worker. */
|
|
@@ -13,6 +13,8 @@ export interface BotRow {
|
|
|
13
13
|
agentUid: string;
|
|
14
14
|
ownerUid: string;
|
|
15
15
|
runtime: BotRuntimeId;
|
|
16
|
+
/** `hq-fleet-runtime` for a bot on the HQ Fleet Runtime; absent for a model-CLI bot. */
|
|
17
|
+
engine?: typeof HQ_FLEET_RUNTIME_ENGINE_ID;
|
|
16
18
|
needsSignIn?: boolean;
|
|
17
19
|
authCommand?: string;
|
|
18
20
|
serviceInstallPending?: boolean;
|
package/dist/commands/bot.js
CHANGED
|
@@ -57,7 +57,7 @@ import { runtimeDisplayName, signInCommand } from "../lib/bot/runtime-sign-in.js
|
|
|
57
57
|
import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
|
|
58
58
|
import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.js";
|
|
59
59
|
import { BOT_LOGIN_RUNTIMES, botAuthEnv, botRuntimeAuth, capacityAccountHome, describeBotAuth, formatBotAuth, isBotLoginRuntime, restoreBotSessions, setAsideBotSessions, signInBotRuntime, signOutBotRuntime, withRuntimeAuth, } from "../lib/bot/runtime-auth.js";
|
|
60
|
-
import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, runtimeCrashSinceStart, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, HQ_FLEET_RUNTIME_ENGINE_ID, authenticateFleetLocalBot, bootstrapFleetLocalBot, buildSystemPrompt, controlFleetLocalBot, ensureFleetPython, fleetLocalGatewayPid, fleetLocalProfileNeedsAdoption, fleetProviderForRuntime, importFleetLocalMachineCredential, fleetProviderNeedsSignIn, fleetDefaultCompanies, fleetOwnerCompanies, fleetRuntimeIsDefault, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
|
|
60
|
+
import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, runtimeCrashSinceStart, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, HQ_FLEET_RUNTIME_ENGINE_ID, authenticateFleetLocalBot, bootstrapFleetLocalBot, buildSystemPrompt, controlFleetLocalBot, ensureFleetPython, fleetLocalGatewayPid, fleetLocalProfileNeedsAdoption, fleetLocalServiceInstalled, fleetProviderForRuntime, importFleetLocalMachineCredential, fleetProviderNeedsSignIn, fleetDefaultCompanies, fleetOwnerCompanies, fleetRuntimeIsDefault, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
|
|
61
61
|
function collectRepeated(value, previous) {
|
|
62
62
|
return [...previous, value];
|
|
63
63
|
}
|
|
@@ -152,6 +152,22 @@ function fleetBotSystemPrompt(name, config, overrides = {}) {
|
|
|
152
152
|
function fleetAuthCommand(name, config) {
|
|
153
153
|
return `hq bot auth ${name} --provider ${config.runtime === "grok" ? "grok" : config.runtime === "codex" ? "chatgpt" : "claude"}`;
|
|
154
154
|
}
|
|
155
|
+
/** How long a just-(re)started HQ Fleet gateway gets to write its pid before it counts as not started. */
|
|
156
|
+
function fleetStartWaitMs() {
|
|
157
|
+
const raw = Number(process.env.HQ_BOT_FLEET_START_WAIT_MS);
|
|
158
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : 15_000;
|
|
159
|
+
}
|
|
160
|
+
async function fleetGatewayComesUp(agentUid, waitMs, stepMs = 500) {
|
|
161
|
+
const deadline = Date.now() + waitMs;
|
|
162
|
+
for (;;) {
|
|
163
|
+
const pid = fleetLocalGatewayPid(agentUid);
|
|
164
|
+
if (pid !== null && isPidAlive(pid))
|
|
165
|
+
return true;
|
|
166
|
+
if (Date.now() >= deadline)
|
|
167
|
+
return false;
|
|
168
|
+
await new Promise((r) => setTimeout(r, stepMs));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
155
171
|
function runFleetRuntimeControl(name, config, action, json) {
|
|
156
172
|
if ((action === "install" || action === "start" || action === "restart") && config.fleetAuthPending === true) {
|
|
157
173
|
const authCommand = fleetAuthCommand(name, config);
|
|
@@ -236,6 +252,7 @@ function rowFor(name, opts = {}) {
|
|
|
236
252
|
agentUid: config.agentUid,
|
|
237
253
|
ownerUid: config.ownerUid,
|
|
238
254
|
runtime: config.runtime,
|
|
255
|
+
...(fleet ? { engine: HQ_FLEET_RUNTIME_ENGINE_ID } : {}),
|
|
239
256
|
model: config.model,
|
|
240
257
|
effort: effectiveBotEffort(config),
|
|
241
258
|
effortIsDefault: !config.effort,
|
|
@@ -477,6 +494,7 @@ async function adoptFleetRuntimeProfile(name, dir, config) {
|
|
|
477
494
|
systemPrompt: fleetBotSystemPrompt(name, config),
|
|
478
495
|
provider: fleetProviderForRuntime(config.runtime, config.model),
|
|
479
496
|
ownerCompanies: companyScope,
|
|
497
|
+
...(config.autoApprove === false ? { autoApprove: false } : {}),
|
|
480
498
|
}, { python, start: false });
|
|
481
499
|
patchBotConfig(dir, { engine: installation.engine });
|
|
482
500
|
const credentials = botCredsPath(dir);
|
|
@@ -1124,6 +1142,7 @@ async function runBotCreateLocked(name, opts) {
|
|
|
1124
1142
|
systemPrompt: fleetBotSystemPrompt(name, config, { kind, companies }),
|
|
1125
1143
|
provider: fleetProvider,
|
|
1126
1144
|
ownerCompanies,
|
|
1145
|
+
...(opts.autoApprove === false ? { autoApprove: false } : {}),
|
|
1127
1146
|
firstStart: {
|
|
1128
1147
|
intro: introDmText(name, runtime, intro, { kind, companies }),
|
|
1129
1148
|
...(kickoff ? { kickoff } : {}),
|
|
@@ -1452,7 +1471,12 @@ async function runBotStatus(nameArg, opts) {
|
|
|
1452
1471
|
console.log(` inbox: last poll ${status.lastInboxPollAt}`);
|
|
1453
1472
|
}
|
|
1454
1473
|
console.log(` answering: ${row.busy ? chalk.green(`yes${row.busySince ? `, since ${row.busySince}` : ""}`) : chalk.gray("no")}`);
|
|
1455
|
-
|
|
1474
|
+
if (fleet) {
|
|
1475
|
+
console.log(` service: ${fleetLocalServiceInstalled(config.agentUid) ? "installed (HQ Fleet gateway, starts at login)" : row.serviceInstallPending ? "not installed yet; installs on hq bot start" : "not installed"}`);
|
|
1476
|
+
}
|
|
1477
|
+
else {
|
|
1478
|
+
console.log(` launchd: ${row.daemonInstalled ? (row.daemonLoaded ? "installed, loaded" : "installed, not loaded") : "not installed"}`);
|
|
1479
|
+
}
|
|
1456
1480
|
if (row.daemonNodeMissing) {
|
|
1457
1481
|
console.log(` ${chalk.red(`node missing: ${row.daemonNodeMissing}`)} — launchd cannot start ${name}; fix with: hq bot start ${name}`);
|
|
1458
1482
|
}
|
|
@@ -2150,8 +2174,7 @@ async function runBotAuth(nameArg, opts) {
|
|
|
2150
2174
|
model: result.model,
|
|
2151
2175
|
...(b.config.fleetAuthPending === true ? { fleetAuthPending: false, fleetServiceInstallPending: !result.restarted, enabled: result.restarted } : {}),
|
|
2152
2176
|
});
|
|
2153
|
-
const
|
|
2154
|
-
const running = pid !== null && isPidAlive(pid);
|
|
2177
|
+
const running = await fleetGatewayComesUp(b.config.agentUid, result.restarted ? fleetStartWaitMs() : 0);
|
|
2155
2178
|
if (opts.json) {
|
|
2156
2179
|
console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result, needsSignIn: false, running, serviceInstallPending: b.config.fleetAuthPending === true && !result.restarted }, null, 2));
|
|
2157
2180
|
return;
|
|
@@ -2446,6 +2469,7 @@ async function adoptRemoteBotLocked(input) {
|
|
|
2446
2469
|
systemPrompt: fleetBotSystemPrompt(name, config),
|
|
2447
2470
|
provider: fleetProviderForRuntime(config.runtime, config.model),
|
|
2448
2471
|
ownerCompanies: await fleetOwnerCompanies(),
|
|
2472
|
+
...(config.autoApprove === false ? { autoApprove: false } : {}),
|
|
2449
2473
|
...(input.preserveFirstStart
|
|
2450
2474
|
? {
|
|
2451
2475
|
firstStart: {
|
|
@@ -41,6 +41,7 @@ export interface FleetLocalProfile {
|
|
|
41
41
|
transport?: string;
|
|
42
42
|
};
|
|
43
43
|
ownerCompanies?: string[];
|
|
44
|
+
autoApprove?: boolean;
|
|
44
45
|
hqRoot: string;
|
|
45
46
|
profileDir: string;
|
|
46
47
|
runtime: {
|
|
@@ -60,7 +61,7 @@ export interface FleetLocalInstallDeps {
|
|
|
60
61
|
hqReleaseLinks?: (release: FleetLocalReleasePin) => Promise<Map<string, string> | null>;
|
|
61
62
|
now?: () => Date;
|
|
62
63
|
release?: FleetLocalReleasePin;
|
|
63
|
-
publicKey?: string;
|
|
64
|
+
publicKey?: string | readonly string[];
|
|
64
65
|
}
|
|
65
66
|
export interface FleetLocalProfileInput {
|
|
66
67
|
agentUid: string;
|
|
@@ -79,6 +80,8 @@ export interface FleetLocalProfileInput {
|
|
|
79
80
|
};
|
|
80
81
|
/** Companies the owner's own turns may use; other senders get none of them. */
|
|
81
82
|
ownerCompanies?: string[];
|
|
83
|
+
/** False keeps the runtime's command approval prompts (`hq bot create --no-auto-approve`). */
|
|
84
|
+
autoApprove?: boolean;
|
|
82
85
|
}
|
|
83
86
|
/** The model the HQ Fleet Runtime asks for when a Claude bot names none. */
|
|
84
87
|
export declare const FLEET_CLAUDE_DEFAULT_MODEL = "claude-opus-5-5";
|
|
@@ -162,9 +165,16 @@ export interface FleetLocalRemovalResult {
|
|
|
162
165
|
credentialsRemoved: number;
|
|
163
166
|
}
|
|
164
167
|
export declare const HQ_FLEET_LOCAL_RELEASE: FleetLocalReleasePin;
|
|
168
|
+
/** hq-fleet-local trust/release-public.pem (rotated 2026-10-30; signed v0.4.17). */
|
|
165
169
|
export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEASJoapvo0WMTg+Wu3Jvt0nzlXLOBEr+G1aJRi0JkEb4A=\n-----END PUBLIC KEY-----\n";
|
|
170
|
+
/** hq-fleet-local trust/release-public-v2.pem (added 2026-11-01; signs v0.4.18). */
|
|
171
|
+
export declare const HQ_FLEET_LOCAL_PUBLIC_KEY_V2 = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAmgUi7pKTJUmf3yzwcuH65wSvn1NyJrDfZHZUX08sGN8=\n-----END PUBLIC KEY-----\n";
|
|
172
|
+
/** Every key a release manifest may be signed with. Any one must verify it. */
|
|
173
|
+
export declare const HQ_FLEET_LOCAL_PUBLIC_KEYS: readonly string[];
|
|
174
|
+
/** True when any trusted Ed25519 key verifies the manifest signature. */
|
|
175
|
+
export declare function fleetLocalManifestSignatureValid(manifest: Buffer, signature: Buffer, publicKey?: string | readonly string[]): boolean;
|
|
166
176
|
export declare function fleetHome(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
167
|
-
export declare function verifyFleetLocalRelease(files: FleetLocalReleaseFiles, release?: FleetLocalReleasePin, publicKey?: string): void;
|
|
177
|
+
export declare function verifyFleetLocalRelease(files: FleetLocalReleaseFiles, release?: FleetLocalReleasePin, publicKey?: string | readonly string[]): void;
|
|
168
178
|
/**
|
|
169
179
|
* Warning text when the signed manifest asks for a newer hq CLI than this
|
|
170
180
|
* one, or null. Advisory only: the pinned runtime is still installed, since
|
|
@@ -181,6 +191,15 @@ export declare const FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS: number;
|
|
|
181
191
|
export declare function installPinnedFleetLocalDistribution(deps?: FleetLocalInstallDeps): Promise<FleetLocalInstallation>;
|
|
182
192
|
/** The process id the bot's gateway recorded, when its file reads; the caller checks whether it is alive. */
|
|
183
193
|
export declare function fleetLocalGatewayPid(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome">): number | null;
|
|
194
|
+
/**
|
|
195
|
+
* Whether the bot's HQ Fleet gateway is installed as a login service. The
|
|
196
|
+
* runtime registers it with launchd as `ai.hq-fleet.gateway-<profile>`; null
|
|
197
|
+
* on platforms where hq-cli does not know the service layout.
|
|
198
|
+
*/
|
|
199
|
+
export declare function fleetLocalServiceInstalled(agentUid: string, deps?: {
|
|
200
|
+
platform?: NodeJS.Platform;
|
|
201
|
+
home?: string;
|
|
202
|
+
}): boolean | null;
|
|
184
203
|
export declare function fleetLocalProfileNeedsAdoption(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome" | "platform"> & {
|
|
185
204
|
release?: FleetLocalReleasePin;
|
|
186
205
|
}): boolean;
|
|
@@ -78,20 +78,42 @@ export function sharedRuntimeLayout(root, id, platform) {
|
|
|
78
78
|
}
|
|
79
79
|
export const HQ_FLEET_LOCAL_RELEASE = {
|
|
80
80
|
repository: "indigoai-us/hq-fleet-local",
|
|
81
|
-
tag: "v0.4.
|
|
82
|
-
version: "0.4.
|
|
83
|
-
artifact: "hq-fleet-local-0.4.
|
|
84
|
-
artifactSha256: "
|
|
81
|
+
tag: "v0.4.18",
|
|
82
|
+
version: "0.4.18",
|
|
83
|
+
artifact: "hq-fleet-local-0.4.18.zip",
|
|
84
|
+
artifactSha256: "194ae7da24d02205061b18172a84c972754bd0607ed3b4c32af7ac10d73904a7",
|
|
85
85
|
runtimeArtifact: "hq-fleet-runtime-abccac13bd9e.tar",
|
|
86
86
|
runtimeArtifactSha256: "29bcfe718ad7fbd3636afd00623772f2a25bc84c4f6f10d8d57f38e549d528d5",
|
|
87
|
-
manifestSha256: "
|
|
88
|
-
signatureSha256: "
|
|
87
|
+
manifestSha256: "ce14449198a0f5d8558ecc617c19551b7a2a31dba5bd563501a93d2ee58e5ff0",
|
|
88
|
+
signatureSha256: "ca7dac6bcc2d12f6e304d1940e7b00243f9c306e7f0a72fd4aa236b7778d3a93",
|
|
89
89
|
runtimeCommit: "abccac13bd9ea593b24c8cd9c0ba28d5c6050cc9",
|
|
90
90
|
};
|
|
91
|
+
/** hq-fleet-local trust/release-public.pem (rotated 2026-10-30; signed v0.4.17). */
|
|
91
92
|
export const HQ_FLEET_LOCAL_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
92
93
|
MCowBQYDK2VwAyEASJoapvo0WMTg+Wu3Jvt0nzlXLOBEr+G1aJRi0JkEb4A=
|
|
93
94
|
-----END PUBLIC KEY-----
|
|
94
95
|
`;
|
|
96
|
+
/** hq-fleet-local trust/release-public-v2.pem (added 2026-11-01; signs v0.4.18). */
|
|
97
|
+
export const HQ_FLEET_LOCAL_PUBLIC_KEY_V2 = `-----BEGIN PUBLIC KEY-----
|
|
98
|
+
MCowBQYDK2VwAyEAmgUi7pKTJUmf3yzwcuH65wSvn1NyJrDfZHZUX08sGN8=
|
|
99
|
+
-----END PUBLIC KEY-----
|
|
100
|
+
`;
|
|
101
|
+
/** Every key a release manifest may be signed with. Any one must verify it. */
|
|
102
|
+
export const HQ_FLEET_LOCAL_PUBLIC_KEYS = [HQ_FLEET_LOCAL_PUBLIC_KEY, HQ_FLEET_LOCAL_PUBLIC_KEY_V2];
|
|
103
|
+
function publicKeyList(publicKey) {
|
|
104
|
+
return typeof publicKey === "string" ? [publicKey] : publicKey;
|
|
105
|
+
}
|
|
106
|
+
/** True when any trusted Ed25519 key verifies the manifest signature. */
|
|
107
|
+
export function fleetLocalManifestSignatureValid(manifest, signature, publicKey = HQ_FLEET_LOCAL_PUBLIC_KEYS) {
|
|
108
|
+
return publicKeyList(publicKey).some((key) => {
|
|
109
|
+
try {
|
|
110
|
+
return crypto.verify(null, manifest, crypto.createPublicKey(key), signature);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
95
117
|
function digest(value) {
|
|
96
118
|
return crypto.createHash("sha256").update(value).digest("hex");
|
|
97
119
|
}
|
|
@@ -110,7 +132,7 @@ function engineMetadata(release) {
|
|
|
110
132
|
export function fleetHome(home = os.homedir(), env = process.env) {
|
|
111
133
|
return env.HQ_FLEET_HOME?.trim() || path.join(home, ".hq-fleet");
|
|
112
134
|
}
|
|
113
|
-
export function verifyFleetLocalRelease(files, release = HQ_FLEET_LOCAL_RELEASE, publicKey =
|
|
135
|
+
export function verifyFleetLocalRelease(files, release = HQ_FLEET_LOCAL_RELEASE, publicKey = HQ_FLEET_LOCAL_PUBLIC_KEYS) {
|
|
114
136
|
if (digest(files.archive) !== release.artifactSha256) {
|
|
115
137
|
throw expectedError(`HQ Fleet Local ${release.version} archive hash did not match the release pin.`);
|
|
116
138
|
}
|
|
@@ -142,7 +164,7 @@ export function verifyFleetLocalRelease(files, release = HQ_FLEET_LOCAL_RELEASE,
|
|
|
142
164
|
if (signature.algorithm !== "ed25519" || typeof signature.signature !== "string") {
|
|
143
165
|
throw expectedError(`HQ Fleet Local ${release.version} signature record is invalid.`);
|
|
144
166
|
}
|
|
145
|
-
const valid =
|
|
167
|
+
const valid = fleetLocalManifestSignatureValid(files.manifest, Buffer.from(signature.signature, "base64"), publicKey);
|
|
146
168
|
if (!valid) {
|
|
147
169
|
throw expectedError(`HQ Fleet Local ${release.version} manifest signature is invalid.`);
|
|
148
170
|
}
|
|
@@ -326,7 +348,7 @@ function installFiles(target, files, release, publicKey) {
|
|
|
326
348
|
fs.writeFileSync(path.join(staging, release.runtimeArtifact), files.runtimeArchive, { mode: 0o644 });
|
|
327
349
|
fs.writeFileSync(path.join(staging, "release-manifest.json"), files.manifest, { mode: 0o644 });
|
|
328
350
|
fs.writeFileSync(path.join(staging, "release-manifest.sig"), files.signature, { mode: 0o644 });
|
|
329
|
-
fs.writeFileSync(path.join(staging, "release-public.pem"), publicKey, { mode: 0o644 });
|
|
351
|
+
fs.writeFileSync(path.join(staging, "release-public.pem"), publicKeyList(publicKey).join(""), { mode: 0o644 });
|
|
330
352
|
fs.writeFileSync(path.join(staging, "install.json"), `${JSON.stringify({ schemaVersion: 1, release, engine: engineMetadata(release) }, null, 2)}\n`, { mode: 0o644 });
|
|
331
353
|
if (fs.existsSync(target))
|
|
332
354
|
fs.renameSync(target, backup);
|
|
@@ -366,7 +388,7 @@ function cachedRuntimeArchive(root, release) {
|
|
|
366
388
|
}
|
|
367
389
|
export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
368
390
|
const release = deps.release ?? HQ_FLEET_LOCAL_RELEASE;
|
|
369
|
-
const publicKey = deps.publicKey ??
|
|
391
|
+
const publicKey = deps.publicKey ?? HQ_FLEET_LOCAL_PUBLIC_KEYS;
|
|
370
392
|
const root = deps.fleetHome ?? fleetHome();
|
|
371
393
|
const dir = path.join(root, "distributions", "hq-fleet-local", release.version);
|
|
372
394
|
const current = installedFiles(dir, release);
|
|
@@ -513,7 +535,8 @@ function fleetLocalExecutables(agentUid, deps) {
|
|
|
513
535
|
function runJson(run, executable, args, options, label) {
|
|
514
536
|
let output;
|
|
515
537
|
try {
|
|
516
|
-
|
|
538
|
+
// windowsHide last: a helper, uv or Python child never opens a console window on Windows.
|
|
539
|
+
output = run(executable, args, { encoding: "utf8", ...options, windowsHide: true });
|
|
517
540
|
}
|
|
518
541
|
catch {
|
|
519
542
|
throw expectedError(`HQ Fleet Local ${label} failed.`);
|
|
@@ -541,6 +564,31 @@ export function fleetLocalGatewayPid(agentUid, deps = {}) {
|
|
|
541
564
|
return null;
|
|
542
565
|
}
|
|
543
566
|
}
|
|
567
|
+
/** The first hq-fleet-local release whose profile schema accepts `autoApprove`; older ones reject unknown fields. */
|
|
568
|
+
const AUTO_APPROVE_RELEASE = [0, 4, 18];
|
|
569
|
+
function releaseKnowsAutoApprove(release) {
|
|
570
|
+
const parts = release.version.split(".").map((part) => Number.parseInt(part, 10));
|
|
571
|
+
for (let i = 0; i < AUTO_APPROVE_RELEASE.length; i += 1) {
|
|
572
|
+
const have = Number.isFinite(parts[i]) ? parts[i] : 0;
|
|
573
|
+
if (have !== AUTO_APPROVE_RELEASE[i])
|
|
574
|
+
return have > AUTO_APPROVE_RELEASE[i];
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Whether the bot's HQ Fleet gateway is installed as a login service. The
|
|
580
|
+
* runtime registers it with launchd as `ai.hq-fleet.gateway-<profile>`; null
|
|
581
|
+
* on platforms where hq-cli does not know the service layout.
|
|
582
|
+
*/
|
|
583
|
+
export function fleetLocalServiceInstalled(agentUid, deps = {}) {
|
|
584
|
+
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid))
|
|
585
|
+
return null;
|
|
586
|
+
const platform = deps.platform ?? process.platform;
|
|
587
|
+
if (platform !== "darwin")
|
|
588
|
+
return null;
|
|
589
|
+
const home = deps.home ?? os.homedir();
|
|
590
|
+
return fs.existsSync(path.join(home, "Library", "LaunchAgents", `ai.hq-fleet.gateway-${profileName(agentUid)}.plist`));
|
|
591
|
+
}
|
|
544
592
|
export function fleetLocalProfileNeedsAdoption(agentUid, deps = {}) {
|
|
545
593
|
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid))
|
|
546
594
|
return true;
|
|
@@ -842,6 +890,7 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
842
890
|
botId: input.agentUid,
|
|
843
891
|
ownerUid: input.ownerUid,
|
|
844
892
|
...(input.ownerCompanies?.length ? { ownerCompanies: [...new Set(input.ownerCompanies)].sort() } : {}),
|
|
893
|
+
...(input.autoApprove === false && releaseKnowsAutoApprove(input.installation.release) ? { autoApprove: false } : {}),
|
|
845
894
|
provider: input.provider ?? { id: "custom:personal-chatgpt", model: "gpt-5.6-sol" },
|
|
846
895
|
hqRoot: path.resolve(input.hqRoot),
|
|
847
896
|
profileDir: path.resolve(dir),
|
|
@@ -958,10 +1007,11 @@ function checkedRun(run, executable, args, options, label) {
|
|
|
958
1007
|
const stdio = options.stdio === "ignore" ? ["ignore", "ignore", "pipe"] : options.stdio;
|
|
959
1008
|
try {
|
|
960
1009
|
run(executable, args, {
|
|
961
|
-
windowsHide: true,
|
|
962
1010
|
maxBuffer: 16 * 1024 * 1024,
|
|
963
1011
|
...options,
|
|
964
1012
|
...(stdio === undefined ? {} : { stdio }),
|
|
1013
|
+
// Last, so no caller can turn it off: no console window flashes on Windows.
|
|
1014
|
+
windowsHide: true,
|
|
965
1015
|
});
|
|
966
1016
|
}
|
|
967
1017
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.345.
|
|
3
|
+
"version": "5.345.191",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -114,11 +114,11 @@
|
|
|
114
114
|
"*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}": "eslint --cache --no-error-on-unmatched-pattern"
|
|
115
115
|
},
|
|
116
116
|
"optionalDependencies": {
|
|
117
|
-
"@indigoai-us/hq-hook-win32-x64": "5.345.
|
|
118
|
-
"@indigoai-us/hq-hook-win32-arm64": "5.345.
|
|
119
|
-
"@indigoai-us/hq-hook-darwin-x64": "5.345.
|
|
120
|
-
"@indigoai-us/hq-hook-darwin-arm64": "5.345.
|
|
121
|
-
"@indigoai-us/hq-hook-linux-x64": "5.345.
|
|
122
|
-
"@indigoai-us/hq-hook-linux-arm64": "5.345.
|
|
117
|
+
"@indigoai-us/hq-hook-win32-x64": "5.345.191",
|
|
118
|
+
"@indigoai-us/hq-hook-win32-arm64": "5.345.191",
|
|
119
|
+
"@indigoai-us/hq-hook-darwin-x64": "5.345.191",
|
|
120
|
+
"@indigoai-us/hq-hook-darwin-arm64": "5.345.191",
|
|
121
|
+
"@indigoai-us/hq-hook-linux-x64": "5.345.191",
|
|
122
|
+
"@indigoai-us/hq-hook-linux-arm64": "5.345.191"
|
|
123
123
|
}
|
|
124
124
|
}
|