@indigoai-us/hq-cli 5.345.170 → 5.345.172
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 +84 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/dist/command-catalog.generated.d.ts +206 -7
- package/dist/command-catalog.generated.js +252 -8
- package/dist/command-registration-plan.d.ts +7 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/access.d.ts +32 -1
- package/dist/commands/access.js +112 -3
- package/dist/commands/agent-kit.d.ts +1 -1
- package/dist/commands/agent-kit.js +3 -1
- package/dist/commands/agents.js +2 -1
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +179 -18
- package/dist/commands/cloud.d.ts +58 -1
- package/dist/commands/cloud.js +228 -10
- package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
- package/dist/commands/core-company-index-scope-flag.js +9 -2
- package/dist/commands/core-delegate-w1b.js +5 -2
- package/dist/commands/core-dm-bind-native.js +19 -4
- package/dist/commands/core-rebuild-index-fast.js +24 -6
- package/dist/commands/core.js +18 -6
- package/dist/commands/daemon.js +16 -1
- package/dist/commands/dm.d.ts +17 -3
- package/dist/commands/dm.js +121 -26
- package/dist/commands/files.d.ts +1 -0
- package/dist/commands/files.js +22 -4
- package/dist/commands/integrations-api.d.ts +4 -0
- package/dist/commands/integrations-api.js +18 -2
- package/dist/commands/integrations-connect.d.ts +10 -3
- package/dist/commands/integrations-connect.js +70 -6
- package/dist/commands/integrations-core.d.ts +23 -0
- package/dist/commands/integrations-core.js +100 -0
- package/dist/commands/integrations-manage.js +8 -1
- package/dist/commands/integrations.js +49 -5
- package/dist/commands/lanes-loop.d.ts +20 -0
- package/dist/commands/lanes-loop.js +69 -11
- package/dist/commands/lanes.js +9 -1
- package/dist/commands/rescue.d.ts +9 -0
- package/dist/commands/rescue.js +31 -0
- package/dist/commands/run.js +115 -40
- package/dist/commands/scan.d.ts +5 -1
- package/dist/commands/scan.js +16 -4
- package/dist/commands/secrets.js +50 -9
- package/dist/commands/sync-core.d.ts +22 -0
- package/dist/commands/sync-core.js +124 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2447 -2135
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
- package/dist/lib/agent-kit/inbox-state.js +41 -20
- package/dist/lib/agent-kit/kit-config.d.ts +2 -0
- package/dist/lib/agent-kit/kit-config.js +6 -0
- package/dist/lib/agent-kit/mcp/tools.js +12 -5
- package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
- package/dist/lib/agent-kit/run/heartbeat.js +6 -3
- package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
- package/dist/lib/agent-kit/run/inbox.js +42 -10
- package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
- package/dist/lib/agent-kit/run/request-timeout.js +51 -0
- package/dist/lib/bot/api.d.ts +1 -1
- package/dist/lib/bot/config.d.ts +10 -0
- package/dist/lib/bot/config.js +17 -1
- package/dist/lib/bot/fleet-local.d.ts +142 -0
- package/dist/lib/bot/fleet-local.js +689 -0
- package/dist/lib/bot/fleet-python.d.ts +47 -0
- package/dist/lib/bot/fleet-python.js +218 -0
- package/dist/lib/bot/index.d.ts +2 -0
- package/dist/lib/bot/index.js +2 -0
- package/dist/lib/bot/local-config.d.ts +3 -2
- package/dist/lib/bot/local-config.js +5 -2
- package/dist/lib/bot/promote.d.ts +10 -0
- package/dist/lib/bot/promote.js +3 -2
- package/dist/lib/bot/prompt.d.ts +6 -1
- package/dist/lib/bot/prompt.js +8 -3
- package/dist/lib/bot/runtime/index.js +8 -4
- package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
- package/dist/lib/bot/runtime/scratch-dir.js +41 -0
- package/dist/lib/caller-principal.d.ts +4 -0
- package/dist/lib/caller-principal.js +40 -0
- package/dist/lib/capacity/collectors/grok.js +8 -2
- package/dist/lib/cli-flag-keys.d.ts +2 -0
- package/dist/lib/cli-flag-keys.js +2 -0
- package/dist/lib/daemon/catalog.d.ts +12 -4
- package/dist/lib/daemon/catalog.js +70 -24
- package/dist/lib/daemon/config.d.ts +0 -7
- package/dist/lib/daemon/config.js +0 -32
- package/dist/lib/daemon/run.d.ts +5 -1
- package/dist/lib/daemon/run.js +48 -11
- package/dist/lib/daemon/supervisor.js +3 -1
- package/dist/lib/doctor/checks/release-links.d.ts +62 -1
- package/dist/lib/doctor/checks/release-links.js +188 -2
- package/dist/lib/doctor/fix/apply.js +54 -0
- package/dist/lib/doctor/fix/remediation.d.ts +8 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/index-render/index.d.ts +9 -0
- package/dist/lib/index-render/index.js +65 -5
- package/dist/lib/index-render/session-scope.d.ts +69 -0
- package/dist/lib/index-render/session-scope.js +279 -0
- package/dist/lib/lanes/admission.js +7 -20
- package/dist/lib/lanes/reconcile.js +13 -15
- package/dist/lib/lanes/stale-lock.d.ts +50 -0
- package/dist/lib/lanes/stale-lock.js +105 -0
- package/dist/lib/lanes/store.js +11 -76
- package/dist/lib/notify-recipient.d.ts +10 -4
- package/dist/lib/notify-recipient.js +16 -4
- package/dist/lib/secret-input-link.d.ts +42 -0
- package/dist/lib/secret-input-link.js +93 -0
- package/dist/lib/secret-scan/range.d.ts +57 -0
- package/dist/lib/secret-scan/range.js +266 -0
- package/dist/lib/secret-scan/repo-config.d.ts +18 -3
- package/dist/lib/secret-scan/repo-config.js +135 -2
- package/dist/lib/sync-core/engine-home.d.ts +44 -0
- package/dist/lib/sync-core/engine-home.js +193 -0
- package/dist/lib/sync-core/flag.d.ts +17 -0
- package/dist/lib/sync-core/flag.js +47 -0
- package/dist/lib/sync-core/reconcile.d.ts +31 -0
- package/dist/lib/sync-core/reconcile.js +196 -0
- package/dist/lib/sync-core/release.d.ts +32 -0
- package/dist/lib/sync-core/release.js +140 -0
- package/dist/lib/sync-core/rollout-state.d.ts +24 -0
- package/dist/lib/sync-core/rollout-state.js +94 -0
- package/dist/lib/vault-sync-switches.d.ts +5 -0
- package/dist/lib/vault-sync-switches.js +43 -0
- package/dist/run/script-bound-schema.d.ts +27 -0
- package/dist/run/script-bound-schema.js +97 -0
- package/dist/secrets-env-action.d.ts +2 -0
- package/dist/secrets-env-action.js +7 -2
- package/dist/utils/cognito-session.d.ts +3 -30
- package/dist/utils/cognito-session.js +2 -100
- package/dist/utils/machine-principal-state.d.ts +38 -0
- package/dist/utils/machine-principal-state.js +110 -0
- package/dist/utils/remote-list-not-converging.d.ts +21 -0
- package/dist/utils/remote-list-not-converging.js +40 -0
- package/dist/utils/safe-link.js +9 -0
- package/dist/utils/script-bound-exec.d.ts +158 -0
- package/dist/utils/script-bound-exec.js +568 -0
- package/dist/utils/sync-watchdog.d.ts +92 -0
- package/dist/utils/sync-watchdog.js +250 -0
- package/package.json +8 -8
- package/dist/lib/daemon/sync-core-canary.d.ts +0 -9
- package/dist/lib/daemon/sync-core-canary.js +0 -56
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import * as crypto from "node:crypto";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { HQ_FLEET_RUNTIME_ENGINE_ID, } from "./config.js";
|
|
7
|
+
import { ensureFleetPython, fleetUvEnv } from "./fleet-python.js";
|
|
8
|
+
export const HQ_FLEET_LOCAL_RELEASE = {
|
|
9
|
+
repository: "indigoai-us/hq-fleet-local",
|
|
10
|
+
tag: "v0.4.5",
|
|
11
|
+
version: "0.4.5",
|
|
12
|
+
artifact: "hq-fleet-local-0.4.5.zip",
|
|
13
|
+
artifactSha256: "5dc05b1a9c8a3fce0d748c1f642a908c4166958cf4e1c3cec8ee3799148eef80",
|
|
14
|
+
runtimeArtifact: "hq-fleet-runtime-abccac13bd9e.tar",
|
|
15
|
+
runtimeArtifactSha256: "29bcfe718ad7fbd3636afd00623772f2a25bc84c4f6f10d8d57f38e549d528d5",
|
|
16
|
+
manifestSha256: "ffb81d7cbb61cdd05e91208ac3eccf045789d9e068309da3164fe002991f7780",
|
|
17
|
+
signatureSha256: "3792d43c191c65c731e9f27a003d86334b4695a43feb8ac23a7b679f79531616",
|
|
18
|
+
runtimeCommit: "abccac13bd9ea593b24c8cd9c0ba28d5c6050cc9",
|
|
19
|
+
};
|
|
20
|
+
export const HQ_FLEET_LOCAL_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
21
|
+
MCowBQYDK2VwAyEAOOL7NRKQgjMYI5cWxLY+HGppXf2AtwpQbCg5v+nBksM=
|
|
22
|
+
-----END PUBLIC KEY-----
|
|
23
|
+
`;
|
|
24
|
+
function digest(value) {
|
|
25
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
26
|
+
}
|
|
27
|
+
function expectedError(message) {
|
|
28
|
+
return Object.assign(new Error(message), { expected: true });
|
|
29
|
+
}
|
|
30
|
+
function engineMetadata(release) {
|
|
31
|
+
return {
|
|
32
|
+
id: HQ_FLEET_RUNTIME_ENGINE_ID,
|
|
33
|
+
distribution: "hq-fleet-local",
|
|
34
|
+
version: release.version,
|
|
35
|
+
artifactSha256: release.artifactSha256,
|
|
36
|
+
runtimeCommit: release.runtimeCommit,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function fleetHome(home = os.homedir(), env = process.env) {
|
|
40
|
+
return env.HQ_FLEET_HOME?.trim() || path.join(home, ".hq-fleet");
|
|
41
|
+
}
|
|
42
|
+
export function verifyFleetLocalRelease(files, release = HQ_FLEET_LOCAL_RELEASE, publicKey = HQ_FLEET_LOCAL_PUBLIC_KEY) {
|
|
43
|
+
if (digest(files.archive) !== release.artifactSha256) {
|
|
44
|
+
throw expectedError(`HQ Fleet Local ${release.version} archive hash did not match the release pin.`);
|
|
45
|
+
}
|
|
46
|
+
if (digest(files.runtimeArchive) !== release.runtimeArtifactSha256) {
|
|
47
|
+
throw expectedError(`HQ Fleet Local ${release.version} runtime archive hash did not match the release pin.`);
|
|
48
|
+
}
|
|
49
|
+
if (digest(files.manifest) !== release.manifestSha256) {
|
|
50
|
+
throw expectedError(`HQ Fleet Local ${release.version} manifest hash did not match the release pin.`);
|
|
51
|
+
}
|
|
52
|
+
if (digest(files.signature) !== release.signatureSha256) {
|
|
53
|
+
throw expectedError(`HQ Fleet Local ${release.version} signature hash did not match the release pin.`);
|
|
54
|
+
}
|
|
55
|
+
let manifest;
|
|
56
|
+
let signature;
|
|
57
|
+
try {
|
|
58
|
+
manifest = JSON.parse(files.manifest.toString("utf8"));
|
|
59
|
+
signature = JSON.parse(files.signature.toString("utf8"));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw expectedError(`HQ Fleet Local ${release.version} release metadata is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
}
|
|
64
|
+
const distribution = manifest.distribution;
|
|
65
|
+
const runtime = manifest.runtime;
|
|
66
|
+
if (distribution?.name !== "hq-fleet-local" ||
|
|
67
|
+
distribution.version !== release.version ||
|
|
68
|
+
runtime?.commit !== release.runtimeCommit) {
|
|
69
|
+
throw expectedError(`HQ Fleet Local ${release.version} manifest does not match the pinned distribution.`);
|
|
70
|
+
}
|
|
71
|
+
if (signature.algorithm !== "ed25519" || typeof signature.signature !== "string") {
|
|
72
|
+
throw expectedError(`HQ Fleet Local ${release.version} signature record is invalid.`);
|
|
73
|
+
}
|
|
74
|
+
const valid = crypto.verify(null, files.manifest, crypto.createPublicKey(publicKey), Buffer.from(signature.signature, "base64"));
|
|
75
|
+
if (!valid) {
|
|
76
|
+
throw expectedError(`HQ Fleet Local ${release.version} manifest signature is invalid.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function resolveFleetReleaseToken(env = process.env) {
|
|
80
|
+
for (const name of ["HQ_FLEET_RELEASE_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"]) {
|
|
81
|
+
const value = env[name]?.trim();
|
|
82
|
+
if (value)
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const value = execFileSync("gh", ["auth", "token"], {
|
|
87
|
+
encoding: "utf8",
|
|
88
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
89
|
+
windowsHide: true,
|
|
90
|
+
}).trim();
|
|
91
|
+
if (value)
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw expectedError("HQ Fleet Local is a private Indigo preview. Sign in with GitHub CLI or set HQ_FLEET_RELEASE_GITHUB_TOKEN before creating this bot.");
|
|
96
|
+
}
|
|
97
|
+
throw expectedError("GitHub CLI returned an empty token for the private HQ Fleet Local release. Run gh auth login and retry.");
|
|
98
|
+
}
|
|
99
|
+
async function responseBytes(response, label) {
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw expectedError(`Could not download ${label}: GitHub returned HTTP ${response.status}.`);
|
|
102
|
+
}
|
|
103
|
+
return Buffer.from(await response.arrayBuffer());
|
|
104
|
+
}
|
|
105
|
+
export const FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
106
|
+
export const FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS = 5 * 60_000;
|
|
107
|
+
export const FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS = 20 * 60_000;
|
|
108
|
+
async function downloadFleetLocalRelease(release, fetcher, token) {
|
|
109
|
+
const headers = {
|
|
110
|
+
Accept: "application/vnd.github+json",
|
|
111
|
+
Authorization: `Bearer ${token}`,
|
|
112
|
+
"User-Agent": "hq-cli-fleet-local",
|
|
113
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
114
|
+
};
|
|
115
|
+
const releaseUrl = `https://api.github.com/repos/${release.repository}/releases/tags/${release.tag}`;
|
|
116
|
+
const response = await fetcher(releaseUrl, { headers, signal: AbortSignal.timeout(30_000) });
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
throw expectedError(`Could not read HQ Fleet Local ${release.tag}: GitHub returned HTTP ${response.status}.`);
|
|
119
|
+
}
|
|
120
|
+
const body = (await response.json());
|
|
121
|
+
const assets = new Map((body.assets ?? [])
|
|
122
|
+
.filter((asset) => !!asset.name && !!asset.url)
|
|
123
|
+
.map((asset) => [asset.name, asset.url]));
|
|
124
|
+
const get = async (name, timeoutMs) => {
|
|
125
|
+
const url = assets.get(name);
|
|
126
|
+
if (!url)
|
|
127
|
+
throw expectedError(`HQ Fleet Local ${release.tag} is missing release asset ${name}.`);
|
|
128
|
+
if (new URL(url).origin !== "https://api.github.com") {
|
|
129
|
+
throw expectedError(`HQ Fleet Local ${release.tag} returned an untrusted asset URL for ${name}.`);
|
|
130
|
+
}
|
|
131
|
+
return responseBytes(await fetcher(url, {
|
|
132
|
+
headers: { ...headers, Accept: "application/octet-stream" },
|
|
133
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
134
|
+
}), name);
|
|
135
|
+
};
|
|
136
|
+
const [archive, runtimeArchive, manifest, signature] = await Promise.all([
|
|
137
|
+
get(release.artifact, FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS),
|
|
138
|
+
get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
|
|
139
|
+
get("release-manifest.json", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
140
|
+
get("release-manifest.sig", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
141
|
+
]);
|
|
142
|
+
return { archive, runtimeArchive, manifest, signature };
|
|
143
|
+
}
|
|
144
|
+
function installedFiles(dir, release) {
|
|
145
|
+
const archive = path.join(dir, release.artifact);
|
|
146
|
+
const runtimeArchive = path.join(dir, release.runtimeArtifact);
|
|
147
|
+
const manifest = path.join(dir, "release-manifest.json");
|
|
148
|
+
const signature = path.join(dir, "release-manifest.sig");
|
|
149
|
+
if (![archive, runtimeArchive, manifest, signature].every((file) => fs.existsSync(file)))
|
|
150
|
+
return null;
|
|
151
|
+
return {
|
|
152
|
+
archive: fs.readFileSync(archive),
|
|
153
|
+
runtimeArchive: fs.readFileSync(runtimeArchive),
|
|
154
|
+
manifest: fs.readFileSync(manifest),
|
|
155
|
+
signature: fs.readFileSync(signature),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function installFiles(target, files, release, publicKey) {
|
|
159
|
+
const parent = path.dirname(target);
|
|
160
|
+
fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
161
|
+
const staging = path.join(parent, `.install-${release.version}-${process.pid}-${crypto.randomUUID()}`);
|
|
162
|
+
const backup = path.join(parent, `.replace-${release.version}-${process.pid}-${crypto.randomUUID()}`);
|
|
163
|
+
fs.mkdirSync(staging, { mode: 0o700 });
|
|
164
|
+
try {
|
|
165
|
+
fs.writeFileSync(path.join(staging, release.artifact), files.archive, { mode: 0o644 });
|
|
166
|
+
fs.writeFileSync(path.join(staging, release.runtimeArtifact), files.runtimeArchive, { mode: 0o644 });
|
|
167
|
+
fs.writeFileSync(path.join(staging, "release-manifest.json"), files.manifest, { mode: 0o644 });
|
|
168
|
+
fs.writeFileSync(path.join(staging, "release-manifest.sig"), files.signature, { mode: 0o644 });
|
|
169
|
+
fs.writeFileSync(path.join(staging, "release-public.pem"), publicKey, { mode: 0o644 });
|
|
170
|
+
fs.writeFileSync(path.join(staging, "install.json"), `${JSON.stringify({ schemaVersion: 1, release, engine: engineMetadata(release) }, null, 2)}\n`, { mode: 0o644 });
|
|
171
|
+
if (fs.existsSync(target))
|
|
172
|
+
fs.renameSync(target, backup);
|
|
173
|
+
try {
|
|
174
|
+
fs.renameSync(staging, target);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (fs.existsSync(backup) && !fs.existsSync(target))
|
|
178
|
+
fs.renameSync(backup, target);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
if (fs.existsSync(backup))
|
|
182
|
+
fs.rmSync(backup, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
if (fs.existsSync(staging))
|
|
186
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
190
|
+
const release = deps.release ?? HQ_FLEET_LOCAL_RELEASE;
|
|
191
|
+
const publicKey = deps.publicKey ?? HQ_FLEET_LOCAL_PUBLIC_KEY;
|
|
192
|
+
const root = deps.fleetHome ?? fleetHome();
|
|
193
|
+
const dir = path.join(root, "distributions", "hq-fleet-local", release.version);
|
|
194
|
+
const current = installedFiles(dir, release);
|
|
195
|
+
if (current) {
|
|
196
|
+
verifyFleetLocalRelease(current, release, publicKey);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
const token = (deps.token ?? resolveFleetReleaseToken)();
|
|
200
|
+
const files = await downloadFleetLocalRelease(release, deps.fetch ?? fetch, token);
|
|
201
|
+
verifyFleetLocalRelease(files, release, publicKey);
|
|
202
|
+
installFiles(dir, files, release, publicKey);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
release,
|
|
206
|
+
dir,
|
|
207
|
+
archive: path.join(dir, release.artifact),
|
|
208
|
+
runtimeArchive: path.join(dir, release.runtimeArtifact),
|
|
209
|
+
manifest: path.join(dir, "release-manifest.json"),
|
|
210
|
+
signature: path.join(dir, "release-manifest.sig"),
|
|
211
|
+
engine: engineMetadata(release),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function profileRoot(agentUid, root) {
|
|
215
|
+
return path.join(root, "profiles", agentUid);
|
|
216
|
+
}
|
|
217
|
+
function venvExecutable(runtimeDir, name, platform) {
|
|
218
|
+
return platform === "win32"
|
|
219
|
+
? path.join(runtimeDir, "Scripts", `${name}.exe`)
|
|
220
|
+
: path.join(runtimeDir, "bin", name);
|
|
221
|
+
}
|
|
222
|
+
function fleetLocalExecutables(agentUid, deps) {
|
|
223
|
+
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid)) {
|
|
224
|
+
throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
|
|
225
|
+
}
|
|
226
|
+
const root = deps.fleetHome ?? fleetHome();
|
|
227
|
+
const platform = deps.platform ?? process.platform;
|
|
228
|
+
const profileDir = profileRoot(agentUid, root);
|
|
229
|
+
const runtimeDir = path.join(profileDir, "runtime");
|
|
230
|
+
const profile = path.join(profileDir, "profile.json");
|
|
231
|
+
const runtime = venvExecutable(runtimeDir, "hq-fleet", platform);
|
|
232
|
+
const helper = venvExecutable(runtimeDir, "hq-fleet-local", platform);
|
|
233
|
+
if (![profile, runtime, helper].every((item) => fs.existsSync(item))) {
|
|
234
|
+
throw expectedError(`HQ Fleet Local is not fully installed for ${agentUid}.`);
|
|
235
|
+
}
|
|
236
|
+
return { profileDir, profile, runtime, helper };
|
|
237
|
+
}
|
|
238
|
+
function runJson(run, executable, args, options, label) {
|
|
239
|
+
let output;
|
|
240
|
+
try {
|
|
241
|
+
output = run(executable, args, { windowsHide: true, encoding: "utf8", ...options });
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
throw expectedError(`HQ Fleet Local ${label} failed.`);
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
const value = JSON.parse(Buffer.isBuffer(output) ? output.toString("utf8") : output);
|
|
248
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
249
|
+
throw new Error("not an object");
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
throw expectedError(`HQ Fleet Local ${label} returned an invalid result.`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** The process id the bot's gateway recorded, when its file reads; the caller checks whether it is alive. */
|
|
257
|
+
export function fleetLocalGatewayPid(agentUid, deps = {}) {
|
|
258
|
+
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid))
|
|
259
|
+
return null;
|
|
260
|
+
const file = path.join(profileRoot(agentUid, deps.fleetHome ?? fleetHome()), "gateway.pid");
|
|
261
|
+
try {
|
|
262
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
263
|
+
return typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0 ? parsed.pid : null;
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
export function controlFleetLocalBot(agentUid, action, deps = {}) {
|
|
270
|
+
const run = deps.run ?? execFileSync;
|
|
271
|
+
const files = fleetLocalExecutables(agentUid, deps);
|
|
272
|
+
return runJson(run, files.helper, ["service", action, "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, `service ${action}`);
|
|
273
|
+
}
|
|
274
|
+
const providerProfiles = {
|
|
275
|
+
chatgpt: {
|
|
276
|
+
provider: "custom:personal-chatgpt",
|
|
277
|
+
runtimeProvider: "openai-codex",
|
|
278
|
+
model: "gpt-5.6-sol",
|
|
279
|
+
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
280
|
+
transport: "codex_responses",
|
|
281
|
+
oauth: true,
|
|
282
|
+
},
|
|
283
|
+
codex: {
|
|
284
|
+
provider: "custom:personal-chatgpt",
|
|
285
|
+
runtimeProvider: "openai-codex",
|
|
286
|
+
model: "gpt-5.6-sol",
|
|
287
|
+
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
288
|
+
transport: "codex_responses",
|
|
289
|
+
oauth: true,
|
|
290
|
+
},
|
|
291
|
+
"openai-codex": {
|
|
292
|
+
provider: "custom:personal-chatgpt",
|
|
293
|
+
runtimeProvider: "openai-codex",
|
|
294
|
+
model: "gpt-5.6-sol",
|
|
295
|
+
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
296
|
+
transport: "codex_responses",
|
|
297
|
+
oauth: true,
|
|
298
|
+
},
|
|
299
|
+
grok: {
|
|
300
|
+
provider: "custom:personal-grok",
|
|
301
|
+
runtimeProvider: "xai-oauth",
|
|
302
|
+
model: "grok-4",
|
|
303
|
+
baseUrl: "https://api.x.ai/v1",
|
|
304
|
+
transport: "chat_completions",
|
|
305
|
+
oauth: true,
|
|
306
|
+
},
|
|
307
|
+
xai: {
|
|
308
|
+
provider: "custom:personal-grok",
|
|
309
|
+
runtimeProvider: "xai-oauth",
|
|
310
|
+
model: "grok-4",
|
|
311
|
+
baseUrl: "https://api.x.ai/v1",
|
|
312
|
+
transport: "chat_completions",
|
|
313
|
+
oauth: true,
|
|
314
|
+
},
|
|
315
|
+
"xai-oauth": {
|
|
316
|
+
provider: "custom:personal-grok",
|
|
317
|
+
runtimeProvider: "xai-oauth",
|
|
318
|
+
model: "grok-4",
|
|
319
|
+
baseUrl: "https://api.x.ai/v1",
|
|
320
|
+
transport: "chat_completions",
|
|
321
|
+
oauth: true,
|
|
322
|
+
},
|
|
323
|
+
claude: {
|
|
324
|
+
provider: "custom:personal-anthropic",
|
|
325
|
+
runtimeProvider: "anthropic",
|
|
326
|
+
model: "claude-sonnet-4-6",
|
|
327
|
+
baseUrl: "https://api.anthropic.com",
|
|
328
|
+
transport: "anthropic_messages",
|
|
329
|
+
oauth: false,
|
|
330
|
+
},
|
|
331
|
+
anthropic: {
|
|
332
|
+
provider: "custom:personal-anthropic",
|
|
333
|
+
runtimeProvider: "anthropic",
|
|
334
|
+
model: "claude-sonnet-4-6",
|
|
335
|
+
baseUrl: "https://api.anthropic.com",
|
|
336
|
+
transport: "anthropic_messages",
|
|
337
|
+
oauth: false,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
function replaceProfileProvider(profilePath, agentUid, provider) {
|
|
341
|
+
let profile;
|
|
342
|
+
try {
|
|
343
|
+
profile = JSON.parse(fs.readFileSync(profilePath, "utf8"));
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
throw expectedError(`HQ Fleet Local profile is unreadable for ${agentUid}.`);
|
|
347
|
+
}
|
|
348
|
+
if (profile.schemaVersion !== 1 || profile.botId !== agentUid) {
|
|
349
|
+
throw expectedError(`HQ Fleet Local profile does not belong to ${agentUid}.`);
|
|
350
|
+
}
|
|
351
|
+
profile.provider = provider;
|
|
352
|
+
const credentialProviders = Array.isArray(profile.credentialProviders)
|
|
353
|
+
? profile.credentialProviders.filter((value) => typeof value === "string")
|
|
354
|
+
: [];
|
|
355
|
+
profile.credentialProviders = [...new Set([...credentialProviders, provider.credentialProvider])];
|
|
356
|
+
const temporary = `${profilePath}.${process.pid}.${crypto.randomUUID()}`;
|
|
357
|
+
fs.writeFileSync(temporary, `${JSON.stringify(profile, null, 2)}\n`, { mode: 0o600 });
|
|
358
|
+
fs.renameSync(temporary, profilePath);
|
|
359
|
+
}
|
|
360
|
+
export function exportFleetLocalMachineCredential(agentUid, deps = {}) {
|
|
361
|
+
const run = deps.run ?? execFileSync;
|
|
362
|
+
const files = fleetLocalExecutables(agentUid, deps);
|
|
363
|
+
const record = runJson(run, files.helper, ["machine-credential", "export", "--profile", files.profileDir, "--bot-id", agentUid], { stdio: ["ignore", "pipe", "pipe"] }, "machine credential export");
|
|
364
|
+
const text = (key) => (typeof record[key] === "string" ? record[key].trim() : "");
|
|
365
|
+
const credential = {
|
|
366
|
+
username: text("username"),
|
|
367
|
+
secret: typeof record.secret === "string" ? record.secret : "",
|
|
368
|
+
entityType: "agent",
|
|
369
|
+
entityUid: text("entityUid"),
|
|
370
|
+
clientId: text("clientId"),
|
|
371
|
+
region: text("region"),
|
|
372
|
+
};
|
|
373
|
+
const entityType = record.entityType ?? "agent";
|
|
374
|
+
if (entityType !== "agent" ||
|
|
375
|
+
credential.entityUid !== agentUid ||
|
|
376
|
+
!credential.username ||
|
|
377
|
+
!credential.secret ||
|
|
378
|
+
!credential.clientId ||
|
|
379
|
+
!credential.region) {
|
|
380
|
+
credential.secret = "";
|
|
381
|
+
throw expectedError(`HQ Fleet Local machine credential does not belong to ${agentUid}.`);
|
|
382
|
+
}
|
|
383
|
+
return credential;
|
|
384
|
+
}
|
|
385
|
+
export function fleetLocalPromotionRuntime(agentUid, deps = {}) {
|
|
386
|
+
return {
|
|
387
|
+
stop: () => {
|
|
388
|
+
controlFleetLocalBot(agentUid, "stop", deps);
|
|
389
|
+
},
|
|
390
|
+
machineCredential: () => exportFleetLocalMachineCredential(agentUid, deps),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
export function removeFleetLocalBot(agentUid, deps = {}) {
|
|
394
|
+
const run = deps.run ?? execFileSync;
|
|
395
|
+
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid)) {
|
|
396
|
+
throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
|
|
397
|
+
}
|
|
398
|
+
const profileDir = profileRoot(agentUid, deps.fleetHome ?? fleetHome());
|
|
399
|
+
if (!fs.existsSync(profileDir))
|
|
400
|
+
return { removed: false, credentialsRemoved: 0 };
|
|
401
|
+
const platform = deps.platform ?? process.platform;
|
|
402
|
+
const runtimeDir = path.join(profileDir, "runtime");
|
|
403
|
+
const partial = ![
|
|
404
|
+
path.join(profileDir, "profile.json"),
|
|
405
|
+
venvExecutable(runtimeDir, "hq-fleet", platform),
|
|
406
|
+
venvExecutable(runtimeDir, "hq-fleet-local", platform),
|
|
407
|
+
].every((item) => fs.existsSync(item));
|
|
408
|
+
if (partial) {
|
|
409
|
+
fs.rmSync(profileDir, { recursive: true, force: true });
|
|
410
|
+
return { removed: true, credentialsRemoved: 0 };
|
|
411
|
+
}
|
|
412
|
+
const files = fleetLocalExecutables(agentUid, deps);
|
|
413
|
+
let profile;
|
|
414
|
+
try {
|
|
415
|
+
profile = JSON.parse(fs.readFileSync(files.profile, "utf8"));
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
throw expectedError(`HQ Fleet Local profile is unreadable for ${agentUid}.`);
|
|
419
|
+
}
|
|
420
|
+
const providers = Array.isArray(profile.credentialProviders)
|
|
421
|
+
? profile.credentialProviders.filter((value) => typeof value === "string")
|
|
422
|
+
: [];
|
|
423
|
+
runJson(run, files.helper, ["service", "remove", "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, "service removal");
|
|
424
|
+
for (const provider of providers) {
|
|
425
|
+
runJson(run, files.helper, ["auth", "logout", "--profile", files.profileDir, "--bot-id", agentUid, "--provider", provider], { stdio: ["ignore", "pipe", "pipe"] }, `${provider} credential removal`);
|
|
426
|
+
}
|
|
427
|
+
runJson(run, files.helper, ["machine-credential", "delete", "--profile", files.profileDir, "--bot-id", agentUid], { stdio: ["ignore", "pipe", "pipe"] }, "machine credential removal");
|
|
428
|
+
fs.rmSync(files.profileDir, { recursive: true, force: true });
|
|
429
|
+
return { removed: true, credentialsRemoved: providers.length + 1 };
|
|
430
|
+
}
|
|
431
|
+
export function authenticateFleetLocalBot(input, deps = {}) {
|
|
432
|
+
const requested = input.provider.trim().toLowerCase();
|
|
433
|
+
if (!/^[a-z0-9][a-z0-9._:-]*$/.test(requested)) {
|
|
434
|
+
throw expectedError("HQ Fleet provider identifier is invalid.");
|
|
435
|
+
}
|
|
436
|
+
const known = providerProfiles[requested];
|
|
437
|
+
const provider = known ?? {
|
|
438
|
+
provider: `custom:personal-${requested}`,
|
|
439
|
+
runtimeProvider: requested,
|
|
440
|
+
model: input.model?.trim() ?? "",
|
|
441
|
+
baseUrl: input.baseUrl?.trim() ?? "",
|
|
442
|
+
transport: input.transport ?? "chat_completions",
|
|
443
|
+
oauth: false,
|
|
444
|
+
};
|
|
445
|
+
const model = input.model?.trim() || provider.model;
|
|
446
|
+
const baseUrl = input.baseUrl?.trim() || provider.baseUrl;
|
|
447
|
+
const transport = input.transport ?? provider.transport;
|
|
448
|
+
if (!model)
|
|
449
|
+
throw expectedError(`Pass --model for runtime provider ${requested}.`);
|
|
450
|
+
if (!baseUrl)
|
|
451
|
+
throw expectedError(`Pass --base-url for runtime provider ${requested}.`);
|
|
452
|
+
if (!provider.oauth && !input.apiKey) {
|
|
453
|
+
throw expectedError(`Pass --api-key and provide ${provider.runtimeProvider} credentials on stdin.`);
|
|
454
|
+
}
|
|
455
|
+
const run = deps.run ?? execFileSync;
|
|
456
|
+
const files = fleetLocalExecutables(input.agentUid, deps);
|
|
457
|
+
const authArgs = provider.oauth && !input.apiKey
|
|
458
|
+
? [
|
|
459
|
+
"auth",
|
|
460
|
+
"login",
|
|
461
|
+
"--profile",
|
|
462
|
+
files.profileDir,
|
|
463
|
+
"--bot-id",
|
|
464
|
+
input.agentUid,
|
|
465
|
+
"--provider",
|
|
466
|
+
provider.runtimeProvider,
|
|
467
|
+
...(input.openBrowser === false ? ["--no-browser"] : []),
|
|
468
|
+
]
|
|
469
|
+
: [
|
|
470
|
+
"auth",
|
|
471
|
+
"api-key",
|
|
472
|
+
"--profile",
|
|
473
|
+
files.profileDir,
|
|
474
|
+
"--bot-id",
|
|
475
|
+
input.agentUid,
|
|
476
|
+
"--provider",
|
|
477
|
+
provider.runtimeProvider,
|
|
478
|
+
"--base-url",
|
|
479
|
+
baseUrl,
|
|
480
|
+
];
|
|
481
|
+
const auth = runJson(run, files.helper, authArgs, input.apiKey
|
|
482
|
+
? { input: input.apiKey, stdio: ["pipe", "pipe", "pipe"] }
|
|
483
|
+
: { stdio: ["inherit", "pipe", "inherit"] }, `${provider.runtimeProvider} authentication`);
|
|
484
|
+
if (auth.authenticated !== true) {
|
|
485
|
+
throw expectedError(`HQ Fleet Local could not authenticate ${provider.runtimeProvider}.`);
|
|
486
|
+
}
|
|
487
|
+
replaceProfileProvider(files.profile, input.agentUid, {
|
|
488
|
+
id: provider.provider,
|
|
489
|
+
model,
|
|
490
|
+
credentialProvider: provider.runtimeProvider,
|
|
491
|
+
baseUrl,
|
|
492
|
+
transport,
|
|
493
|
+
});
|
|
494
|
+
runJson(run, files.helper, ["render-profile", "--spec", files.profile, "--mode", "update"], { stdio: ["ignore", "pipe", "pipe"] }, "profile update");
|
|
495
|
+
runJson(run, files.helper, ["doctor-profile", "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, "profile validation");
|
|
496
|
+
const restarted = controlFleetLocalBot(input.agentUid, "restart", deps).ok === true;
|
|
497
|
+
return {
|
|
498
|
+
provider: requested,
|
|
499
|
+
runtimeProvider: provider.runtimeProvider,
|
|
500
|
+
model,
|
|
501
|
+
authenticated: true,
|
|
502
|
+
restarted,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
export function writeFleetLocalProfile(input, deps = {}) {
|
|
506
|
+
if (!/^agt_[A-Za-z0-9_-]+$/.test(input.agentUid)) {
|
|
507
|
+
throw expectedError(`Cannot create an HQ Fleet profile for invalid bot id ${input.agentUid}.`);
|
|
508
|
+
}
|
|
509
|
+
if (!/^prs_[A-Za-z0-9_-]+$/.test(input.ownerUid)) {
|
|
510
|
+
throw expectedError(`Cannot create an HQ Fleet profile for invalid owner id ${input.ownerUid}.`);
|
|
511
|
+
}
|
|
512
|
+
const root = deps.fleetHome ?? fleetHome();
|
|
513
|
+
const dir = profileRoot(input.agentUid, root);
|
|
514
|
+
const runtimeDir = path.join(dir, "runtime");
|
|
515
|
+
const destination = path.join(dir, "profile.json");
|
|
516
|
+
const temporary = path.join(dir, `.profile-${process.pid}-${crypto.randomUUID()}`);
|
|
517
|
+
const profile = {
|
|
518
|
+
schemaVersion: 1,
|
|
519
|
+
botId: input.agentUid,
|
|
520
|
+
ownerUid: input.ownerUid,
|
|
521
|
+
provider: { id: "custom:personal-chatgpt", model: "gpt-5.6-sol" },
|
|
522
|
+
hqRoot: path.resolve(input.hqRoot),
|
|
523
|
+
profileDir: path.resolve(dir),
|
|
524
|
+
runtime: { commit: input.installation.release.runtimeCommit, path: path.resolve(runtimeDir) },
|
|
525
|
+
paths: {
|
|
526
|
+
pluginSource: path.resolve(path.join(dir, "sources", "distribution", "src", "hq_fleet_local", "plugins", "hqdm")),
|
|
527
|
+
statusFile: path.resolve(path.join(input.botDir, "status.json")),
|
|
528
|
+
logsDir: path.resolve(path.join(input.botDir, "logs")),
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
532
|
+
fs.mkdirSync(profile.paths.logsDir, { recursive: true, mode: 0o700 });
|
|
533
|
+
fs.writeFileSync(temporary, `${JSON.stringify(profile, null, 2)}\n`, { mode: 0o600 });
|
|
534
|
+
fs.renameSync(temporary, destination);
|
|
535
|
+
if (input.systemPrompt !== undefined)
|
|
536
|
+
writeFleetLocalSoul(input.agentUid, input.systemPrompt, { fleetHome: root });
|
|
537
|
+
return destination;
|
|
538
|
+
}
|
|
539
|
+
export function writeFleetLocalSoul(agentUid, systemPrompt, deps = {}) {
|
|
540
|
+
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid)) {
|
|
541
|
+
throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
|
|
542
|
+
}
|
|
543
|
+
const dir = profileRoot(agentUid, deps.fleetHome ?? fleetHome());
|
|
544
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
545
|
+
const soul = path.join(dir, "SOUL.md");
|
|
546
|
+
const temporary = path.join(dir, `.soul-${process.pid}-${crypto.randomUUID()}`);
|
|
547
|
+
fs.writeFileSync(temporary, `${systemPrompt.trimEnd()}\n`, { mode: 0o600 });
|
|
548
|
+
fs.renameSync(temporary, soul);
|
|
549
|
+
return soul;
|
|
550
|
+
}
|
|
551
|
+
const FLEET_INSTRUCTION_SCAN = [
|
|
552
|
+
"import json, sys",
|
|
553
|
+
"sys.path.insert(0, sys.argv[1])",
|
|
554
|
+
"from tools.threat_patterns import scan_for_threats",
|
|
555
|
+
"print(json.dumps(scan_for_threats(sys.stdin.read(), scope='context')))",
|
|
556
|
+
].join("\n");
|
|
557
|
+
/**
|
|
558
|
+
* The pinned runtime refuses a persona file that trips its context-file
|
|
559
|
+
* scanner and then starts with no identity at all. The same scanner, run with
|
|
560
|
+
* the bot's own interpreter, says beforehand what it would refuse.
|
|
561
|
+
*/
|
|
562
|
+
export function scanFleetLocalInstructions(agentUid, text, deps = {}) {
|
|
563
|
+
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid)) {
|
|
564
|
+
throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
|
|
565
|
+
}
|
|
566
|
+
const run = deps.run ?? execFileSync;
|
|
567
|
+
const profileDir = profileRoot(agentUid, deps.fleetHome ?? fleetHome());
|
|
568
|
+
const python = venvExecutable(path.join(profileDir, "runtime"), "python", deps.platform ?? process.platform);
|
|
569
|
+
const source = path.join(profileDir, "sources", "runtime");
|
|
570
|
+
if (!fs.existsSync(python) || !fs.existsSync(source)) {
|
|
571
|
+
throw expectedError("HQ Fleet Local is not installed for this bot, so its instructions could not be checked.");
|
|
572
|
+
}
|
|
573
|
+
const failure = expectedError("HQ Fleet Local could not check the bot's instructions with its runtime.");
|
|
574
|
+
let parsed;
|
|
575
|
+
try {
|
|
576
|
+
const output = run(python, ["-I", "-c", FLEET_INSTRUCTION_SCAN, source], {
|
|
577
|
+
input: text,
|
|
578
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
579
|
+
encoding: "utf8",
|
|
580
|
+
windowsHide: true,
|
|
581
|
+
});
|
|
582
|
+
parsed = JSON.parse((Buffer.isBuffer(output) ? output.toString("utf8") : output).trim());
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
throw failure;
|
|
586
|
+
}
|
|
587
|
+
if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === "string"))
|
|
588
|
+
throw failure;
|
|
589
|
+
return parsed;
|
|
590
|
+
}
|
|
591
|
+
/** Checks the instructions with the runtime's scanner, then makes them the bot's persona. */
|
|
592
|
+
export function refreshFleetLocalInstructions(agentUid, systemPrompt, deps = {}) {
|
|
593
|
+
const findings = scanFleetLocalInstructions(agentUid, systemPrompt, deps);
|
|
594
|
+
if (findings.length > 0) {
|
|
595
|
+
throw expectedError(`The bot's instructions contain wording the HQ Fleet Runtime refuses to load (${findings.join(", ")}). Reword the bot's persona.md, worker.yaml or memory notes and try again.`);
|
|
596
|
+
}
|
|
597
|
+
return writeFleetLocalSoul(agentUid, systemPrompt, deps);
|
|
598
|
+
}
|
|
599
|
+
function checkedRun(run, executable, args, options, label) {
|
|
600
|
+
try {
|
|
601
|
+
run(executable, args, { windowsHide: true, ...options });
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
throw expectedError(`HQ Fleet Local ${label} failed.`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
608
|
+
const run = deps.run ?? execFileSync;
|
|
609
|
+
const platform = deps.platform ?? process.platform;
|
|
610
|
+
const root = deps.fleetHome ?? fleetHome();
|
|
611
|
+
const python = deps.python ?? await ensureFleetPython({ fleetHome: root, platform, run });
|
|
612
|
+
const profileDir = profileRoot(input.agentUid, root);
|
|
613
|
+
const sources = path.join(profileDir, "sources");
|
|
614
|
+
const distributionSource = path.join(sources, "distribution");
|
|
615
|
+
const runtimeSource = path.join(sources, "runtime");
|
|
616
|
+
const runtimeDir = path.join(profileDir, "runtime");
|
|
617
|
+
const marker = path.join(profileDir, "bootstrap.json");
|
|
618
|
+
fs.mkdirSync(profileDir, { recursive: true, mode: 0o700 });
|
|
619
|
+
if (!fs.existsSync(marker)) {
|
|
620
|
+
fs.rmSync(sources, { recursive: true, force: true });
|
|
621
|
+
fs.rmSync(runtimeDir, { recursive: true, force: true });
|
|
622
|
+
fs.mkdirSync(distributionSource, { recursive: true, mode: 0o700 });
|
|
623
|
+
fs.mkdirSync(runtimeSource, { recursive: true, mode: 0o700 });
|
|
624
|
+
const uvEnv = fleetUvEnv(python.pythonDir);
|
|
625
|
+
checkedRun(run, python.python, [
|
|
626
|
+
"-m",
|
|
627
|
+
"zipfile",
|
|
628
|
+
"-e",
|
|
629
|
+
input.installation.archive,
|
|
630
|
+
distributionSource,
|
|
631
|
+
], { stdio: "ignore" }, "distribution extraction");
|
|
632
|
+
checkedRun(run, python.python, [
|
|
633
|
+
"-m",
|
|
634
|
+
"tarfile",
|
|
635
|
+
"-e",
|
|
636
|
+
input.installation.runtimeArchive,
|
|
637
|
+
runtimeSource,
|
|
638
|
+
], { stdio: "ignore" }, "runtime extraction");
|
|
639
|
+
checkedRun(run, python.uv, [
|
|
640
|
+
"venv",
|
|
641
|
+
"--no-config",
|
|
642
|
+
"--python",
|
|
643
|
+
python.python,
|
|
644
|
+
runtimeDir,
|
|
645
|
+
], { stdio: "ignore", env: uvEnv }, "environment creation");
|
|
646
|
+
const runtimePython = venvExecutable(runtimeDir, "python", platform);
|
|
647
|
+
checkedRun(run, python.uv, [
|
|
648
|
+
"pip",
|
|
649
|
+
"install",
|
|
650
|
+
"--no-config",
|
|
651
|
+
"--python",
|
|
652
|
+
runtimePython,
|
|
653
|
+
"--editable",
|
|
654
|
+
`${runtimeSource}[all,messaging,slack,anthropic]`,
|
|
655
|
+
distributionSource,
|
|
656
|
+
], { stdio: ["ignore", 2, 2], env: uvEnv }, "package installation");
|
|
657
|
+
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: input.installation.engine }, null, 2)}\n`, { mode: 0o600 });
|
|
658
|
+
}
|
|
659
|
+
const { systemPrompt, ...profileInput } = input;
|
|
660
|
+
if (systemPrompt !== undefined)
|
|
661
|
+
refreshFleetLocalInstructions(input.agentUid, systemPrompt, { fleetHome: root, platform, run });
|
|
662
|
+
const profile = writeFleetLocalProfile(profileInput, { fleetHome: root, platform });
|
|
663
|
+
const helper = venvExecutable(runtimeDir, "hq-fleet-local", platform);
|
|
664
|
+
const runtime = venvExecutable(runtimeDir, "hq-fleet", platform);
|
|
665
|
+
if (!fs.existsSync(helper) || !fs.existsSync(runtime)) {
|
|
666
|
+
throw expectedError("HQ Fleet Local installed without its runtime executables.");
|
|
667
|
+
}
|
|
668
|
+
checkedRun(run, helper, ["render-profile", "--spec", profile, "--mode", "create"], { stdio: "ignore" }, "profile rendering");
|
|
669
|
+
const machineValue = fs.readFileSync(input.machineCredentials, "utf8").trim();
|
|
670
|
+
if (!machineValue) {
|
|
671
|
+
throw expectedError(`HQ Fleet Local machine credential file is empty: ${input.machineCredentials}`);
|
|
672
|
+
}
|
|
673
|
+
checkedRun(run, helper, [
|
|
674
|
+
"machine-credential",
|
|
675
|
+
"import",
|
|
676
|
+
"--profile",
|
|
677
|
+
profileDir,
|
|
678
|
+
"--bot-id",
|
|
679
|
+
input.agentUid,
|
|
680
|
+
], { input: machineValue, stdio: ["pipe", "ignore", "pipe"] }, "machine credential import");
|
|
681
|
+
fs.rmSync(input.machineCredentials, { force: true });
|
|
682
|
+
checkedRun(run, helper, ["doctor-profile", "--profile", profileDir, "--runtime", runtime], { stdio: "ignore" }, "profile validation");
|
|
683
|
+
const start = deps.start !== false;
|
|
684
|
+
if (start) {
|
|
685
|
+
checkedRun(run, helper, ["service", "install", "--profile", profileDir, "--runtime", runtime], { stdio: ["ignore", 2, 2] }, "service installation");
|
|
686
|
+
}
|
|
687
|
+
return { profile, runtime, helper, running: start };
|
|
688
|
+
}
|
|
689
|
+
//# sourceMappingURL=fleet-local.js.map
|