@indigoai-us/hq-cli 5.175.0 → 5.177.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 +21 -0
- package/dist/command-catalog.generated.d.ts +40 -0
- package/dist/command-catalog.generated.js +52 -0
- package/dist/commands/bot.d.ts +1 -0
- package/dist/commands/bot.js +96 -5
- package/dist/commands/lanes.d.ts +4 -1
- package/dist/commands/lanes.js +605 -121
- package/dist/lib/lanes/hire-grants.d.ts +25 -0
- package/dist/lib/lanes/hire-grants.js +85 -0
- package/dist/lib/lanes/index.d.ts +3 -1
- package/dist/lib/lanes/index.js +2 -1
- package/dist/lib/lanes/outpost-dispatch.d.ts +3 -1
- package/dist/lib/lanes/outpost-dispatch.js +7 -1
- package/dist/lib/lanes/store.d.ts +5 -1
- package/dist/lib/lanes/store.js +25 -3
- package/dist/lib/lanes/types.d.ts +2 -0
- package/dist/lib/lanes/types.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.177.0] — 2026-09-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `hq lanes create --hire-grant`, `hq lanes grant`, and `hq lanes backfill-grants`
|
|
10
|
+
manage alive child-lane grants. `lanes.hire-grants` defaults off; lane list and
|
|
11
|
+
show include grant and alive-child counts. Terminal child resume, message, and
|
|
12
|
+
account-move paths recheck occupancy and remaining grant allocation. The
|
|
13
|
+
backfill defaults to dry-run.
|
|
14
|
+
|
|
15
|
+
- When `lanes.hire-grants` is enabled, `hq lanes list` and `hq lanes show` use counts
|
|
16
|
+
from the owning Outpost for active remote lanes. If the Outpost does not return
|
|
17
|
+
counts, both commands show local counts and a warning.
|
|
18
|
+
|
|
19
|
+
## [5.176.0] — 2026-09-24
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `hq bot create setup` restores the account's existing setup bot when the local copy is missing.
|
|
24
|
+
|
|
5
25
|
## [5.175.0] — 2026-09-24
|
|
6
26
|
|
|
7
27
|
### Fixed
|
|
@@ -26,6 +46,7 @@
|
|
|
26
46
|
- Bot LaunchAgents and systemd units, the Work Mesh daemon unit, the `hq daemon` unit, and the lanes and agent-kit units now name node by its upgrade-stable path (`/opt/homebrew/bin/node` or `/opt/homebrew/opt/<formula>/bin/node`, or nvm's `current` link) instead of the versioned `/opt/homebrew/Cellar/node/<version>/bin/node`. Before, `brew upgrade node` deleted that path and launchd could no longer start the bots. The stable path is used only when it resolves to the same node binary; otherwise the unit keeps the resolved path as before.
|
|
27
47
|
- Installed bot LaunchAgents that still name a versioned or missing node are repaired: `hq bot start` rewrites and reloads the agent, a bot started by launchd rewrites its own agent and reloads it from a detached shell before it takes any message, and `hq doctor` reports it (`bots.<name>.daemon-node`), with `hq doctor --fix` rewriting only the plist file and not reloading launchd. `hq bot list` and `hq bot status` flag a missing node binary. `hq mesh daemon install --if-stale` also reinstalls a same-version unit whose node path is missing or versioned.
|
|
28
48
|
|
|
49
|
+
|
|
29
50
|
## [5.172.0] — 2026-09-24
|
|
30
51
|
|
|
31
52
|
### Changed
|
|
@@ -6886,6 +6886,9 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
6886
6886
|
}, {
|
|
6887
6887
|
readonly flags: "--senior <ref>";
|
|
6888
6888
|
readonly description: "session:<id> | lane:<id> | user";
|
|
6889
|
+
}, {
|
|
6890
|
+
readonly flags: "--hire-grant <n>";
|
|
6891
|
+
readonly description: "Maximum alive direct child lanes assigned to this lane; set by its senior";
|
|
6889
6892
|
}, {
|
|
6890
6893
|
readonly flags: "--timeout <secs>";
|
|
6891
6894
|
readonly description: "Deadline in seconds (warns, does not kill)";
|
|
@@ -6906,6 +6909,43 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
6906
6909
|
readonly description: "JSON on stdout (always)";
|
|
6907
6910
|
}];
|
|
6908
6911
|
readonly subcommands: readonly [];
|
|
6912
|
+
}, {
|
|
6913
|
+
readonly name: "grant";
|
|
6914
|
+
readonly description: "Set a lane's alive child-lane grant as its senior";
|
|
6915
|
+
readonly aliases: readonly [];
|
|
6916
|
+
readonly hidden: false;
|
|
6917
|
+
readonly usage: "[options] <lane-id>";
|
|
6918
|
+
readonly arguments: readonly [{
|
|
6919
|
+
readonly name: "lane-id";
|
|
6920
|
+
readonly required: true;
|
|
6921
|
+
readonly variadic: false;
|
|
6922
|
+
}];
|
|
6923
|
+
readonly options: readonly [{
|
|
6924
|
+
readonly flags: "--hire <n>";
|
|
6925
|
+
readonly description: "Non-negative safe integer hire grant";
|
|
6926
|
+
}, {
|
|
6927
|
+
readonly flags: "--json";
|
|
6928
|
+
readonly description: "JSON on stdout (always)";
|
|
6929
|
+
}];
|
|
6930
|
+
readonly subcommands: readonly [];
|
|
6931
|
+
}, {
|
|
6932
|
+
readonly name: "backfill-grants";
|
|
6933
|
+
readonly description: "Preview or apply missing hire grants for alive manager lanes";
|
|
6934
|
+
readonly aliases: readonly [];
|
|
6935
|
+
readonly hidden: false;
|
|
6936
|
+
readonly usage: "[options]";
|
|
6937
|
+
readonly arguments: readonly [];
|
|
6938
|
+
readonly options: readonly [{
|
|
6939
|
+
readonly flags: "--dry-run";
|
|
6940
|
+
readonly description: "Print proposed values without writing (default)";
|
|
6941
|
+
}, {
|
|
6942
|
+
readonly flags: "--apply";
|
|
6943
|
+
readonly description: "Write missing hire grants; only run before enforcement is enabled";
|
|
6944
|
+
}, {
|
|
6945
|
+
readonly flags: "--json";
|
|
6946
|
+
readonly description: "JSON on stdout (always)";
|
|
6947
|
+
}];
|
|
6948
|
+
readonly subcommands: readonly [];
|
|
6909
6949
|
}, {
|
|
6910
6950
|
readonly name: "list";
|
|
6911
6951
|
readonly description: "List lanes";
|
|
@@ -8898,6 +8898,10 @@ export const COMMAND_CATALOG = [
|
|
|
8898
8898
|
"flags": "--senior <ref>",
|
|
8899
8899
|
"description": "session:<id> | lane:<id> | user"
|
|
8900
8900
|
},
|
|
8901
|
+
{
|
|
8902
|
+
"flags": "--hire-grant <n>",
|
|
8903
|
+
"description": "Maximum alive direct child lanes assigned to this lane; set by its senior"
|
|
8904
|
+
},
|
|
8901
8905
|
{
|
|
8902
8906
|
"flags": "--timeout <secs>",
|
|
8903
8907
|
"description": "Deadline in seconds (warns, does not kill)"
|
|
@@ -8925,6 +8929,54 @@ export const COMMAND_CATALOG = [
|
|
|
8925
8929
|
],
|
|
8926
8930
|
"subcommands": []
|
|
8927
8931
|
},
|
|
8932
|
+
{
|
|
8933
|
+
"name": "grant",
|
|
8934
|
+
"description": "Set a lane's alive child-lane grant as its senior",
|
|
8935
|
+
"aliases": [],
|
|
8936
|
+
"hidden": false,
|
|
8937
|
+
"usage": "[options] <lane-id>",
|
|
8938
|
+
"arguments": [
|
|
8939
|
+
{
|
|
8940
|
+
"name": "lane-id",
|
|
8941
|
+
"required": true,
|
|
8942
|
+
"variadic": false
|
|
8943
|
+
}
|
|
8944
|
+
],
|
|
8945
|
+
"options": [
|
|
8946
|
+
{
|
|
8947
|
+
"flags": "--hire <n>",
|
|
8948
|
+
"description": "Non-negative safe integer hire grant"
|
|
8949
|
+
},
|
|
8950
|
+
{
|
|
8951
|
+
"flags": "--json",
|
|
8952
|
+
"description": "JSON on stdout (always)"
|
|
8953
|
+
}
|
|
8954
|
+
],
|
|
8955
|
+
"subcommands": []
|
|
8956
|
+
},
|
|
8957
|
+
{
|
|
8958
|
+
"name": "backfill-grants",
|
|
8959
|
+
"description": "Preview or apply missing hire grants for alive manager lanes",
|
|
8960
|
+
"aliases": [],
|
|
8961
|
+
"hidden": false,
|
|
8962
|
+
"usage": "[options]",
|
|
8963
|
+
"arguments": [],
|
|
8964
|
+
"options": [
|
|
8965
|
+
{
|
|
8966
|
+
"flags": "--dry-run",
|
|
8967
|
+
"description": "Print proposed values without writing (default)"
|
|
8968
|
+
},
|
|
8969
|
+
{
|
|
8970
|
+
"flags": "--apply",
|
|
8971
|
+
"description": "Write missing hire grants; only run before enforcement is enabled"
|
|
8972
|
+
},
|
|
8973
|
+
{
|
|
8974
|
+
"flags": "--json",
|
|
8975
|
+
"description": "JSON on stdout (always)"
|
|
8976
|
+
}
|
|
8977
|
+
],
|
|
8978
|
+
"subcommands": []
|
|
8979
|
+
},
|
|
8928
8980
|
{
|
|
8929
8981
|
"name": "list",
|
|
8930
8982
|
"description": "List lanes",
|
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 };
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -9,9 +9,10 @@ import { Command } from "commander";
|
|
|
9
9
|
import type { OutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
10
10
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
11
11
|
export type { OutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
12
|
+
export { LANE_HIRE_GRANTS_FLAG };
|
|
12
13
|
import { type OutpostDispatchResult } from "../lib/lanes/outpost-dispatch.js";
|
|
13
14
|
import { emitLaneDecisionEvents, type LaneMesh } from "../lib/lanes/mesh-events.js";
|
|
14
|
-
import { MAX_LANE_DEPTH, type LaneCompanyRef, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
|
|
15
|
+
import { LANE_HIRE_GRANTS_FLAG, MAX_LANE_DEPTH, type LaneCompanyRef, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
|
|
15
16
|
import { questionBodyAbsPath } from "../lib/lanes/questions.js";
|
|
16
17
|
import { type RunCompactionFn } from "../lib/lanes/compaction.js";
|
|
17
18
|
import { type KnownModelsDeps, type ProbeCommand } from "../lib/lanes/models.js";
|
|
@@ -77,6 +78,8 @@ export interface LanesCommandDeps {
|
|
|
77
78
|
monitorEvidence?: MonitorEvidenceReader;
|
|
78
79
|
/** Test seam for the default-off outpost placement rollout flag. */
|
|
79
80
|
lanePlacementEnabled?: () => boolean;
|
|
81
|
+
/** Test seam for the default-off lane hire-grants rollout gate. */
|
|
82
|
+
laneHireGrantsEnabled?: () => boolean;
|
|
80
83
|
}
|
|
81
84
|
export declare const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-balancing";
|
|
82
85
|
export declare function parseSeniorRef(raw: string): SeniorRef;
|