@indigoai-us/hq-cli 5.174.0 → 5.176.0
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 +13 -0
- package/dist/commands/agent-kit.js +5 -2
- package/dist/commands/bot.d.ts +1 -0
- package/dist/commands/bot.js +96 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.176.0] — 2026-09-24
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `hq bot create setup` restores the account's existing setup bot when the local copy is missing.
|
|
10
|
+
|
|
11
|
+
## [5.175.0] — 2026-09-24
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- `hq agent kit` now shows missing external credentials at a home-relative path and reports the missing enrollment as an actionable precondition.
|
|
16
|
+
- Missing external agent credentials show an actionable error without creating a Sentry fault.
|
|
17
|
+
|
|
5
18
|
## [5.174.0] — 2026-09-24
|
|
6
19
|
|
|
7
20
|
### Fixed
|
|
@@ -37,6 +37,8 @@ import { buildWakeNotice, clearWakeConfig, currentWakeState, DEFAULT_WAKE_RETRY_
|
|
|
37
37
|
import { runSyncLoop } from "../lib/agent-kit/run/sync.js";
|
|
38
38
|
import { resolveHqBinary, resolveNodeBinary, } from "../lib/mesh/live/daemon/install.js";
|
|
39
39
|
import { installServices, servicesStatus, uninstallServices, } from "../lib/service-manager/index.js";
|
|
40
|
+
import { markExpectedUserError } from "../utils/expected-cli-error.js";
|
|
41
|
+
import { scrubHomeDirectoryPrefix } from "../utils/install-tree-torn.js";
|
|
40
42
|
export class KitError extends Error {
|
|
41
43
|
constructor(message) {
|
|
42
44
|
super(message);
|
|
@@ -47,8 +49,9 @@ export class KitError extends Error {
|
|
|
47
49
|
export function requireExternalCreds(paths) {
|
|
48
50
|
const creds = readExternalMachineCreds(paths);
|
|
49
51
|
if (!creds) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
const credentialsPath = scrubHomeDirectoryPrefix(paths.machineCredsPath, paths.home);
|
|
53
|
+
throw markExpectedUserError(new KitError(`No external agent credentials at ${credentialsPath}. Run ` +
|
|
54
|
+
`\`hq agent enroll <code>\` first (an admin creates the code in the HQ console).`));
|
|
52
55
|
}
|
|
53
56
|
return creds;
|
|
54
57
|
}
|
package/dist/commands/bot.d.ts
CHANGED
package/dist/commands/bot.js
CHANGED
|
@@ -481,9 +481,6 @@ export async function runBotCreate(nameArg, opts) {
|
|
|
481
481
|
const kickoff = opts.kickoff !== undefined ? validateBotKickoff(opts.kickoff) : undefined;
|
|
482
482
|
const model = opts.model !== undefined ? validateBotModel(opts.model) : undefined;
|
|
483
483
|
const effort = opts.effort !== undefined ? validateBotEffort(runtime, opts.effort) : undefined;
|
|
484
|
-
if (name === RESERVED_SETUP_BOT_NAME && opts.worker?.trim() !== RESERVED_SETUP_BOT_NAME) {
|
|
485
|
-
throw Object.assign(new Error(`The name ${RESERVED_SETUP_BOT_NAME} is reserved for HQ's setup bot; create it with --worker ${RESERVED_SETUP_BOT_NAME}.`), { expected: true });
|
|
486
|
-
}
|
|
487
484
|
const dir = botDir(name);
|
|
488
485
|
if (readBotConfig(dir)) {
|
|
489
486
|
throw Object.assign(new Error(`Bot "${name}" already exists (${dir}). Use hq bot start ${name} or hq bot rm ${name}.`), {
|
|
@@ -495,11 +492,84 @@ export async function runBotCreate(nameArg, opts) {
|
|
|
495
492
|
// cost an identity).
|
|
496
493
|
const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
|
|
497
494
|
const { kind, companies } = resolveCreateKind(opts, worker);
|
|
495
|
+
const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
|
|
496
|
+
if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
|
|
497
|
+
throw Object.assign(new Error(`The name ${RESERVED_SETUP_BOT_NAME} is reserved for HQ's setup bot; create it with --worker ${RESERVED_SETUP_BOT_NAME}.`), { expected: true });
|
|
498
|
+
}
|
|
498
499
|
const token = await ownerToken();
|
|
499
500
|
const owner = await resolveCallerPersonUid(token);
|
|
500
501
|
if (!owner)
|
|
501
502
|
throw Object.assign(new Error("Your HQ login has no person entity yet; open the HQ desktop app once, then retry."), { expected: true });
|
|
502
503
|
const api = new BotApi({ token: async () => token });
|
|
504
|
+
const adoptOwnedSetupBot = async (allowUnsupportedListing) => {
|
|
505
|
+
let mine;
|
|
506
|
+
try {
|
|
507
|
+
mine = await api.listMine({ local: true });
|
|
508
|
+
}
|
|
509
|
+
catch (err) {
|
|
510
|
+
const failure = remoteBotsUnavailable(err);
|
|
511
|
+
if (allowUnsupportedListing && failure.reason === "server-unsupported")
|
|
512
|
+
return false;
|
|
513
|
+
if (opts.json)
|
|
514
|
+
console.log(JSON.stringify({ ok: false, ...failure }, null, 2));
|
|
515
|
+
throw Object.assign(new Error(failure.message), { expected: true });
|
|
516
|
+
}
|
|
517
|
+
const matches = matchRemoteBots(mine, name);
|
|
518
|
+
if (matches.length > 1) {
|
|
519
|
+
throw Object.assign(new Error(`You own more than one bot called "${name}": ${matches.map((bot) => bot.agentUid).join(", ")}. Name the one you mean by its id.`), { expected: true });
|
|
520
|
+
}
|
|
521
|
+
if (matches.length === 0)
|
|
522
|
+
return false;
|
|
523
|
+
const remote = matches[0];
|
|
524
|
+
if (!isReservedSetupBotRecord(remote, owner))
|
|
525
|
+
return false;
|
|
526
|
+
const { result, config } = await adoptRemoteBot({
|
|
527
|
+
bot: remote,
|
|
528
|
+
name,
|
|
529
|
+
api,
|
|
530
|
+
hqRoot,
|
|
531
|
+
overrides: { ...(model ? { model } : {}) },
|
|
532
|
+
daemon: opts.daemon !== false,
|
|
533
|
+
preserveFirstStart: true,
|
|
534
|
+
});
|
|
535
|
+
const recovered = {
|
|
536
|
+
agentUid: result.agentUid,
|
|
537
|
+
kind: effectiveBotKind(config),
|
|
538
|
+
companies: effectiveBotCompanies(config),
|
|
539
|
+
runtime: config.runtime,
|
|
540
|
+
dir: result.dir,
|
|
541
|
+
worker: config.workerSource === "worker" ? path.join(hqRoot, config.workerDir) : result.worker,
|
|
542
|
+
workerId: config.workerId ?? null,
|
|
543
|
+
companySlug: config.companySlug ?? null,
|
|
544
|
+
memory: botMemoryMode(config),
|
|
545
|
+
memoryDir: result.memoryDir,
|
|
546
|
+
intro: config.intro ?? null,
|
|
547
|
+
kickoff: config.kickoff ?? null,
|
|
548
|
+
model: config.model ?? null,
|
|
549
|
+
effort: config.effort ?? DEFAULT_BOT_EFFORT,
|
|
550
|
+
};
|
|
551
|
+
if (!opts.json) {
|
|
552
|
+
console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${result.agentUid}, ${result.runtime})`));
|
|
553
|
+
console.log(chalk.dim(` kind: ${describeKind(recovered.kind, recovered.companies)}`));
|
|
554
|
+
console.log(chalk.dim(` worker: ${result.worker}`));
|
|
555
|
+
console.log(chalk.dim(` memory: ${result.memoryDir} (${result.memory})`));
|
|
556
|
+
console.log(chalk.dim(` state: ${result.dir}`));
|
|
557
|
+
if (result.daemon)
|
|
558
|
+
console.log(chalk.dim(` ${result.daemon}`));
|
|
559
|
+
}
|
|
560
|
+
if (opts.start !== false) {
|
|
561
|
+
await startBot(name, result.dir, config, opts.json ? "json" : "text", { ...recovered, daemon: result.daemon });
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
if (opts.json)
|
|
565
|
+
console.log(JSON.stringify({ ok: true, name, ...recovered, daemon: result.daemon, running: false }, null, 2));
|
|
566
|
+
return true;
|
|
567
|
+
};
|
|
568
|
+
// HQ adds the owner suffix to the setup bot slug. If a create reached the
|
|
569
|
+
// server but its response was lost, recover the caller-owned bot instead
|
|
570
|
+
// of sending another create that the entity store will reject.
|
|
571
|
+
if (isSetupBot && (await adoptOwnedSetupBot(true)))
|
|
572
|
+
return;
|
|
503
573
|
// A company bot's companies are resolved first (a typo must not cost an
|
|
504
574
|
// identity) and sent with the create: the server makes the bot a member of
|
|
505
575
|
// each and refuses the whole create if the owner cannot add agents there.
|
|
@@ -549,6 +619,12 @@ export async function runBotCreate(nameArg, opts) {
|
|
|
549
619
|
});
|
|
550
620
|
}
|
|
551
621
|
if (err instanceof BotApiError && err.status === 409) {
|
|
622
|
+
if (isSetupBot) {
|
|
623
|
+
if (await adoptOwnedSetupBot(false))
|
|
624
|
+
return;
|
|
625
|
+
throw Object.assign(new Error(`HQ could not create the reserved ${name} bot. If you created it on another computer, bring it back with: ` +
|
|
626
|
+
`hq bot adopt ${name}. Otherwise, choose a different name or ask your HQ admin.`), { expected: true });
|
|
627
|
+
}
|
|
552
628
|
// The cloud already has this bot; it is simply not set up on this
|
|
553
629
|
// computer (a reinstall, or a second Mac). Re-creating it is the wrong
|
|
554
630
|
// move — adopting brings the existing one back, memory and all.
|
|
@@ -1223,6 +1299,18 @@ export function matchRemoteBots(bots, wanted) {
|
|
|
1223
1299
|
(b.name ?? "").trim().toLowerCase() === needle ||
|
|
1224
1300
|
localNameForRemoteBot(b) === needle);
|
|
1225
1301
|
}
|
|
1302
|
+
/** A name match alone does not prove that a remote record is HQ's setup bot. */
|
|
1303
|
+
function isReservedSetupBotRecord(bot, ownerUid) {
|
|
1304
|
+
const owner = ownerUid.trim();
|
|
1305
|
+
const local = parseBotLocalConfig(bot.localConfig);
|
|
1306
|
+
return (!!owner &&
|
|
1307
|
+
bot.ownerUid === owner &&
|
|
1308
|
+
bot.slug === `${RESERVED_SETUP_BOT_NAME}-${owner.slice(-8).toLowerCase()}` &&
|
|
1309
|
+
bot.botKind === "personal" &&
|
|
1310
|
+
bot.computeMode === "local" &&
|
|
1311
|
+
local?.workerSource === "worker" &&
|
|
1312
|
+
local.workerId === SETUP_BOT_WORKER_ID);
|
|
1313
|
+
}
|
|
1226
1314
|
export function resolveRemoteBot(bots, wanted) {
|
|
1227
1315
|
const hits = matchRemoteBots(bots, wanted);
|
|
1228
1316
|
if (hits.length === 1)
|
|
@@ -1392,8 +1480,10 @@ export function configFromLocalConfig(input) {
|
|
|
1392
1480
|
enabled: true,
|
|
1393
1481
|
// A restore is not a first start: the bot's one-off kickoff task already
|
|
1394
1482
|
// ran on the machine it was created on, and `runs once` has to keep
|
|
1395
|
-
// meaning once.
|
|
1396
|
-
|
|
1483
|
+
// meaning once. Recovery after a create response was lost is different:
|
|
1484
|
+
// this machine has not started the bot yet, so its first intro/kickoff
|
|
1485
|
+
// must still run.
|
|
1486
|
+
...(input.preserveFirstStart ? {} : { introSentAt: (input.now ?? (() => new Date()))().toISOString() }),
|
|
1397
1487
|
};
|
|
1398
1488
|
return { config, notes };
|
|
1399
1489
|
}
|
|
@@ -1440,6 +1530,7 @@ async function adoptRemoteBot(input) {
|
|
|
1440
1530
|
dir,
|
|
1441
1531
|
local,
|
|
1442
1532
|
...(input.overrides ? { overrides: input.overrides } : {}),
|
|
1533
|
+
...(input.preserveFirstStart ? { preserveFirstStart: true } : {}),
|
|
1443
1534
|
});
|
|
1444
1535
|
writeBotConfig(dir, config);
|
|
1445
1536
|
const scaffoldOpts = { memoryDir: config.memoryDir };
|