@indigoai-us/hq-cli 5.345.179 → 5.345.181
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 +23 -0
- package/dist/command-catalog.generated.d.ts +126 -0
- package/dist/command-catalog.generated.js +155 -0
- package/dist/commands/bot.d.ts +29 -1
- package/dist/commands/bot.js +282 -22
- package/dist/commands/channels.d.ts +18 -1
- package/dist/commands/channels.js +185 -1
- package/dist/commands/lanes.d.ts +1 -1
- package/dist/commands/lanes.js +6 -2
- package/dist/commands/mesh.js +19 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2285 -1743
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/config.d.ts +8 -0
- package/dist/lib/bot/fleet-local.d.ts +7 -3
- package/dist/lib/bot/fleet-local.js +234 -64
- package/dist/lib/bot/run.js +39 -2
- package/dist/lib/bot/runtime-auth.d.ts +119 -0
- package/dist/lib/bot/runtime-auth.js +251 -0
- package/dist/lib/daemon/catalog.d.ts +1 -0
- package/dist/lib/daemon/catalog.js +1 -1
- package/dist/lib/daemon/run.d.ts +2 -0
- package/dist/lib/daemon/run.js +64 -16
- 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/hot-channels/adapters/meetings.js +3 -1
- package/dist/lib/hot-channels/contract.d.ts +6 -0
- package/dist/lib/hot-channels/ledger.js +17 -0
- package/dist/lib/hot-channels/loop.js +2 -0
- package/dist/lib/lanes/accounts.d.ts +1 -17
- package/dist/lib/lanes/accounts.js +5 -45
- package/dist/lib/lanes/provider-home.d.ts +24 -0
- package/dist/lib/lanes/provider-home.js +52 -0
- package/dist/lib/lanes/spawn.d.ts +12 -0
- package/dist/lib/lanes/spawn.js +27 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/doctor.js +13 -0
- package/dist/lib/mesh/live/daemon/owner-inbox-push.d.ts +94 -0
- package/dist/lib/mesh/live/daemon/owner-inbox-push.js +273 -0
- package/dist/lib/mesh/live/daemon/run.js +31 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +14 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.js +210 -3
- package/dist/lib/mesh/live/paths.d.ts +2 -0
- package/dist/lib/mesh/live/paths.js +2 -0
- package/dist/lib/sync-core/flag.d.ts +1 -1
- package/dist/lib/sync-core/flag.js +1 -1
- package/dist/lib/sync-core/reconcile.d.ts +1 -1
- package/dist/lib/sync-core/reconcile.js +9 -7
- package/dist/lib/sync-core/release.d.ts +3 -3
- package/dist/lib/sync-core/release.js +4 -4
- package/dist/lib/sync-core/rollout-state.d.ts +1 -0
- package/dist/lib/sync-core/rollout-state.js +1 -0
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.
|
|
1
|
+
{"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.105","bundleSha256":"f57aa12671cdfcf08ce39f11ce6885d6e52d5478f2ae77a75d38d79162194ce5","formatVersion":1}
|
package/dist/lib/bot/config.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* bot.json — non-secret per-bot configuration (local-bots US-003/US-005).
|
|
3
3
|
*/
|
|
4
|
+
import type { BotRuntimeAuthMap } from "./runtime-auth.js";
|
|
4
5
|
export declare const BOT_RUNTIMES: readonly ["claude", "codex", "grok"];
|
|
5
6
|
export type BotRuntimeId = (typeof BOT_RUNTIMES)[number];
|
|
6
7
|
export declare const HQ_FLEET_RUNTIME_ENGINE_ID: "hq-fleet-runtime";
|
|
@@ -167,6 +168,13 @@ export interface BotConfig {
|
|
|
167
168
|
* setup bots, which only need files and shell commands.
|
|
168
169
|
*/
|
|
169
170
|
lean?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Which model login each runtime uses (see runtime-auth.ts): unset = the
|
|
173
|
+
* owner's default sign-in; { mode: "own" } = the bot's own login under
|
|
174
|
+
* ~/.hq/bots/<name>/auth/<runtime>; { mode: "account", account } = a
|
|
175
|
+
* capacity account home. Names only, never a credential.
|
|
176
|
+
*/
|
|
177
|
+
runtimeAuth?: BotRuntimeAuthMap;
|
|
170
178
|
}
|
|
171
179
|
export declare function readBotConfig(dir: string): BotConfig | null;
|
|
172
180
|
export declare function writeBotConfig(dir: string, config: BotConfig): void;
|
|
@@ -99,6 +99,9 @@ export interface FleetLocalBootstrapDeps {
|
|
|
99
99
|
start?: boolean;
|
|
100
100
|
python?: FleetPython;
|
|
101
101
|
}
|
|
102
|
+
type FleetLocalProfileDeps = Pick<FleetLocalBootstrapDeps, "fleetHome" | "platform"> & {
|
|
103
|
+
runtimeRoot?: string;
|
|
104
|
+
};
|
|
102
105
|
export interface FleetLocalBootstrapResult {
|
|
103
106
|
profile: string;
|
|
104
107
|
runtime: string;
|
|
@@ -133,7 +136,7 @@ export interface FleetLocalRemovalResult {
|
|
|
133
136
|
credentialsRemoved: number;
|
|
134
137
|
}
|
|
135
138
|
export declare const HQ_FLEET_LOCAL_RELEASE: FleetLocalReleasePin;
|
|
136
|
-
export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\
|
|
139
|
+
export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEARXr2I8RNcw2c0Ep77XkYGwylresXBFn8dm9pxN2Zq/U=\n-----END PUBLIC KEY-----\n";
|
|
137
140
|
export declare function fleetHome(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
138
141
|
export declare function verifyFleetLocalRelease(files: FleetLocalReleaseFiles, release?: FleetLocalReleasePin, publicKey?: string): void;
|
|
139
142
|
export declare function resolveFleetReleaseToken(env?: NodeJS.ProcessEnv): string;
|
|
@@ -144,12 +147,13 @@ export declare const FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS: number;
|
|
|
144
147
|
export declare function installPinnedFleetLocalDistribution(deps?: FleetLocalInstallDeps): Promise<FleetLocalInstallation>;
|
|
145
148
|
/** The process id the bot's gateway recorded, when its file reads; the caller checks whether it is alive. */
|
|
146
149
|
export declare function fleetLocalGatewayPid(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome">): number | null;
|
|
150
|
+
export declare function fleetLocalProfileNeedsAdoption(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome" | "platform">): boolean;
|
|
147
151
|
export declare function controlFleetLocalBot(agentUid: string, action: FleetLocalServiceAction, deps?: FleetLocalControlDeps): Record<string, unknown>;
|
|
148
152
|
export declare function exportFleetLocalMachineCredential(agentUid: string, deps?: FleetLocalControlDeps): BotMachineCreds;
|
|
149
153
|
export declare function fleetLocalPromotionRuntime(agentUid: string, deps?: FleetLocalControlDeps): LocalBotPromotionRuntime;
|
|
150
154
|
export declare function removeFleetLocalBot(agentUid: string, deps?: FleetLocalControlDeps): FleetLocalRemovalResult;
|
|
151
155
|
export declare function authenticateFleetLocalBot(input: FleetLocalAuthInput, deps?: FleetLocalControlDeps): FleetLocalAuthResult;
|
|
152
|
-
export declare function writeFleetLocalProfile(input: FleetLocalProfileInput, deps?:
|
|
156
|
+
export declare function writeFleetLocalProfile(input: FleetLocalProfileInput, deps?: FleetLocalProfileDeps): string;
|
|
153
157
|
/** The runtime's inbox plugin sends the intro and runs the kickoff once, then stamps this file. */
|
|
154
158
|
export declare function writeFleetLocalFirstStart(profileDir: string, firstStart: {
|
|
155
159
|
intro?: string;
|
|
@@ -167,7 +171,7 @@ export declare function refreshFleetLocalInstructions(agentUid: string, systemPr
|
|
|
167
171
|
/** Moves a fresh machine credential into the bot's secure store and deletes the file. */
|
|
168
172
|
export declare function importFleetLocalMachineCredential(agentUid: string, machineCredentials: string, deps?: FleetLocalControlDeps): void;
|
|
169
173
|
export declare function bootstrapFleetLocalBot(input: FleetLocalProfileInput & {
|
|
170
|
-
machineCredentials
|
|
174
|
+
machineCredentials?: string;
|
|
171
175
|
}, deps?: FleetLocalBootstrapDeps): Promise<FleetLocalBootstrapResult>;
|
|
172
176
|
export {};
|
|
173
177
|
//# sourceMappingURL=fleet-local.d.ts.map
|
|
@@ -4,6 +4,7 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { HQ_FLEET_RUNTIME_ENGINE_ID, } from "./config.js";
|
|
7
|
+
import { withBotLock } from "./lock.js";
|
|
7
8
|
import { ensureFleetPython, fleetUvEnv } from "./fleet-python.js";
|
|
8
9
|
import { ensureCognitoToken } from "../../utils/cognito-session.js";
|
|
9
10
|
import { vaultApiFetch } from "../../utils/vault-api.js";
|
|
@@ -35,18 +36,18 @@ export function fleetProviderNeedsSignIn(provider) {
|
|
|
35
36
|
}
|
|
36
37
|
export const HQ_FLEET_LOCAL_RELEASE = {
|
|
37
38
|
repository: "indigoai-us/hq-fleet-local",
|
|
38
|
-
tag: "v0.4.
|
|
39
|
-
version: "0.4.
|
|
40
|
-
artifact: "hq-fleet-local-0.4.
|
|
41
|
-
artifactSha256: "
|
|
39
|
+
tag: "v0.4.14",
|
|
40
|
+
version: "0.4.14",
|
|
41
|
+
artifact: "hq-fleet-local-0.4.14.zip",
|
|
42
|
+
artifactSha256: "9ff6ad4f7235988468e1913008c5650f5d064210078d6d20c407b17120310f47",
|
|
42
43
|
runtimeArtifact: "hq-fleet-runtime-abccac13bd9e.tar",
|
|
43
44
|
runtimeArtifactSha256: "29bcfe718ad7fbd3636afd00623772f2a25bc84c4f6f10d8d57f38e549d528d5",
|
|
44
|
-
manifestSha256: "
|
|
45
|
-
signatureSha256: "
|
|
45
|
+
manifestSha256: "88e71a733a5f57089764cbf9947fb88025c2263c5f3d62a2c6f5ab3635538871",
|
|
46
|
+
signatureSha256: "b892b54d6fcb2aaca86d049d7662fe5549a250d81877d6c529288e79ae1565d9",
|
|
46
47
|
runtimeCommit: "abccac13bd9ea593b24c8cd9c0ba28d5c6050cc9",
|
|
47
48
|
};
|
|
48
49
|
export const HQ_FLEET_LOCAL_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
49
|
-
|
|
50
|
+
MCowBQYDK2VwAyEARXr2I8RNcw2c0Ep77XkYGwylresXBFn8dm9pxN2Zq/U=
|
|
50
51
|
-----END PUBLIC KEY-----
|
|
51
52
|
`;
|
|
52
53
|
function digest(value) {
|
|
@@ -162,7 +163,7 @@ function trustedReleaseLink(url) {
|
|
|
162
163
|
return false;
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
|
-
async function downloadFleetLocalReleaseFromHq(release, fetcher, links) {
|
|
166
|
+
async function downloadFleetLocalReleaseFromHq(release, fetcher, links, cachedRuntime) {
|
|
166
167
|
const get = async (name, timeoutMs) => {
|
|
167
168
|
const url = links.get(name);
|
|
168
169
|
if (!url)
|
|
@@ -178,7 +179,7 @@ async function downloadFleetLocalReleaseFromHq(release, fetcher, links) {
|
|
|
178
179
|
};
|
|
179
180
|
const [archive, runtimeArchive, manifest, signature] = await Promise.all([
|
|
180
181
|
get(release.artifact, FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS),
|
|
181
|
-
get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
|
|
182
|
+
cachedRuntime ?? get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
|
|
182
183
|
get("release-manifest.json", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
183
184
|
get("release-manifest.sig", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
184
185
|
]);
|
|
@@ -193,7 +194,7 @@ async function responseBytes(response, label) {
|
|
|
193
194
|
export const FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
194
195
|
export const FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS = 5 * 60_000;
|
|
195
196
|
export const FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS = 20 * 60_000;
|
|
196
|
-
async function downloadFleetLocalRelease(release, fetcher, token) {
|
|
197
|
+
async function downloadFleetLocalRelease(release, fetcher, token, cachedRuntime) {
|
|
197
198
|
const headers = {
|
|
198
199
|
Accept: "application/vnd.github+json",
|
|
199
200
|
Authorization: `Bearer ${token}`,
|
|
@@ -223,7 +224,7 @@ async function downloadFleetLocalRelease(release, fetcher, token) {
|
|
|
223
224
|
};
|
|
224
225
|
const [archive, runtimeArchive, manifest, signature] = await Promise.all([
|
|
225
226
|
get(release.artifact, FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS),
|
|
226
|
-
get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
|
|
227
|
+
cachedRuntime ?? get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
|
|
227
228
|
get("release-manifest.json", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
228
229
|
get("release-manifest.sig", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
229
230
|
]);
|
|
@@ -274,6 +275,24 @@ function installFiles(target, files, release, publicKey) {
|
|
|
274
275
|
fs.rmSync(staging, { recursive: true, force: true });
|
|
275
276
|
}
|
|
276
277
|
}
|
|
278
|
+
function cachedRuntimeArchive(root, release) {
|
|
279
|
+
const distributions = path.join(root, "distributions", "hq-fleet-local");
|
|
280
|
+
if (!fs.existsSync(distributions))
|
|
281
|
+
return null;
|
|
282
|
+
for (const entry of fs.readdirSync(distributions, { withFileTypes: true })) {
|
|
283
|
+
if (!entry.isDirectory() || entry.name.startsWith("."))
|
|
284
|
+
continue;
|
|
285
|
+
const directory = path.join(distributions, entry.name);
|
|
286
|
+
for (const file of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
287
|
+
if (!file.isFile() || !file.name.startsWith("hq-fleet-runtime-") || !file.name.endsWith(".tar"))
|
|
288
|
+
continue;
|
|
289
|
+
const bytes = fs.readFileSync(path.join(directory, file.name));
|
|
290
|
+
if (digest(bytes) === release.runtimeArtifactSha256)
|
|
291
|
+
return bytes;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
277
296
|
export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
278
297
|
const release = deps.release ?? HQ_FLEET_LOCAL_RELEASE;
|
|
279
298
|
const publicKey = deps.publicKey ?? HQ_FLEET_LOCAL_PUBLIC_KEY;
|
|
@@ -286,9 +305,10 @@ export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
|
286
305
|
else {
|
|
287
306
|
const fetcher = deps.fetch ?? fetch;
|
|
288
307
|
const links = await (deps.hqReleaseLinks ?? fetchHqFleetReleaseLinks)(release);
|
|
308
|
+
const cachedRuntime = cachedRuntimeArchive(root, release);
|
|
289
309
|
const files = links
|
|
290
|
-
? await downloadFleetLocalReleaseFromHq(release, fetcher, links)
|
|
291
|
-
: await downloadFleetLocalRelease(release, fetcher, (deps.token ?? resolveFleetReleaseToken)());
|
|
310
|
+
? await downloadFleetLocalReleaseFromHq(release, fetcher, links, cachedRuntime)
|
|
311
|
+
: await downloadFleetLocalRelease(release, fetcher, (deps.token ?? resolveFleetReleaseToken)(), cachedRuntime);
|
|
292
312
|
verifyFleetLocalRelease(files, release, publicKey);
|
|
293
313
|
installFiles(dir, files, release, publicKey);
|
|
294
314
|
}
|
|
@@ -302,14 +322,83 @@ export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
|
302
322
|
engine: engineMetadata(release),
|
|
303
323
|
};
|
|
304
324
|
}
|
|
325
|
+
function profileName(agentUid) {
|
|
326
|
+
return agentUid.toLowerCase();
|
|
327
|
+
}
|
|
305
328
|
function profileRoot(agentUid, root) {
|
|
306
|
-
|
|
329
|
+
const profiles = path.join(root, "profiles");
|
|
330
|
+
const canonicalName = profileName(agentUid);
|
|
331
|
+
const canonical = path.join(profiles, canonicalName);
|
|
332
|
+
if (fs.existsSync(canonical))
|
|
333
|
+
return canonical;
|
|
334
|
+
try {
|
|
335
|
+
const legacy = fs.readdirSync(profiles, { withFileTypes: true })
|
|
336
|
+
.find((entry) => entry.isDirectory() && entry.name.toLowerCase() === canonicalName);
|
|
337
|
+
return legacy ? path.join(profiles, legacy.name) : canonical;
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
return canonical;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function adoptLegacyProfileDirectory(agentUid, root) {
|
|
344
|
+
const profiles = path.join(root, "profiles");
|
|
345
|
+
const canonicalName = profileName(agentUid);
|
|
346
|
+
fs.mkdirSync(profiles, { recursive: true, mode: 0o700 });
|
|
347
|
+
const matches = fs.readdirSync(profiles, { withFileTypes: true })
|
|
348
|
+
.filter((entry) => entry.isDirectory() && entry.name.toLowerCase() === canonicalName)
|
|
349
|
+
.map((entry) => entry.name);
|
|
350
|
+
if (matches.length > 1) {
|
|
351
|
+
throw expectedError(`HQ Fleet has conflicting profile directories for ${agentUid}.`);
|
|
352
|
+
}
|
|
353
|
+
const existing = matches[0];
|
|
354
|
+
const canonical = path.join(profiles, canonicalName);
|
|
355
|
+
if (!existing || existing === canonicalName)
|
|
356
|
+
return canonical;
|
|
357
|
+
const source = path.join(profiles, existing);
|
|
358
|
+
const temporary = path.join(profiles, `.adopt-${canonicalName}-${process.pid}-${crypto.randomUUID()}`);
|
|
359
|
+
fs.renameSync(source, temporary);
|
|
360
|
+
try {
|
|
361
|
+
fs.renameSync(temporary, canonical);
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
fs.renameSync(temporary, source);
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
return canonical;
|
|
307
368
|
}
|
|
308
369
|
function venvExecutable(runtimeDir, name, platform) {
|
|
309
370
|
return platform === "win32"
|
|
310
371
|
? path.join(runtimeDir, "Scripts", `${name}.exe`)
|
|
311
372
|
: path.join(runtimeDir, "bin", name);
|
|
312
373
|
}
|
|
374
|
+
function sharedRuntimeFromMarker(profileDir, root) {
|
|
375
|
+
try {
|
|
376
|
+
const marker = JSON.parse(fs.readFileSync(path.join(profileDir, "bootstrap.json"), "utf8"));
|
|
377
|
+
if (marker.schemaVersion !== 2 || typeof marker.runtimeId !== "string" || !/^[a-f0-9]{64}$/.test(marker.runtimeId))
|
|
378
|
+
return null;
|
|
379
|
+
const sharedRoot = path.join(root, "runtimes", marker.runtimeId);
|
|
380
|
+
return {
|
|
381
|
+
id: marker.runtimeId,
|
|
382
|
+
root: sharedRoot,
|
|
383
|
+
runtimeDir: path.join(sharedRoot, "runtime"),
|
|
384
|
+
sourcesDir: path.join(sharedRoot, "sources"),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function hasEmbeddedRuntime(profileDir, platform) {
|
|
392
|
+
const runtimeDir = path.join(profileDir, "runtime");
|
|
393
|
+
try {
|
|
394
|
+
if (fs.lstatSync(runtimeDir).isSymbolicLink())
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
return ["hq-fleet", "hq-fleet-local"].some((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)));
|
|
401
|
+
}
|
|
313
402
|
function fleetLocalExecutables(agentUid, deps) {
|
|
314
403
|
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid)) {
|
|
315
404
|
throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
|
|
@@ -317,14 +406,16 @@ function fleetLocalExecutables(agentUid, deps) {
|
|
|
317
406
|
const root = deps.fleetHome ?? fleetHome();
|
|
318
407
|
const platform = deps.platform ?? process.platform;
|
|
319
408
|
const profileDir = profileRoot(agentUid, root);
|
|
320
|
-
const
|
|
409
|
+
const shared = sharedRuntimeFromMarker(profileDir, root);
|
|
410
|
+
const runtimeDir = shared?.runtimeDir ?? path.join(profileDir, "runtime");
|
|
411
|
+
const sourcesDir = shared?.sourcesDir ?? path.join(profileDir, "sources");
|
|
321
412
|
const profile = path.join(profileDir, "profile.json");
|
|
322
413
|
const runtime = venvExecutable(runtimeDir, "hq-fleet", platform);
|
|
323
414
|
const helper = venvExecutable(runtimeDir, "hq-fleet-local", platform);
|
|
324
415
|
if (![profile, runtime, helper].every((item) => fs.existsSync(item))) {
|
|
325
416
|
throw expectedError(`HQ Fleet Local is not fully installed for ${agentUid}.`);
|
|
326
417
|
}
|
|
327
|
-
return { profileDir, profile, runtime, helper };
|
|
418
|
+
return { profileDir, profile, runtime, helper, runtimeDir, sourcesDir };
|
|
328
419
|
}
|
|
329
420
|
function runJson(run, executable, args, options, label) {
|
|
330
421
|
let output;
|
|
@@ -357,10 +448,39 @@ export function fleetLocalGatewayPid(agentUid, deps = {}) {
|
|
|
357
448
|
return null;
|
|
358
449
|
}
|
|
359
450
|
}
|
|
451
|
+
export function fleetLocalProfileNeedsAdoption(agentUid, deps = {}) {
|
|
452
|
+
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid))
|
|
453
|
+
return true;
|
|
454
|
+
const root = deps.fleetHome ?? fleetHome();
|
|
455
|
+
const profiles = path.join(root, "profiles");
|
|
456
|
+
const canonicalName = profileName(agentUid);
|
|
457
|
+
let entries;
|
|
458
|
+
try {
|
|
459
|
+
entries = fs.readdirSync(profiles);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
if (!entries.includes(canonicalName))
|
|
465
|
+
return true;
|
|
466
|
+
const profileDir = path.join(profiles, canonicalName);
|
|
467
|
+
const shared = sharedRuntimeFromMarker(profileDir, root);
|
|
468
|
+
if (!shared)
|
|
469
|
+
return true;
|
|
470
|
+
const platform = deps.platform ?? process.platform;
|
|
471
|
+
if (!["hq-fleet", "hq-fleet-local", "python"].every((name) => fs.existsSync(venvExecutable(shared.runtimeDir, name, platform))))
|
|
472
|
+
return true;
|
|
473
|
+
if (hasEmbeddedRuntime(profileDir, platform) || fs.existsSync(path.join(profileDir, "sources")))
|
|
474
|
+
return true;
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
360
477
|
export function controlFleetLocalBot(agentUid, action, deps = {}) {
|
|
361
478
|
const run = deps.run ?? execFileSync;
|
|
362
479
|
const files = fleetLocalExecutables(agentUid, deps);
|
|
363
|
-
|
|
480
|
+
const result = runJson(run, files.helper, ["service", action, "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, `service ${action}`);
|
|
481
|
+
if (action === "install" && result.ok === true)
|
|
482
|
+
removeEmbeddedRuntime(files.profileDir, deps.platform ?? process.platform);
|
|
483
|
+
return result;
|
|
364
484
|
}
|
|
365
485
|
const providerProfiles = {
|
|
366
486
|
chatgpt: {
|
|
@@ -491,14 +611,15 @@ export function removeFleetLocalBot(agentUid, deps = {}) {
|
|
|
491
611
|
const profileDir = profileRoot(agentUid, deps.fleetHome ?? fleetHome());
|
|
492
612
|
if (!fs.existsSync(profileDir))
|
|
493
613
|
return { removed: false, credentialsRemoved: 0 };
|
|
614
|
+
if (!fs.existsSync(path.join(profileDir, "profile.json"))) {
|
|
615
|
+
fs.rmSync(profileDir, { recursive: true, force: true });
|
|
616
|
+
return { removed: true, credentialsRemoved: 0 };
|
|
617
|
+
}
|
|
494
618
|
const platform = deps.platform ?? process.platform;
|
|
495
|
-
const
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
venvExecutable(runtimeDir, "hq-fleet-local", platform),
|
|
500
|
-
].every((item) => fs.existsSync(item));
|
|
501
|
-
if (partial) {
|
|
619
|
+
const legacyRuntime = path.join(profileDir, "runtime");
|
|
620
|
+
const shared = sharedRuntimeFromMarker(profileDir, deps.fleetHome ?? fleetHome());
|
|
621
|
+
const installedRuntime = shared?.runtimeDir ?? legacyRuntime;
|
|
622
|
+
if (!["hq-fleet", "hq-fleet-local"].every((name) => fs.existsSync(venvExecutable(installedRuntime, name, platform)))) {
|
|
502
623
|
fs.rmSync(profileDir, { recursive: true, force: true });
|
|
503
624
|
return { removed: true, credentialsRemoved: 0 };
|
|
504
625
|
}
|
|
@@ -614,7 +735,9 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
614
735
|
}
|
|
615
736
|
const root = deps.fleetHome ?? fleetHome();
|
|
616
737
|
const dir = profileRoot(input.agentUid, root);
|
|
617
|
-
const
|
|
738
|
+
const markerRuntime = sharedRuntimeFromMarker(dir, root);
|
|
739
|
+
const runtimeRoot = deps.runtimeRoot ?? markerRuntime?.root ?? dir;
|
|
740
|
+
const runtimeDir = path.join(runtimeRoot, "runtime");
|
|
618
741
|
const destination = path.join(dir, "profile.json");
|
|
619
742
|
const temporary = path.join(dir, `.profile-${process.pid}-${crypto.randomUUID()}`);
|
|
620
743
|
const profile = {
|
|
@@ -627,7 +750,7 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
627
750
|
profileDir: path.resolve(dir),
|
|
628
751
|
runtime: { commit: input.installation.release.runtimeCommit, path: path.resolve(runtimeDir) },
|
|
629
752
|
paths: {
|
|
630
|
-
pluginSource: path.resolve(path.join(
|
|
753
|
+
pluginSource: path.resolve(path.join(runtimeRoot, "sources", "distribution", "src", "hq_fleet_local", "plugins", "hqdm")),
|
|
631
754
|
statusFile: path.resolve(path.join(input.botDir, "status.json")),
|
|
632
755
|
logsDir: path.resolve(path.join(input.botDir, "logs")),
|
|
633
756
|
},
|
|
@@ -686,9 +809,13 @@ export function scanFleetLocalInstructions(agentUid, text, deps = {}) {
|
|
|
686
809
|
throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
|
|
687
810
|
}
|
|
688
811
|
const run = deps.run ?? execFileSync;
|
|
689
|
-
const
|
|
690
|
-
const
|
|
691
|
-
const
|
|
812
|
+
const root = deps.fleetHome ?? fleetHome();
|
|
813
|
+
const profileDir = profileRoot(agentUid, root);
|
|
814
|
+
const shared = sharedRuntimeFromMarker(profileDir, root);
|
|
815
|
+
const runtimeDir = shared?.runtimeDir ?? path.join(profileDir, "runtime");
|
|
816
|
+
const sourcesDir = shared?.sourcesDir ?? path.join(profileDir, "sources");
|
|
817
|
+
const python = venvExecutable(runtimeDir, "python", deps.platform ?? process.platform);
|
|
818
|
+
const source = path.join(sourcesDir, "runtime");
|
|
692
819
|
if (!fs.existsSync(python) || !fs.existsSync(source)) {
|
|
693
820
|
throw expectedError("HQ Fleet Local is not installed for this bot, so its instructions could not be checked.");
|
|
694
821
|
}
|
|
@@ -744,21 +871,36 @@ export function importFleetLocalMachineCredential(agentUid, machineCredentials,
|
|
|
744
871
|
], { input: machineValue, stdio: ["pipe", "ignore", "pipe"] }, "machine credential import");
|
|
745
872
|
fs.rmSync(machineCredentials, { force: true });
|
|
746
873
|
}
|
|
747
|
-
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
874
|
+
async function installSharedFleetRuntime(installation, root, python, platform, run) {
|
|
875
|
+
const identity = digest(Buffer.from(JSON.stringify({
|
|
876
|
+
version: installation.release.version,
|
|
877
|
+
distribution: installation.release.artifactSha256,
|
|
878
|
+
runtime: installation.release.runtimeArtifactSha256,
|
|
879
|
+
python: python.python,
|
|
880
|
+
platform,
|
|
881
|
+
extras: ["mcp", "anthropic"],
|
|
882
|
+
})));
|
|
883
|
+
const shared = path.join(root, "runtimes", identity);
|
|
884
|
+
const descriptor = () => ({
|
|
885
|
+
id: identity,
|
|
886
|
+
root: shared,
|
|
887
|
+
runtimeDir: path.join(shared, "runtime"),
|
|
888
|
+
sourcesDir: path.join(shared, "sources"),
|
|
889
|
+
});
|
|
890
|
+
return withBotLock(identity, async () => {
|
|
891
|
+
const marker = path.join(shared, "ready.json");
|
|
892
|
+
const sources = path.join(shared, "sources");
|
|
893
|
+
const distributionSource = path.join(sources, "distribution");
|
|
894
|
+
const runtimeSource = path.join(sources, "runtime");
|
|
895
|
+
const runtimeDir = path.join(shared, "runtime");
|
|
896
|
+
const executables = ["python", "hq-fleet", "hq-fleet-local"];
|
|
897
|
+
if (fs.existsSync(marker)) {
|
|
898
|
+
if (!executables.every((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)))) {
|
|
899
|
+
throw expectedError("The shared HQ Fleet Runtime is incomplete. Reinstall it before creating another bot.");
|
|
900
|
+
}
|
|
901
|
+
return descriptor();
|
|
902
|
+
}
|
|
903
|
+
fs.rmSync(shared, { recursive: true, force: true });
|
|
762
904
|
fs.mkdirSync(distributionSource, { recursive: true, mode: 0o700 });
|
|
763
905
|
fs.mkdirSync(runtimeSource, { recursive: true, mode: 0o700 });
|
|
764
906
|
const uvEnv = fleetUvEnv(python.pythonDir);
|
|
@@ -766,14 +908,14 @@ export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
|
766
908
|
"-m",
|
|
767
909
|
"zipfile",
|
|
768
910
|
"-e",
|
|
769
|
-
|
|
911
|
+
installation.archive,
|
|
770
912
|
distributionSource,
|
|
771
913
|
], { stdio: "ignore" }, "distribution extraction");
|
|
772
914
|
checkedRun(run, python.python, [
|
|
773
915
|
"-m",
|
|
774
916
|
"tarfile",
|
|
775
917
|
"-e",
|
|
776
|
-
|
|
918
|
+
installation.runtimeArchive,
|
|
777
919
|
runtimeSource,
|
|
778
920
|
], { stdio: "ignore" }, "runtime extraction");
|
|
779
921
|
checkedRun(run, python.uv, [
|
|
@@ -794,35 +936,63 @@ export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
|
794
936
|
`${runtimeSource}[mcp,anthropic]`,
|
|
795
937
|
distributionSource,
|
|
796
938
|
], { stdio: ["ignore", 2, 2], env: uvEnv }, "package installation");
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
939
|
+
if (!executables.every((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)))) {
|
|
940
|
+
throw expectedError("HQ Fleet Local installed without its runtime executables.");
|
|
941
|
+
}
|
|
942
|
+
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: installation.engine })}\n`, { mode: 0o600 });
|
|
943
|
+
return descriptor();
|
|
944
|
+
}, { root: path.join(root, "runtimes"), command: "HQ Fleet Runtime installation", waitMs: 20 * 60_000, staleMs: 2 * 60 * 60_000 });
|
|
945
|
+
}
|
|
946
|
+
function writeNativeProfileMarker(profileDir, shared, engine) {
|
|
947
|
+
const marker = path.join(profileDir, "bootstrap.json");
|
|
948
|
+
const temporary = `${marker}.${process.pid}.${crypto.randomUUID()}`;
|
|
949
|
+
fs.writeFileSync(temporary, `${JSON.stringify({ schemaVersion: 2, runtimeId: shared.id, engine }, null, 2)}\n`, { mode: 0o600 });
|
|
950
|
+
fs.renameSync(temporary, marker);
|
|
951
|
+
}
|
|
952
|
+
function removeEmbeddedRuntime(profileDir, platform = process.platform) {
|
|
953
|
+
fs.rmSync(path.join(profileDir, "sources"), { recursive: true, force: true });
|
|
954
|
+
if (hasEmbeddedRuntime(profileDir, platform))
|
|
955
|
+
fs.rmSync(path.join(profileDir, "runtime"), { recursive: true, force: true });
|
|
956
|
+
}
|
|
957
|
+
export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
958
|
+
const run = deps.run ?? execFileSync;
|
|
959
|
+
const platform = deps.platform ?? process.platform;
|
|
960
|
+
const root = deps.fleetHome ?? fleetHome();
|
|
961
|
+
const python = deps.python ?? await ensureFleetPython({ fleetHome: root, platform, run });
|
|
962
|
+
const profileDir = adoptLegacyProfileDirectory(input.agentUid, root);
|
|
963
|
+
fs.mkdirSync(profileDir, { recursive: true, mode: 0o700 });
|
|
964
|
+
const shared = await installSharedFleetRuntime(input.installation, root, python, platform, run);
|
|
965
|
+
writeNativeProfileMarker(profileDir, shared, input.installation.engine);
|
|
966
|
+
const { systemPrompt, machineCredentials, ...profileInput } = input;
|
|
800
967
|
if (systemPrompt !== undefined)
|
|
801
968
|
refreshFleetLocalInstructions(input.agentUid, systemPrompt, { fleetHome: root, platform, run });
|
|
802
|
-
const profile = writeFleetLocalProfile(profileInput, { fleetHome: root, platform });
|
|
803
|
-
const helper = venvExecutable(runtimeDir, "hq-fleet-local", platform);
|
|
804
|
-
const runtime = venvExecutable(runtimeDir, "hq-fleet", platform);
|
|
969
|
+
const profile = writeFleetLocalProfile(profileInput, { fleetHome: root, platform, runtimeRoot: shared.root });
|
|
970
|
+
const helper = venvExecutable(shared.runtimeDir, "hq-fleet-local", platform);
|
|
971
|
+
const runtime = venvExecutable(shared.runtimeDir, "hq-fleet", platform);
|
|
805
972
|
if (!fs.existsSync(helper) || !fs.existsSync(runtime)) {
|
|
806
973
|
throw expectedError("HQ Fleet Local installed without its runtime executables.");
|
|
807
974
|
}
|
|
808
975
|
checkedRun(run, helper, ["render-profile", "--spec", profile, "--mode", "create"], { stdio: "ignore" }, "profile rendering");
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
976
|
+
if (machineCredentials) {
|
|
977
|
+
const machineValue = fs.readFileSync(machineCredentials, "utf8").trim();
|
|
978
|
+
if (!machineValue) {
|
|
979
|
+
throw expectedError(`HQ Fleet Local machine credential file is empty: ${machineCredentials}`);
|
|
980
|
+
}
|
|
981
|
+
checkedRun(run, helper, [
|
|
982
|
+
"machine-credential",
|
|
983
|
+
"import",
|
|
984
|
+
"--profile",
|
|
985
|
+
profileDir,
|
|
986
|
+
"--bot-id",
|
|
987
|
+
input.agentUid,
|
|
988
|
+
], { input: machineValue, stdio: ["pipe", "ignore", "pipe"] }, "machine credential import");
|
|
989
|
+
fs.rmSync(machineCredentials, { force: true });
|
|
812
990
|
}
|
|
813
|
-
checkedRun(run, helper, [
|
|
814
|
-
"machine-credential",
|
|
815
|
-
"import",
|
|
816
|
-
"--profile",
|
|
817
|
-
profileDir,
|
|
818
|
-
"--bot-id",
|
|
819
|
-
input.agentUid,
|
|
820
|
-
], { input: machineValue, stdio: ["pipe", "ignore", "pipe"] }, "machine credential import");
|
|
821
|
-
fs.rmSync(input.machineCredentials, { force: true });
|
|
822
991
|
checkedRun(run, helper, ["doctor-profile", "--profile", profileDir, "--runtime", runtime], { stdio: "ignore" }, "profile validation");
|
|
823
992
|
const start = deps.start !== false;
|
|
824
993
|
if (start) {
|
|
825
994
|
checkedRun(run, helper, ["service", "install", "--profile", profileDir, "--runtime", runtime], { stdio: ["ignore", 2, 2] }, "service installation");
|
|
995
|
+
removeEmbeddedRuntime(profileDir, platform);
|
|
826
996
|
}
|
|
827
997
|
return { profile, runtime, helper, running: start };
|
|
828
998
|
}
|
package/dist/lib/bot/run.js
CHANGED
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
*
|
|
47
47
|
* Everything with a side effect is injectable so the loop is unit-testable.
|
|
48
48
|
*/
|
|
49
|
+
import { botAuthEnv, describeBotAuth, formatBotAuth } from "./runtime-auth.js";
|
|
49
50
|
import { ownerContextBlock } from "./owner-context.js";
|
|
50
51
|
import { randomUUID } from "node:crypto";
|
|
51
52
|
import { hasPromotionHold } from "./promotion-hold.js";
|
|
@@ -558,6 +559,12 @@ export async function runBot(deps) {
|
|
|
558
559
|
setStatus({ ...fallback(), pid, state: "starting", canDrainForPromotion: false, runtimeSignIn: null, cliVersion });
|
|
559
560
|
log("info", `starting ${config.name} on hq-cli ${cliVersion} (${config.runtime}, ${kind} bot${companies.length ? ` of ${companies.join(", ")}` : ""}) as ${config.agentUid} for ${config.ownerUid}`);
|
|
560
561
|
log("info", `${runtime.id} binary: ${describeRuntimeBinary(runtime, turnEnv(process.env, kind, config.agentUid, companySlug), turnCwd)}`);
|
|
562
|
+
{
|
|
563
|
+
const login = describeBotAuth(dir, config);
|
|
564
|
+
log("info", `${runtime.id} login: ${formatBotAuth(login, config.name)}`);
|
|
565
|
+
if (login.login === "own" && !login.present)
|
|
566
|
+
setStatus({ lastError: `own ${runtime.id} login missing: run hq bot login ${config.name} --runtime ${runtime.id}` });
|
|
567
|
+
}
|
|
561
568
|
deps.cliUpdate?.startup(log);
|
|
562
569
|
try {
|
|
563
570
|
const record = await api.getAgent(config.agentUid);
|
|
@@ -1048,7 +1055,15 @@ export async function runBot(deps) {
|
|
|
1048
1055
|
// resets). Claude gets the full hook set via its payload session id;
|
|
1049
1056
|
// Codex/Grok only see HQ_SESSION_ID.
|
|
1050
1057
|
const sid = session?.sessionId ?? randomUUID();
|
|
1051
|
-
|
|
1058
|
+
let env;
|
|
1059
|
+
try {
|
|
1060
|
+
// The bot's own login or account, when set. A missing one fails the
|
|
1061
|
+
// turn loudly (status + reply); it never falls back to the owner's login.
|
|
1062
|
+
env = withOsUser(botAuthEnv(dir, { ...live, runtime: config.runtime }, turnEnv(process.env, kind, config.agentUid, companySlug)));
|
|
1063
|
+
}
|
|
1064
|
+
catch (err) {
|
|
1065
|
+
throw new CompanyBindingError(`I can't answer yet: ${err instanceof Error ? err.message : String(err)} I did not use anyone else's login; this work is still pending.`);
|
|
1066
|
+
}
|
|
1052
1067
|
let bind = { env };
|
|
1053
1068
|
if (companySlug) {
|
|
1054
1069
|
try {
|
|
@@ -1941,7 +1956,29 @@ export async function runBot(deps) {
|
|
|
1941
1956
|
// The job's stored company is checked against the bot's current config
|
|
1942
1957
|
// (read fresh from disk) every time it fires. A company the bot no longer
|
|
1943
1958
|
// serves is never synced: the run is skipped and the job removed.
|
|
1944
|
-
const
|
|
1959
|
+
const freshConfig = readBotConfig(dir);
|
|
1960
|
+
if (!freshConfig) {
|
|
1961
|
+
// bot.json is missing or unreadable, so which company the bot serves is
|
|
1962
|
+
// unknown. Skip this run without writing anything; the job stays for the
|
|
1963
|
+
// next fire.
|
|
1964
|
+
const text = `Skipped the hot-channel sync for ${syncCompany}: the bot's config (bot.json) could not be read, so nothing was written. The job will try again at its next run.`;
|
|
1965
|
+
markProcessed(dir, id, now().toISOString());
|
|
1966
|
+
try {
|
|
1967
|
+
await sendToPerson(text);
|
|
1968
|
+
}
|
|
1969
|
+
catch (err) {
|
|
1970
|
+
log("warn", `report for run ${id} of job ${jobId} was not delivered: ${err instanceof Error ? err.message : String(err)}`);
|
|
1971
|
+
}
|
|
1972
|
+
await completeJobRun(jobId, id, { status: "skipped-precondition", output: jobRunOutput(text) });
|
|
1973
|
+
markReported("skipped-precondition");
|
|
1974
|
+
await api.ackInbox(config.agentUid, id);
|
|
1975
|
+
clearInflight(dir, id);
|
|
1976
|
+
attempts.delete(id);
|
|
1977
|
+
setStatus({ lastActivityAt: now().toISOString() });
|
|
1978
|
+
log("warn", `hot-signal-sync job ${jobId} run ${id} skipped: bot.json missing or unreadable`);
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
const currentCompany = currentHotSignalSyncCompany(freshConfig);
|
|
1945
1982
|
if (currentCompany !== syncCompany) {
|
|
1946
1983
|
let removedNote = "";
|
|
1947
1984
|
try {
|