@indigoai-us/hq-cli 5.345.180 → 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 +3 -0
- package/dist/commands/bot.js +37 -9
- package/dist/lib/bot/fleet-local.d.ts +6 -2
- package/dist/lib/bot/fleet-local.js +164 -46
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.181] — 2026-10-30
|
|
6
|
+
- Local personal and company bots now share one HQ Fleet runtime installation while keeping isolated native profiles and gateway services. Existing local Fleet bots migrate in place without losing profile state.
|
|
7
|
+
|
|
5
8
|
## [5.345.180] — 2026-10-30
|
|
6
9
|
- New `hq channels add <channel> <member...>` and `hq channels remove <channel> <member>`. An owner can now add a personal bot (or a teammate) to a channel from the CLI by @handle, email, or prs_/agt_ uid, so everyone in the channel can @mention it. Before this there was no verb for it, and posting "@bot" into the channel was refused with "No channel member matches". The server's join-first refusal and the owner-or-company-admin rule for adding and removing members are explained in plain words. Feedback e165b286-b059-4d5c-bd44-ac344f00c821.
|
|
7
10
|
- A local bot can now have a model login of its own, so it no longer has to use yours. `hq bot login <name> [--runtime claude|codex]` runs the runtime's own sign-in into `~/.hq/bots/<name>/auth/<runtime>/` (`CLAUDE_CONFIG_DIR` or `CODEX_HOME`). `hq bot logout <name>` signs that login out and deletes the folder. `hq bot set <name> --account <id|default>` points the bot at an existing `hq capacity` account instead. Every model turn uses the chosen login. When that login is missing or its account is gone, the turn does not fall back to your login: the bot replies in the DM saying so, keeps the message pending, and `hq bot status` shows the problem. `hq bot status` now has a `login:` line (default, own, or the account name) and never shows a token.
|
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, 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, 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
|
}
|
|
@@ -153,7 +153,7 @@ function fleetAuthCommand(name, config) {
|
|
|
153
153
|
return `hq bot auth ${name} --provider ${config.runtime === "grok" ? "grok" : config.runtime === "codex" ? "chatgpt" : "claude"}`;
|
|
154
154
|
}
|
|
155
155
|
function runFleetRuntimeControl(name, config, action, json) {
|
|
156
|
-
if ((action === "start" || action === "restart") && config.fleetAuthPending === true) {
|
|
156
|
+
if ((action === "install" || action === "start" || action === "restart") && config.fleetAuthPending === true) {
|
|
157
157
|
const authCommand = fleetAuthCommand(name, config);
|
|
158
158
|
if (json)
|
|
159
159
|
console.log(JSON.stringify({ ok: false, name, running: false, state: "stopped", needsSignIn: true, authCommand }, null, 2));
|
|
@@ -161,10 +161,10 @@ function runFleetRuntimeControl(name, config, action, json) {
|
|
|
161
161
|
throw Object.assign(new Error(`Sign this bot in first: ${authCommand}`), { expected: true });
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
164
|
-
if (action === "start" || action === "restart") {
|
|
164
|
+
if (action === "install" || action === "start" || action === "restart") {
|
|
165
165
|
refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
|
|
166
166
|
}
|
|
167
|
-
const installService = config.fleetServiceInstallPending === true && (action === "start" || action === "restart");
|
|
167
|
+
const installService = action === "install" || (config.fleetServiceInstallPending === true && (action === "start" || action === "restart"));
|
|
168
168
|
const result = controlFleetLocalBot(config.agentUid, installService ? "install" : action);
|
|
169
169
|
if (installService && result.ok)
|
|
170
170
|
patchBotConfig(botDir(name), { fleetServiceInstallPending: false, enabled: true });
|
|
@@ -434,10 +434,11 @@ async function startFleetRuntimeBot(name, dir, config, output, extra) {
|
|
|
434
434
|
throw Object.assign(new Error(`Sign this bot in first: ${authCommand}`), { expected: true });
|
|
435
435
|
return { running: false, how: "waiting for sign-in" };
|
|
436
436
|
}
|
|
437
|
+
const adopted = await adoptFleetRuntimeProfile(name, dir, config);
|
|
437
438
|
refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
|
|
438
439
|
const before = readBotStatus(dir);
|
|
439
440
|
const alreadyRunning = !!before && before.state === "running" && isPidAlive(before.pid);
|
|
440
|
-
const installService = config.fleetServiceInstallPending === true;
|
|
441
|
+
const installService = adopted || config.fleetServiceInstallPending === true;
|
|
441
442
|
const result = controlFleetLocalBot(config.agentUid, installService ? "install" : alreadyRunning ? "restart" : "start");
|
|
442
443
|
if (installService && result.ok)
|
|
443
444
|
patchBotConfig(dir, { fleetServiceInstallPending: false, enabled: true });
|
|
@@ -459,6 +460,29 @@ async function startFleetRuntimeBot(name, dir, config, output, extra) {
|
|
|
459
460
|
console.log(chalk.yellow(`${name} did not report running within 45s`) + chalk.dim(` logs: ~/.hq-fleet/profiles/${config.agentUid}/logs`));
|
|
460
461
|
return { running, how };
|
|
461
462
|
}
|
|
463
|
+
async function adoptFleetRuntimeProfile(name, dir, config) {
|
|
464
|
+
if (!fleetLocalProfileNeedsAdoption(config.agentUid))
|
|
465
|
+
return false;
|
|
466
|
+
const installation = await installPinnedFleetLocalDistribution();
|
|
467
|
+
const python = await ensureFleetPython();
|
|
468
|
+
const companyScope = effectiveBotKind(config) === "company"
|
|
469
|
+
? effectiveBotCompanies(config)
|
|
470
|
+
: await fleetOwnerCompanies();
|
|
471
|
+
await bootstrapFleetLocalBot({
|
|
472
|
+
agentUid: config.agentUid,
|
|
473
|
+
ownerUid: config.ownerUid,
|
|
474
|
+
hqRoot: config.hqRoot,
|
|
475
|
+
botDir: dir,
|
|
476
|
+
installation,
|
|
477
|
+
systemPrompt: fleetBotSystemPrompt(name, config),
|
|
478
|
+
provider: fleetProviderForRuntime(config.runtime, config.model),
|
|
479
|
+
ownerCompanies: companyScope,
|
|
480
|
+
}, { python, start: false });
|
|
481
|
+
const credentials = botCredsPath(dir);
|
|
482
|
+
if (fs.existsSync(credentials))
|
|
483
|
+
importFleetLocalMachineCredential(config.agentUid, credentials);
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
462
486
|
/**
|
|
463
487
|
* `output`: "json" | "text" print a result; "silent" prints nothing — used
|
|
464
488
|
* when stop is a step inside rm/restart, whose own JSON must be the only
|
|
@@ -2073,7 +2097,8 @@ export function botExists(name) {
|
|
|
2073
2097
|
export async function runBotRestart(nameArg, opts = {}) {
|
|
2074
2098
|
const b = requireBot(nameArg);
|
|
2075
2099
|
if (isFleetRuntimeBot(b.config)) {
|
|
2076
|
-
|
|
2100
|
+
const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
|
|
2101
|
+
runFleetRuntimeControl(b.name, b.config, adopted ? "install" : "restart", opts.json === true || opts.output === "json");
|
|
2077
2102
|
return;
|
|
2078
2103
|
}
|
|
2079
2104
|
const running = readBotStatus(b.dir);
|
|
@@ -2094,10 +2119,11 @@ async function runBotAuth(nameArg, opts) {
|
|
|
2094
2119
|
if (opts.apiKey && !apiKey) {
|
|
2095
2120
|
throw Object.assign(new Error("No API key was received on stdin."), { expected: true });
|
|
2096
2121
|
}
|
|
2122
|
+
const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
|
|
2097
2123
|
const result = authenticateFleetLocalBot({
|
|
2098
2124
|
agentUid: b.config.agentUid,
|
|
2099
2125
|
provider: opts.provider,
|
|
2100
|
-
...(b.config.fleetAuthPending === true ? { installService: true } : {}),
|
|
2126
|
+
...(adopted || b.config.fleetAuthPending === true ? { installService: true } : {}),
|
|
2101
2127
|
...(opts.model ? { model: opts.model } : {}),
|
|
2102
2128
|
...(apiKey ? { apiKey } : {}),
|
|
2103
2129
|
...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
|
|
@@ -2529,8 +2555,9 @@ async function repairLocalBot(name, api, bot, daemon) {
|
|
|
2529
2555
|
});
|
|
2530
2556
|
createBotLogger(dir)("info", `re-issued machine credentials for ${name} (${bot.agentUid}) on ${localMachineId()}`);
|
|
2531
2557
|
if (isFleetRuntimeBot(config)) {
|
|
2558
|
+
const adopted = await adoptFleetRuntimeProfile(name, dir, config);
|
|
2532
2559
|
importFleetLocalMachineCredential(bot.agentUid, botCredsPath(dir));
|
|
2533
|
-
const restarted = controlFleetLocalBot(bot.agentUid, "restart").ok === true;
|
|
2560
|
+
const restarted = controlFleetLocalBot(bot.agentUid, adopted ? "install" : "restart").ok === true;
|
|
2534
2561
|
return restarted ? "new credentials, HQ Fleet Runtime restarted" : "new credentials; the HQ Fleet Runtime did not restart (hq bot start " + name + ")";
|
|
2535
2562
|
}
|
|
2536
2563
|
if (daemon && process.platform === "darwin") {
|
|
@@ -2782,7 +2809,8 @@ export function registerBotCommand(program) {
|
|
|
2782
2809
|
.action((name, opts) => wrap(async () => {
|
|
2783
2810
|
const b = requireBot(name);
|
|
2784
2811
|
if (isFleetRuntimeBot(b.config)) {
|
|
2785
|
-
|
|
2812
|
+
const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
|
|
2813
|
+
runFleetRuntimeControl(b.name, b.config, adopted ? "install" : "start", opts.json === true);
|
|
2786
2814
|
return;
|
|
2787
2815
|
}
|
|
2788
2816
|
await startBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
|
|
@@ -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;
|
|
@@ -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
|
|
@@ -322,14 +322,83 @@ export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
|
322
322
|
engine: engineMetadata(release),
|
|
323
323
|
};
|
|
324
324
|
}
|
|
325
|
+
function profileName(agentUid) {
|
|
326
|
+
return agentUid.toLowerCase();
|
|
327
|
+
}
|
|
325
328
|
function profileRoot(agentUid, root) {
|
|
326
|
-
|
|
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;
|
|
327
368
|
}
|
|
328
369
|
function venvExecutable(runtimeDir, name, platform) {
|
|
329
370
|
return platform === "win32"
|
|
330
371
|
? path.join(runtimeDir, "Scripts", `${name}.exe`)
|
|
331
372
|
: path.join(runtimeDir, "bin", name);
|
|
332
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
|
+
}
|
|
333
402
|
function fleetLocalExecutables(agentUid, deps) {
|
|
334
403
|
if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid)) {
|
|
335
404
|
throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
|
|
@@ -337,14 +406,16 @@ function fleetLocalExecutables(agentUid, deps) {
|
|
|
337
406
|
const root = deps.fleetHome ?? fleetHome();
|
|
338
407
|
const platform = deps.platform ?? process.platform;
|
|
339
408
|
const profileDir = profileRoot(agentUid, root);
|
|
340
|
-
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");
|
|
341
412
|
const profile = path.join(profileDir, "profile.json");
|
|
342
413
|
const runtime = venvExecutable(runtimeDir, "hq-fleet", platform);
|
|
343
414
|
const helper = venvExecutable(runtimeDir, "hq-fleet-local", platform);
|
|
344
415
|
if (![profile, runtime, helper].every((item) => fs.existsSync(item))) {
|
|
345
416
|
throw expectedError(`HQ Fleet Local is not fully installed for ${agentUid}.`);
|
|
346
417
|
}
|
|
347
|
-
return { profileDir, profile, runtime, helper };
|
|
418
|
+
return { profileDir, profile, runtime, helper, runtimeDir, sourcesDir };
|
|
348
419
|
}
|
|
349
420
|
function runJson(run, executable, args, options, label) {
|
|
350
421
|
let output;
|
|
@@ -377,10 +448,39 @@ export function fleetLocalGatewayPid(agentUid, deps = {}) {
|
|
|
377
448
|
return null;
|
|
378
449
|
}
|
|
379
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
|
+
}
|
|
380
477
|
export function controlFleetLocalBot(agentUid, action, deps = {}) {
|
|
381
478
|
const run = deps.run ?? execFileSync;
|
|
382
479
|
const files = fleetLocalExecutables(agentUid, deps);
|
|
383
|
-
|
|
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;
|
|
384
484
|
}
|
|
385
485
|
const providerProfiles = {
|
|
386
486
|
chatgpt: {
|
|
@@ -511,14 +611,15 @@ export function removeFleetLocalBot(agentUid, deps = {}) {
|
|
|
511
611
|
const profileDir = profileRoot(agentUid, deps.fleetHome ?? fleetHome());
|
|
512
612
|
if (!fs.existsSync(profileDir))
|
|
513
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
|
+
}
|
|
514
618
|
const platform = deps.platform ?? process.platform;
|
|
515
|
-
const
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
venvExecutable(runtimeDir, "hq-fleet-local", platform),
|
|
520
|
-
].every((item) => fs.existsSync(item));
|
|
521
|
-
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)))) {
|
|
522
623
|
fs.rmSync(profileDir, { recursive: true, force: true });
|
|
523
624
|
return { removed: true, credentialsRemoved: 0 };
|
|
524
625
|
}
|
|
@@ -634,7 +735,9 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
634
735
|
}
|
|
635
736
|
const root = deps.fleetHome ?? fleetHome();
|
|
636
737
|
const dir = profileRoot(input.agentUid, root);
|
|
637
|
-
const
|
|
738
|
+
const markerRuntime = sharedRuntimeFromMarker(dir, root);
|
|
739
|
+
const runtimeRoot = deps.runtimeRoot ?? markerRuntime?.root ?? dir;
|
|
740
|
+
const runtimeDir = path.join(runtimeRoot, "runtime");
|
|
638
741
|
const destination = path.join(dir, "profile.json");
|
|
639
742
|
const temporary = path.join(dir, `.profile-${process.pid}-${crypto.randomUUID()}`);
|
|
640
743
|
const profile = {
|
|
@@ -647,7 +750,7 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
647
750
|
profileDir: path.resolve(dir),
|
|
648
751
|
runtime: { commit: input.installation.release.runtimeCommit, path: path.resolve(runtimeDir) },
|
|
649
752
|
paths: {
|
|
650
|
-
pluginSource: path.resolve(path.join(
|
|
753
|
+
pluginSource: path.resolve(path.join(runtimeRoot, "sources", "distribution", "src", "hq_fleet_local", "plugins", "hqdm")),
|
|
651
754
|
statusFile: path.resolve(path.join(input.botDir, "status.json")),
|
|
652
755
|
logsDir: path.resolve(path.join(input.botDir, "logs")),
|
|
653
756
|
},
|
|
@@ -706,9 +809,13 @@ export function scanFleetLocalInstructions(agentUid, text, deps = {}) {
|
|
|
706
809
|
throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
|
|
707
810
|
}
|
|
708
811
|
const run = deps.run ?? execFileSync;
|
|
709
|
-
const
|
|
710
|
-
const
|
|
711
|
-
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");
|
|
712
819
|
if (!fs.existsSync(python) || !fs.existsSync(source)) {
|
|
713
820
|
throw expectedError("HQ Fleet Local is not installed for this bot, so its instructions could not be checked.");
|
|
714
821
|
}
|
|
@@ -774,6 +881,12 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
774
881
|
extras: ["mcp", "anthropic"],
|
|
775
882
|
})));
|
|
776
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
|
+
});
|
|
777
890
|
return withBotLock(identity, async () => {
|
|
778
891
|
const marker = path.join(shared, "ready.json");
|
|
779
892
|
const sources = path.join(shared, "sources");
|
|
@@ -785,7 +898,7 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
785
898
|
if (!executables.every((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)))) {
|
|
786
899
|
throw expectedError("The shared HQ Fleet Runtime is incomplete. Reinstall it before creating another bot.");
|
|
787
900
|
}
|
|
788
|
-
return
|
|
901
|
+
return descriptor();
|
|
789
902
|
}
|
|
790
903
|
fs.rmSync(shared, { recursive: true, force: true });
|
|
791
904
|
fs.mkdirSync(distributionSource, { recursive: true, mode: 0o700 });
|
|
@@ -827,54 +940,59 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
827
940
|
throw expectedError("HQ Fleet Local installed without its runtime executables.");
|
|
828
941
|
}
|
|
829
942
|
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: installation.engine })}\n`, { mode: 0o600 });
|
|
830
|
-
return
|
|
943
|
+
return descriptor();
|
|
831
944
|
}, { root: path.join(root, "runtimes"), command: "HQ Fleet Runtime installation", waitMs: 20 * 60_000, staleMs: 2 * 60 * 60_000 });
|
|
832
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
|
+
}
|
|
833
957
|
export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
834
958
|
const run = deps.run ?? execFileSync;
|
|
835
959
|
const platform = deps.platform ?? process.platform;
|
|
836
960
|
const root = deps.fleetHome ?? fleetHome();
|
|
837
961
|
const python = deps.python ?? await ensureFleetPython({ fleetHome: root, platform, run });
|
|
838
|
-
const profileDir =
|
|
839
|
-
const runtimeDir = path.join(profileDir, "runtime");
|
|
840
|
-
const marker = path.join(profileDir, "bootstrap.json");
|
|
962
|
+
const profileDir = adoptLegacyProfileDirectory(input.agentUid, root);
|
|
841
963
|
fs.mkdirSync(profileDir, { recursive: true, mode: 0o700 });
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const target = path.join(profileDir, name);
|
|
846
|
-
fs.rmSync(target, { recursive: true, force: true });
|
|
847
|
-
fs.symlinkSync(path.join(shared, name), target, platform === "win32" ? "junction" : "dir");
|
|
848
|
-
}
|
|
849
|
-
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: input.installation.engine }, null, 2)}\n`, { mode: 0o600 });
|
|
850
|
-
}
|
|
851
|
-
const { systemPrompt, ...profileInput } = input;
|
|
964
|
+
const shared = await installSharedFleetRuntime(input.installation, root, python, platform, run);
|
|
965
|
+
writeNativeProfileMarker(profileDir, shared, input.installation.engine);
|
|
966
|
+
const { systemPrompt, machineCredentials, ...profileInput } = input;
|
|
852
967
|
if (systemPrompt !== undefined)
|
|
853
968
|
refreshFleetLocalInstructions(input.agentUid, systemPrompt, { fleetHome: root, platform, run });
|
|
854
|
-
const profile = writeFleetLocalProfile(profileInput, { fleetHome: root, platform });
|
|
855
|
-
const helper = venvExecutable(runtimeDir, "hq-fleet-local", platform);
|
|
856
|
-
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);
|
|
857
972
|
if (!fs.existsSync(helper) || !fs.existsSync(runtime)) {
|
|
858
973
|
throw expectedError("HQ Fleet Local installed without its runtime executables.");
|
|
859
974
|
}
|
|
860
975
|
checkedRun(run, helper, ["render-profile", "--spec", profile, "--mode", "create"], { stdio: "ignore" }, "profile rendering");
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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 });
|
|
864
990
|
}
|
|
865
|
-
checkedRun(run, helper, [
|
|
866
|
-
"machine-credential",
|
|
867
|
-
"import",
|
|
868
|
-
"--profile",
|
|
869
|
-
profileDir,
|
|
870
|
-
"--bot-id",
|
|
871
|
-
input.agentUid,
|
|
872
|
-
], { input: machineValue, stdio: ["pipe", "ignore", "pipe"] }, "machine credential import");
|
|
873
|
-
fs.rmSync(input.machineCredentials, { force: true });
|
|
874
991
|
checkedRun(run, helper, ["doctor-profile", "--profile", profileDir, "--runtime", runtime], { stdio: "ignore" }, "profile validation");
|
|
875
992
|
const start = deps.start !== false;
|
|
876
993
|
if (start) {
|
|
877
994
|
checkedRun(run, helper, ["service", "install", "--profile", profileDir, "--runtime", runtime], { stdio: ["ignore", 2, 2] }, "service installation");
|
|
995
|
+
removeEmbeddedRuntime(profileDir, platform);
|
|
878
996
|
}
|
|
879
997
|
return { profile, runtime, helper, running: start };
|
|
880
998
|
}
|
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.181",
|
|
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.181",
|
|
118
|
+
"@indigoai-us/hq-hook-win32-arm64": "5.345.181",
|
|
119
|
+
"@indigoai-us/hq-hook-darwin-x64": "5.345.181",
|
|
120
|
+
"@indigoai-us/hq-hook-darwin-arm64": "5.345.181",
|
|
121
|
+
"@indigoai-us/hq-hook-linux-x64": "5.345.181",
|
|
122
|
+
"@indigoai-us/hq-hook-linux-arm64": "5.345.181"
|
|
123
123
|
}
|
|
124
124
|
}
|