@indigoai-us/hq-cli 5.345.193 → 5.345.195
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 +67 -0
- package/dist/command-catalog.generated.d.ts +49 -0
- package/dist/command-catalog.generated.js +60 -0
- package/dist/commands/bot.js +200 -29
- package/dist/commands/signals-run.d.ts +1 -0
- package/dist/commands/signals-run.js +5 -0
- package/dist/commands/sources.d.ts +47 -0
- package/dist/commands/sources.js +157 -1
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +35 -2
- package/dist/lib/bot/fleet-local.d.ts +3 -1
- package/dist/lib/bot/fleet-local.js +12 -11
- package/dist/lib/bot/legacy-convert.d.ts +15 -0
- package/dist/lib/bot/legacy-convert.js +38 -0
- package/dist/lib/hot-channels/loop.d.ts +6 -0
- package/dist/lib/hot-channels/loop.js +1 -0
- package/dist/lib/hot-channels/signals-pass.d.ts +33 -2
- package/dist/lib/hot-channels/signals-pass.js +142 -33
- package/dist/lib/hot-channels/signals-redo.d.ts +60 -0
- package/dist/lib/hot-channels/signals-redo.js +207 -0
- package/dist/lib/signals/engine.d.ts +6 -0
- package/dist/lib/signals/engine.js +10 -1
- package/dist/lib/signals/steps/extract.d.ts +5 -0
- package/dist/lib/signals/steps/extract.js +30 -2
- package/dist/lib/signals/steps/types.d.ts +5 -0
- package/dist/utils/vault-api.d.ts +1 -0
- package/dist/utils/vault-api.js +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,73 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.195] — 2026-11-03
|
|
6
|
+
- Legacy local bots now move to the HQ Fleet Runtime. A Claude bot moves on
|
|
7
|
+
its next start (`hq bot start`, `hq bot restart`, or its launchd or
|
|
8
|
+
`hq daemon` start at login): it keeps its identity, settings, worker folder
|
|
9
|
+
and scheduled jobs, its legacy runner stops, and its legacy startup agent is
|
|
10
|
+
removed. If the Fleet install fails, the bot runs on its legacy runner that
|
|
11
|
+
time and tries again at its next start. A ChatGPT or Grok bot needs its own
|
|
12
|
+
sign-in on the Fleet Runtime, so it stays on its legacy runner until the
|
|
13
|
+
owner runs `hq bot auth <name>`, which now moves it; a failed sign-in puts it
|
|
14
|
+
back on its legacy runner. Set `HQ_BOT_KEEP_LEGACY_RUNTIME=1` to keep a bot
|
|
15
|
+
on its legacy runner. `hq bot run` on a bot that already moved removes a
|
|
16
|
+
leftover legacy startup agent and exits 0 instead of failing, so launchd
|
|
17
|
+
stops relaunching it.
|
|
18
|
+
- `hq-hook`: the `policy-enforcement-gate` port is now a full native port of the gate at
|
|
19
|
+
hq-core-staging 7a5677db (#1746, sha256 0a5f4849). It answers PreToolUse, PostToolUse and
|
|
20
|
+
UserPromptSubmit with the same exit code, stdout, stderr and state files as the bash gate, on
|
|
21
|
+
macOS and on Windows with Git Bash. The bridge pin moves to that version only, and the old
|
|
22
|
+
allow-only port and its fixture are removed. The port hands a call back to bash (exit 75) only
|
|
23
|
+
when the gate's `hook-lib.sh` or `session-id.sh` is not the pinned version, or a host program
|
|
24
|
+
it needs is missing. The port flag stays off.
|
|
25
|
+
- `hq-hook`: in the gate port, a one-time override token is removed only once the native answer stands; a hand-back (exit 75) or a `shadow` run leaves it for bash. Under Git Bash a project root in MSYS form, or a hook bash that is not Git Bash, goes back to bash, and read receipts follow `pwd -P` spelling.
|
|
26
|
+
- The native hook package release no longer reports a successful npm publish as a
|
|
27
|
+
failure. The download check now waits for npm to serve the exact new version,
|
|
28
|
+
polling every 15 seconds for up to 15 minutes, before it compares the published
|
|
29
|
+
bytes with the tested package. A re-run skips the publish when npm already has
|
|
30
|
+
that version with the same integrity, and fails with a clear message when the
|
|
31
|
+
published bytes differ.
|
|
32
|
+
- Local bots now install HQ Fleet Local 0.4.21 on HQ Fleet Runtime main 24211b85.
|
|
33
|
+
The owner's own DMs run commands without approval prompts, while teammate, job
|
|
34
|
+
and email turns keep prompting unless the bot was created with `--auto-approve`.
|
|
35
|
+
A bot reads up to four HQ DM inbox items at once, so one slow request no longer
|
|
36
|
+
holds up the rest, and a restart no longer replays messages it already handled.
|
|
37
|
+
Existing Fleet bots move to 0.4.21 at their next start.
|
|
38
|
+
|
|
39
|
+
## [5.345.194] — 2026-11-03
|
|
40
|
+
- Meeting and other hot-channel documents that land over the signals pass's
|
|
41
|
+
20-document limit now get signals on a later run. Before this fix, a large
|
|
42
|
+
refresh (for example 40 meetings at once) left everything past the first 20
|
|
43
|
+
without signals for good. Each run now works through the channel's backlog,
|
|
44
|
+
newest first, up to 20 documents, even when the run landed nothing new. For
|
|
45
|
+
a channel with an ontology worklist the backlog is the worklist; for a hot
|
|
46
|
+
channel without one (such as meeting pointers) the pass keeps its own queue
|
|
47
|
+
in `sources/_signals-pending/`. When there is nothing to process, claude is
|
|
48
|
+
not called.
|
|
49
|
+
- New `hq sources signals --company <co> --since <date>` processes landed
|
|
50
|
+
documents from that date on that never got signals, and `--key
|
|
51
|
+
sources/<channel>/<id>.md` (repeatable) processes one again. The 20-document
|
|
52
|
+
limit still applies (`--max-docs` changes it); what it leaves stays queued
|
|
53
|
+
for the next run. `--dry-run` lists what would be queued.
|
|
54
|
+
- `hq signals run --company <co> --redo sources/<channel>/<file>.md` (can be
|
|
55
|
+
repeated) processes an ontology source item again. It needs the ontology
|
|
56
|
+
worklist script with `--redo` (hq-core-staging #1781).
|
|
57
|
+
- The six native hook packages (one per platform, `@indigoai-us/hq-hook-win32-x64`
|
|
58
|
+
and siblings) now publish to npm through OIDC trusted publishing without a
|
|
59
|
+
provenance statement. The v5.345.193 release failed every hook package job
|
|
60
|
+
because npm rejects provenance from a private source repository (E422), and
|
|
61
|
+
hq-cli is private. The main `@indigoai-us/hq-cli` package already publishes
|
|
62
|
+
this way.
|
|
63
|
+
- `hq whoami --json` now reports the signed-in person's `personUid` (`prs_*`).
|
|
64
|
+
Person sign-in tokens carry no entity claims, so the field was always null
|
|
65
|
+
for people, and the Daily Brief read only the email-keyed signals folder and
|
|
66
|
+
missed each company's signals for that person. whoami now asks the vault for
|
|
67
|
+
the caller's person entity, the same lookup `hq secrets --personal` uses,
|
|
68
|
+
bounded to 1 second. When the lookup fails or times out, `personUid` stays
|
|
69
|
+
null and the rest of the output is unchanged. Machine (agent) identities and
|
|
70
|
+
expired sessions make no lookup.
|
|
71
|
+
|
|
5
72
|
## [5.345.193] — 2026-11-03
|
|
6
73
|
- HQ's setup assistant answers the desktop app's end-of-setup handoff as the
|
|
7
74
|
assistant it is: who it is and what it can help with (questions, finding
|
|
@@ -5335,6 +5335,51 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5335
5335
|
readonly defaultValue: "<runtime-path>";
|
|
5336
5336
|
}];
|
|
5337
5337
|
readonly subcommands: readonly [];
|
|
5338
|
+
}, {
|
|
5339
|
+
readonly name: "signals";
|
|
5340
|
+
readonly description: "Process landed sources for signals again: queue them for the signals pass and run it now (the pass's document cap still applies)";
|
|
5341
|
+
readonly aliases: readonly [];
|
|
5342
|
+
readonly hidden: false;
|
|
5343
|
+
readonly helpOption: true;
|
|
5344
|
+
readonly helpCommand: false;
|
|
5345
|
+
readonly usage: "[options]";
|
|
5346
|
+
readonly helpText: readonly [];
|
|
5347
|
+
readonly arguments: readonly [];
|
|
5348
|
+
readonly options: readonly [{
|
|
5349
|
+
readonly flags: "--company <slug>";
|
|
5350
|
+
readonly description: "Company slug (defaults to .hq/config.json activeCompany)";
|
|
5351
|
+
readonly hidden: false;
|
|
5352
|
+
}, {
|
|
5353
|
+
readonly flags: "--since <date>";
|
|
5354
|
+
readonly description: "Landed documents from this date on (YYYY-MM-DD is local midnight) that have no signals yet";
|
|
5355
|
+
readonly hidden: false;
|
|
5356
|
+
}, {
|
|
5357
|
+
readonly flags: "--key <key>";
|
|
5358
|
+
readonly description: "A landed document to process again, signals or not (sources/<channel>/<id>.md; repeatable)";
|
|
5359
|
+
readonly hidden: false;
|
|
5360
|
+
}, {
|
|
5361
|
+
readonly flags: "--channel <channel>";
|
|
5362
|
+
readonly description: "Limit --since to one channel";
|
|
5363
|
+
readonly hidden: false;
|
|
5364
|
+
}, {
|
|
5365
|
+
readonly flags: "--max-docs <n>";
|
|
5366
|
+
readonly description: "Documents sent to claude in this run (default 20)";
|
|
5367
|
+
readonly hidden: false;
|
|
5368
|
+
}, {
|
|
5369
|
+
readonly flags: "--dry-run";
|
|
5370
|
+
readonly description: "Print what would be queued; queue nothing and call no model";
|
|
5371
|
+
readonly hidden: false;
|
|
5372
|
+
}, {
|
|
5373
|
+
readonly flags: "--json";
|
|
5374
|
+
readonly description: "Print the report as JSON";
|
|
5375
|
+
readonly hidden: false;
|
|
5376
|
+
}, {
|
|
5377
|
+
readonly flags: "--hq-root <path>";
|
|
5378
|
+
readonly description: "Local HQ tree root (default: <runtime-path>)";
|
|
5379
|
+
readonly hidden: false;
|
|
5380
|
+
readonly defaultValue: "<runtime-path>";
|
|
5381
|
+
}];
|
|
5382
|
+
readonly subcommands: readonly [];
|
|
5338
5383
|
}, {
|
|
5339
5384
|
readonly name: "sync";
|
|
5340
5385
|
readonly description: "Run every hot channel now: each adapter once per connection you can use";
|
|
@@ -5540,6 +5585,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5540
5585
|
readonly flags: "--report-found";
|
|
5541
5586
|
readonly description: "Print the owner's run reply only when the run found new signals; print nothing otherwise";
|
|
5542
5587
|
readonly hidden: false;
|
|
5588
|
+
}, {
|
|
5589
|
+
readonly flags: "--redo <file>";
|
|
5590
|
+
readonly description: "Process a source item again (sources/<channel>/<file>.md; repeatable; needs --company): the extract step re-queues it in the ontology worklist before listing";
|
|
5591
|
+
readonly hidden: false;
|
|
5543
5592
|
}, {
|
|
5544
5593
|
readonly flags: "--hq-root <path>";
|
|
5545
5594
|
readonly description: "HQ root (default: <runtime-path>)";
|
|
@@ -6471,6 +6471,61 @@ export const COMMAND_CATALOG = [
|
|
|
6471
6471
|
],
|
|
6472
6472
|
"subcommands": []
|
|
6473
6473
|
},
|
|
6474
|
+
{
|
|
6475
|
+
"name": "signals",
|
|
6476
|
+
"description": "Process landed sources for signals again: queue them for the signals pass and run it now (the pass's document cap still applies)",
|
|
6477
|
+
"aliases": [],
|
|
6478
|
+
"hidden": false,
|
|
6479
|
+
"helpOption": true,
|
|
6480
|
+
"helpCommand": false,
|
|
6481
|
+
"usage": "[options]",
|
|
6482
|
+
"helpText": [],
|
|
6483
|
+
"arguments": [],
|
|
6484
|
+
"options": [
|
|
6485
|
+
{
|
|
6486
|
+
"flags": "--company <slug>",
|
|
6487
|
+
"description": "Company slug (defaults to .hq/config.json activeCompany)",
|
|
6488
|
+
"hidden": false
|
|
6489
|
+
},
|
|
6490
|
+
{
|
|
6491
|
+
"flags": "--since <date>",
|
|
6492
|
+
"description": "Landed documents from this date on (YYYY-MM-DD is local midnight) that have no signals yet",
|
|
6493
|
+
"hidden": false
|
|
6494
|
+
},
|
|
6495
|
+
{
|
|
6496
|
+
"flags": "--key <key>",
|
|
6497
|
+
"description": "A landed document to process again, signals or not (sources/<channel>/<id>.md; repeatable)",
|
|
6498
|
+
"hidden": false
|
|
6499
|
+
},
|
|
6500
|
+
{
|
|
6501
|
+
"flags": "--channel <channel>",
|
|
6502
|
+
"description": "Limit --since to one channel",
|
|
6503
|
+
"hidden": false
|
|
6504
|
+
},
|
|
6505
|
+
{
|
|
6506
|
+
"flags": "--max-docs <n>",
|
|
6507
|
+
"description": "Documents sent to claude in this run (default 20)",
|
|
6508
|
+
"hidden": false
|
|
6509
|
+
},
|
|
6510
|
+
{
|
|
6511
|
+
"flags": "--dry-run",
|
|
6512
|
+
"description": "Print what would be queued; queue nothing and call no model",
|
|
6513
|
+
"hidden": false
|
|
6514
|
+
},
|
|
6515
|
+
{
|
|
6516
|
+
"flags": "--json",
|
|
6517
|
+
"description": "Print the report as JSON",
|
|
6518
|
+
"hidden": false
|
|
6519
|
+
},
|
|
6520
|
+
{
|
|
6521
|
+
"flags": "--hq-root <path>",
|
|
6522
|
+
"description": "Local HQ tree root (default: <runtime-path>)",
|
|
6523
|
+
"hidden": false,
|
|
6524
|
+
"defaultValue": "<runtime-path>"
|
|
6525
|
+
}
|
|
6526
|
+
],
|
|
6527
|
+
"subcommands": []
|
|
6528
|
+
},
|
|
6474
6529
|
{
|
|
6475
6530
|
"name": "sync",
|
|
6476
6531
|
"description": "Run every hot channel now: each adapter once per connection you can use",
|
|
@@ -6728,6 +6783,11 @@ export const COMMAND_CATALOG = [
|
|
|
6728
6783
|
"description": "Print the owner's run reply only when the run found new signals; print nothing otherwise",
|
|
6729
6784
|
"hidden": false
|
|
6730
6785
|
},
|
|
6786
|
+
{
|
|
6787
|
+
"flags": "--redo <file>",
|
|
6788
|
+
"description": "Process a source item again (sources/<channel>/<file>.md; repeatable; needs --company): the extract step re-queues it in the ontology worklist before listing",
|
|
6789
|
+
"hidden": false
|
|
6790
|
+
},
|
|
6731
6791
|
{
|
|
6732
6792
|
"flags": "--hq-root <path>",
|
|
6733
6793
|
"description": "HQ root (default: <runtime-path>)",
|
package/dist/commands/bot.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { withLocalJobs } from "../lib/bot/local-jobs.js";
|
|
2
|
+
import { fleetMoveFailurePath, fleetMoveRecentlyFailed, legacyConversionDecision, writeFleetMoveFailure } from "../lib/bot/legacy-convert.js";
|
|
2
3
|
import { runSignals } from "../lib/signals/engine.js";
|
|
3
4
|
import { BOT_SIGNALS_ENGINE_JOB_FLAG, signalsEngineForJob } from "../lib/bot/signals-engine-flag.js";
|
|
4
5
|
import { resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
@@ -505,11 +506,14 @@ function stopProcess(dir) {
|
|
|
505
506
|
* restore brings several bots back and its own JSON must be the only document
|
|
506
507
|
* on stdout (the desktop app parses it whole).
|
|
507
508
|
*/
|
|
508
|
-
async function startBot(name, dir, config, output, extra = {}) {
|
|
509
|
+
async function startBot(name, dir, config, output, extra = {}, opts = {}) {
|
|
509
510
|
if (hasPromotionHold(dir))
|
|
510
511
|
throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
|
|
511
512
|
if (isFleetRuntimeBot(config))
|
|
512
513
|
return startFleetRuntimeBot(name, dir, config, output, extra);
|
|
514
|
+
const converted = opts.keepLegacy ? null : await convertLegacyBotToFleet(name, dir, config, "start");
|
|
515
|
+
if (converted)
|
|
516
|
+
return reportFleetBotStart(name, dir, converted, output, { ...extra, movedToFleet: true }, true, "started by the HQ Fleet Runtime");
|
|
513
517
|
if (config.enabled === false)
|
|
514
518
|
patchBotConfig(dir, { enabled: true });
|
|
515
519
|
const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
|
|
@@ -571,6 +575,102 @@ async function startBot(name, dir, config, output, extra = {}) {
|
|
|
571
575
|
}
|
|
572
576
|
return { running, how };
|
|
573
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* Move a legacy bot onto the HQ Fleet Runtime under the same identity.
|
|
580
|
+
* Returns the bot's new settings, or null when it stays on the legacy runner
|
|
581
|
+
* (it needs its own sign-in, the move is turned off, or the move failed, in
|
|
582
|
+
* which case the next start tries again).
|
|
583
|
+
*
|
|
584
|
+
* bot.json switches to Fleet only after the Fleet service is installed, so a
|
|
585
|
+
* failure or a kill at any earlier step leaves a legacy bot that starts as
|
|
586
|
+
* before. Legacy jobs move and the legacy startup agent goes after that.
|
|
587
|
+
*
|
|
588
|
+
* `from: "start"` stops the legacy runner first; a failed move leaves it
|
|
589
|
+
* stopped and the caller starts it again. `from: "run"` is the legacy runner
|
|
590
|
+
* itself starting (launchd, hq daemon or a detached run), before it polls:
|
|
591
|
+
* its own startup agent is booted out last, detached.
|
|
592
|
+
*/
|
|
593
|
+
async function convertLegacyBotToFleet(name, dir, config, from) {
|
|
594
|
+
if (!legacyConversionDecision(config).convert || hasPromotionHold(dir))
|
|
595
|
+
return null;
|
|
596
|
+
// A launchd or daemon restart loop would otherwise retry a failing install before every start.
|
|
597
|
+
if (from === "run" && fleetMoveRecentlyFailed(dir))
|
|
598
|
+
return null;
|
|
599
|
+
if (from === "start") {
|
|
600
|
+
const running = readBotStatus(dir);
|
|
601
|
+
if (running && isPidAlive(running.pid) && selfStopProbe(running.pid))
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
const log = createBotLogger(dir);
|
|
605
|
+
const keepLegacy = (step, err) => {
|
|
606
|
+
writeFleetMoveFailure(dir);
|
|
607
|
+
log("warn", `could not ${step} ${name} on the HQ Fleet Runtime (${err instanceof Error ? err.message : String(err)}); it runs on ${config.runtime} this time and tries again at its next start`);
|
|
608
|
+
};
|
|
609
|
+
let engine;
|
|
610
|
+
try {
|
|
611
|
+
engine = await installFleetRuntimeProfile(name, dir, config, { force: true, keepCredentials: true });
|
|
612
|
+
if (!engine)
|
|
613
|
+
throw new Error("no HQ Fleet Local release was available");
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
keepLegacy("install", err);
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
const fleet = { ...(readBotConfig(dir) ?? config), engine, enabled: true, fleetServiceInstallPending: false };
|
|
620
|
+
if (from === "start")
|
|
621
|
+
await stopBot(name, dir, config, "silent");
|
|
622
|
+
let failure = null;
|
|
623
|
+
try {
|
|
624
|
+
refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, fleet));
|
|
625
|
+
const result = controlFleetLocalBot(config.agentUid, "install");
|
|
626
|
+
if (result.ok !== true)
|
|
627
|
+
failure = new Error(typeof result.summary === "string" ? result.summary : "the service did not install");
|
|
628
|
+
}
|
|
629
|
+
catch (err) {
|
|
630
|
+
failure = err;
|
|
631
|
+
}
|
|
632
|
+
if (failure !== null) {
|
|
633
|
+
try {
|
|
634
|
+
controlFleetLocalBot(config.agentUid, "stop");
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
/* nothing was running */
|
|
638
|
+
}
|
|
639
|
+
keepLegacy("start", failure);
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
writeBotConfig(dir, fleet);
|
|
643
|
+
fs.rmSync(fleetMoveFailurePath(dir), { force: true });
|
|
644
|
+
// The legacy runner's copy of the machine credentials goes only now: a failed move above still needs it.
|
|
645
|
+
fs.rmSync(botCredsPath(dir), { force: true });
|
|
646
|
+
log("info", `moved ${name} to the HQ Fleet Runtime under the same identity; its ${config.runtime} runner is retired`);
|
|
647
|
+
syncFleetRuntimeJobs(name, dir, fleet);
|
|
648
|
+
retireLegacyBotAgent(name, dir, config, from === "run" ? "detached" : "inline");
|
|
649
|
+
return fleet;
|
|
650
|
+
}
|
|
651
|
+
/** Remove a legacy bot's own startup agent once the bot runs on the HQ Fleet Runtime. True when there was one. */
|
|
652
|
+
function retireLegacyBotAgent(name, dir, config, bootout) {
|
|
653
|
+
const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
|
|
654
|
+
if (!fs.existsSync(paths.plistPath) && !fs.existsSync(paths.systemdUnitPath))
|
|
655
|
+
return false;
|
|
656
|
+
const log = createBotLogger(dir);
|
|
657
|
+
const systemd = process.platform === "linux" && fs.existsSync(paths.systemdUnitPath);
|
|
658
|
+
if (systemd)
|
|
659
|
+
systemctlUser(["disable", path.basename(paths.systemdUnitPath)]);
|
|
660
|
+
uninstallBotDaemon(paths, { ...daemonDeps(), bootout, announce: (r) => log("info", `${r.message}: ${name} runs under the HQ Fleet Runtime service now`) });
|
|
661
|
+
if (systemd)
|
|
662
|
+
systemctlUser(["daemon-reload"]);
|
|
663
|
+
return true;
|
|
664
|
+
}
|
|
665
|
+
/** Best effort: the systemd user unit is an optional extra the owner may never have enabled. */
|
|
666
|
+
function systemctlUser(args) {
|
|
667
|
+
try {
|
|
668
|
+
execFileSync("systemctl", ["--user", ...args], { stdio: "ignore", timeout: 10_000 });
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
/* not enabled, or no user systemd */
|
|
672
|
+
}
|
|
673
|
+
}
|
|
574
674
|
/** Start (or restart) an HQ Fleet Runtime bot under its own service, with fresh instructions. */
|
|
575
675
|
async function startFleetRuntimeBot(name, dir, config, output, extra) {
|
|
576
676
|
if (config.fleetAuthPending === true) {
|
|
@@ -591,7 +691,11 @@ async function startFleetRuntimeBot(name, dir, config, output, extra) {
|
|
|
591
691
|
if (installService && result.ok)
|
|
592
692
|
patchBotConfig(dir, { fleetServiceInstallPending: false, enabled: true });
|
|
593
693
|
const how = alreadyRunning ? "restarted by the HQ Fleet Runtime" : "started by the HQ Fleet Runtime";
|
|
594
|
-
|
|
694
|
+
return reportFleetBotStart(name, dir, config, output, extra, result.ok === true, how);
|
|
695
|
+
}
|
|
696
|
+
/** Wait for a just-started HQ Fleet gateway to report running, and print the outcome. */
|
|
697
|
+
async function reportFleetBotStart(name, dir, config, output, extra, started, how) {
|
|
698
|
+
const running = started && (await waitFor(() => {
|
|
595
699
|
const s = readBotStatus(dir);
|
|
596
700
|
return !!s && s.state === "running" && isPidAlive(s.pid);
|
|
597
701
|
}, 45_000));
|
|
@@ -609,11 +713,24 @@ async function startFleetRuntimeBot(name, dir, config, output, extra) {
|
|
|
609
713
|
return { running, how };
|
|
610
714
|
}
|
|
611
715
|
async function adoptFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
716
|
+
const engine = await installFleetRuntimeProfile(name, dir, config, opts);
|
|
717
|
+
if (!engine)
|
|
718
|
+
return false;
|
|
719
|
+
patchBotConfig(dir, { engine });
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Install or refresh the bot's HQ Fleet profile and return the engine it now
|
|
724
|
+
* runs, without touching bot.json. Null when nothing was installed. `force`
|
|
725
|
+
* rebuilds a profile that already matches the release (a legacy bot whose
|
|
726
|
+
* earlier move did not finish).
|
|
727
|
+
*/
|
|
728
|
+
async function installFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
612
729
|
const target = opts.pointerOnly ? await resolveFleetLocalPointerTarget() : await resolveFleetLocalTargetRelease();
|
|
613
730
|
if (!target)
|
|
614
|
-
return
|
|
615
|
-
if (!fleetLocalProfileNeedsAdoption(config.agentUid, { release: target.release }))
|
|
616
|
-
return
|
|
731
|
+
return null;
|
|
732
|
+
if (!opts.force && !fleetLocalProfileNeedsAdoption(config.agentUid, { release: target.release }))
|
|
733
|
+
return null;
|
|
617
734
|
let installation;
|
|
618
735
|
try {
|
|
619
736
|
installation = await installTargetFleetLocalDistribution({ target });
|
|
@@ -623,11 +740,11 @@ async function adoptFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
|
623
740
|
throw err;
|
|
624
741
|
if (opts.pointerOnly) {
|
|
625
742
|
createBotLogger(dir)("warn", `could not install HQ Fleet Local ${target.release.version} (${err instanceof Error ? err.message : String(err)}); the bot stays on its current release`);
|
|
626
|
-
return
|
|
743
|
+
return null;
|
|
627
744
|
}
|
|
628
745
|
createBotLogger(dir)("warn", `could not install HQ Fleet Local ${target.release.version} (${err instanceof Error ? err.message : String(err)}); staying on ${HQ_FLEET_LOCAL_RELEASE.version}`);
|
|
629
|
-
if (!fleetLocalProfileNeedsAdoption(config.agentUid, { release: HQ_FLEET_LOCAL_RELEASE }))
|
|
630
|
-
return
|
|
746
|
+
if (!opts.force && !fleetLocalProfileNeedsAdoption(config.agentUid, { release: HQ_FLEET_LOCAL_RELEASE }))
|
|
747
|
+
return null;
|
|
631
748
|
installation = await installPinnedFleetLocalDistribution();
|
|
632
749
|
}
|
|
633
750
|
const python = await ensureFleetPython();
|
|
@@ -645,11 +762,10 @@ async function adoptFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
|
645
762
|
ownerCompanies: companyScope,
|
|
646
763
|
...(config.autoApprove === true ? { autoApprove: true } : {}),
|
|
647
764
|
}, { python, start: false });
|
|
648
|
-
patchBotConfig(dir, { engine: installation.engine });
|
|
649
765
|
const credentials = botCredsPath(dir);
|
|
650
766
|
if (fs.existsSync(credentials))
|
|
651
|
-
importFleetLocalMachineCredential(config.agentUid, credentials);
|
|
652
|
-
return
|
|
767
|
+
importFleetLocalMachineCredential(config.agentUid, credentials, {}, { keepSource: opts.keepCredentials === true });
|
|
768
|
+
return installation.engine;
|
|
653
769
|
}
|
|
654
770
|
/**
|
|
655
771
|
* `output`: "json" | "text" print a result; "silent" prints nothing — used
|
|
@@ -1393,6 +1509,10 @@ async function runBotResident(nameArg) {
|
|
|
1393
1509
|
const name = validateBotName(nameArg);
|
|
1394
1510
|
const dir = botDir(name);
|
|
1395
1511
|
const config = readBotConfig(dir);
|
|
1512
|
+
// A legacy startup agent left behind after the move to Fleet: exiting
|
|
1513
|
+
// non-zero would have launchd relaunch it forever. Remove it and exit 0.
|
|
1514
|
+
if (config && isFleetRuntimeBot(config) && retireLegacyBotAgent(name, dir, config, "detached"))
|
|
1515
|
+
return;
|
|
1396
1516
|
if (config && isFleetRuntimeBot(config)) {
|
|
1397
1517
|
throw Object.assign(new Error(`${name} runs on the HQ Fleet Runtime under its own background service. Use hq bot start ${name}, hq bot status ${name} or hq bot logs ${name}.`), { expected: true });
|
|
1398
1518
|
}
|
|
@@ -1420,6 +1540,8 @@ async function runBotResident(nameArg) {
|
|
|
1420
1540
|
console.log(orphanBotRunMessage(name, outcome));
|
|
1421
1541
|
return;
|
|
1422
1542
|
}
|
|
1543
|
+
if (await convertLegacyBotToFleet(name, dir, config, "run"))
|
|
1544
|
+
return;
|
|
1423
1545
|
if (healNodePathFromJob(name, dir, config))
|
|
1424
1546
|
return;
|
|
1425
1547
|
const token = botTokenSupplier(dir);
|
|
@@ -2273,35 +2395,84 @@ export async function runBotRestart(nameArg, opts = {}) {
|
|
|
2273
2395
|
}
|
|
2274
2396
|
async function runBotAuth(nameArg, opts) {
|
|
2275
2397
|
const b = requireBot(nameArg);
|
|
2276
|
-
if (!isFleetRuntimeBot(b.config)) {
|
|
2277
|
-
throw Object.assign(new Error(`hq bot auth is for bots created with --hq-fleet-runtime. ${b.name} uses ${b.config.runtime}.`), { expected: true });
|
|
2278
|
-
}
|
|
2279
2398
|
const apiKey = opts.apiKey ? fs.readFileSync(0, "utf8").trim() : undefined;
|
|
2280
2399
|
if (opts.apiKey && !apiKey) {
|
|
2281
2400
|
throw Object.assign(new Error("No API key was received on stdin."), { expected: true });
|
|
2282
2401
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2402
|
+
// A legacy bot moves to the HQ Fleet Runtime as part of its sign-in. Its
|
|
2403
|
+
// legacy runner stops for the sign-in and comes back if the sign-in fails.
|
|
2404
|
+
const legacy = isFleetRuntimeBot(b.config) ? null : b.config;
|
|
2405
|
+
if (legacy) {
|
|
2406
|
+
refuseHeldFleetBot(b.dir);
|
|
2407
|
+
const running = readBotStatus(b.dir);
|
|
2408
|
+
if (running && isPidAlive(running.pid) && selfStopProbe(running.pid)) {
|
|
2409
|
+
throw Object.assign(new Error(`${b.name} cannot move itself to the HQ Fleet Runtime from inside one of its own turns. Run hq bot auth ${b.name} --provider ${opts.provider} from outside the bot.`), { expected: true });
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
// bot.json stays legacy until the sign-in succeeds, so an interrupted sign-in leaves a legacy bot.
|
|
2413
|
+
const engine = legacy ? await installFleetRuntimeProfile(b.name, b.dir, legacy, { force: true, keepCredentials: true }) : null;
|
|
2414
|
+
if (legacy && !engine) {
|
|
2415
|
+
throw Object.assign(new Error(`No HQ Fleet Local release could be installed for ${b.name}; it stays on ${legacy.runtime}.`), { expected: true });
|
|
2416
|
+
}
|
|
2417
|
+
const adopted = legacy ? true : await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
|
|
2418
|
+
if (legacy)
|
|
2419
|
+
await stopBot(b.name, b.dir, legacy, "silent");
|
|
2420
|
+
// Ctrl-C reaches the sign-in helper too; keep this process alive so the rollback below runs.
|
|
2421
|
+
const keepAliveOnInterrupt = () => undefined;
|
|
2422
|
+
if (legacy)
|
|
2423
|
+
process.on("SIGINT", keepAliveOnInterrupt);
|
|
2424
|
+
let result;
|
|
2425
|
+
try {
|
|
2426
|
+
result = authenticateFleetLocalBot({
|
|
2427
|
+
agentUid: b.config.agentUid,
|
|
2428
|
+
provider: opts.provider,
|
|
2429
|
+
...(adopted || legacy || b.config.fleetAuthPending === true ? { installService: true } : {}),
|
|
2430
|
+
...(opts.model ? { model: opts.model } : {}),
|
|
2431
|
+
...(apiKey ? { apiKey } : {}),
|
|
2432
|
+
...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
|
|
2433
|
+
...(opts.transport ? { transport: opts.transport } : {}),
|
|
2434
|
+
openBrowser: opts.browser !== false,
|
|
2435
|
+
});
|
|
2436
|
+
if (result.authenticated !== true)
|
|
2437
|
+
throw Object.assign(new Error("The provider has not completed sign-in."), { expected: true });
|
|
2438
|
+
if (legacy && result.restarted !== true) {
|
|
2439
|
+
throw Object.assign(new Error(`${b.name} signed in, but its HQ Fleet service did not start; it stays on ${legacy.runtime}. Run hq bot auth ${b.name} --provider ${opts.provider} again.`), { expected: true });
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
catch (err) {
|
|
2443
|
+
if (legacy) {
|
|
2444
|
+
try {
|
|
2445
|
+
controlFleetLocalBot(legacy.agentUid, "stop");
|
|
2446
|
+
}
|
|
2447
|
+
catch {
|
|
2448
|
+
/* nothing was running */
|
|
2449
|
+
}
|
|
2450
|
+
await startBot(b.name, b.dir, legacy, "silent", {}, { keepLegacy: true }).catch(() => undefined);
|
|
2451
|
+
}
|
|
2452
|
+
throw err;
|
|
2453
|
+
}
|
|
2454
|
+
finally {
|
|
2455
|
+
if (legacy)
|
|
2456
|
+
process.off("SIGINT", keepAliveOnInterrupt);
|
|
2457
|
+
}
|
|
2458
|
+
if (legacy && engine) {
|
|
2459
|
+
const moved = { ...(readBotConfig(b.dir) ?? legacy), engine };
|
|
2460
|
+
writeBotConfig(b.dir, moved);
|
|
2461
|
+
fs.rmSync(botCredsPath(b.dir), { force: true });
|
|
2462
|
+
createBotLogger(b.dir)("info", `moved ${b.name} to the HQ Fleet Runtime with its ${opts.provider} sign-in; its ${legacy.runtime} runner is retired`);
|
|
2463
|
+
syncFleetRuntimeJobs(b.name, b.dir, moved);
|
|
2464
|
+
retireLegacyBotAgent(b.name, b.dir, legacy, "inline");
|
|
2465
|
+
}
|
|
2294
2466
|
const signedInRuntime = result.runtimeProvider === "anthropic" ? "claude" : result.runtimeProvider === "xai-oauth" ? "grok" : result.runtimeProvider === "openai-codex" ? "codex" : null;
|
|
2295
|
-
|
|
2296
|
-
throw Object.assign(new Error("The provider has not completed sign-in."), { expected: true });
|
|
2467
|
+
const pending = b.config.fleetAuthPending === true || legacy !== null;
|
|
2297
2468
|
patchBotConfig(b.dir, {
|
|
2298
2469
|
...(signedInRuntime ? { runtime: signedInRuntime } : {}),
|
|
2299
2470
|
model: result.model,
|
|
2300
|
-
...(
|
|
2471
|
+
...(pending ? { fleetAuthPending: false, fleetServiceInstallPending: !result.restarted, enabled: result.restarted } : {}),
|
|
2301
2472
|
});
|
|
2302
2473
|
const running = await fleetGatewayComesUp(b.config.agentUid, result.restarted ? fleetStartWaitMs() : 0);
|
|
2303
2474
|
if (opts.json) {
|
|
2304
|
-
console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result, needsSignIn: false, running, serviceInstallPending:
|
|
2475
|
+
console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result, needsSignIn: false, running, serviceInstallPending: pending && !result.restarted, ...(legacy ? { movedToFleet: true } : {}) }, null, 2));
|
|
2305
2476
|
return;
|
|
2306
2477
|
}
|
|
2307
2478
|
console.log(running
|
|
@@ -19,6 +19,9 @@ function parsePositiveInt(v) {
|
|
|
19
19
|
throw new InvalidArgumentError("must be a positive integer");
|
|
20
20
|
return n;
|
|
21
21
|
}
|
|
22
|
+
function collectRedo(v, prev = []) {
|
|
23
|
+
return [...prev, v];
|
|
24
|
+
}
|
|
22
25
|
function parseNonNegative(v) {
|
|
23
26
|
const n = Number(v);
|
|
24
27
|
if (!Number.isFinite(n) || n < 0)
|
|
@@ -113,6 +116,7 @@ export async function runSignalsCli(options, overrides = {}) {
|
|
|
113
116
|
catchUp: options.catchUp === true || envCatchUp(),
|
|
114
117
|
maxTurns: options.maxTurns,
|
|
115
118
|
maxBudgetUsd: options.maxBudgetUsd,
|
|
119
|
+
...(options.redo?.length ? { redo: options.redo } : {}),
|
|
116
120
|
signalsAlert: overrides.signalsAlert ?? terminalSignalsAlert(),
|
|
117
121
|
});
|
|
118
122
|
const code = signalsExitCode(result);
|
|
@@ -155,6 +159,7 @@ export function registerSignalsRunSubcommand(signals) {
|
|
|
155
159
|
.option("--max-budget-usd <x>", "Spend cap in USD for the extract step (default from core/settings/signals.yaml, else 2.00)", parseNonNegative)
|
|
156
160
|
.option("--json", "Print the run record as JSON")
|
|
157
161
|
.option("--report-found", "Print the owner's run reply only when the run found new signals; print nothing otherwise")
|
|
162
|
+
.option("--redo <file>", "Process a source item again (sources/<channel>/<file>.md; repeatable; needs --company): the extract step re-queues it in the ontology worklist before listing", collectRedo)
|
|
158
163
|
.option("--hq-root <path>", `HQ root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
|
|
159
164
|
.action(async (options) => {
|
|
160
165
|
try {
|
|
@@ -128,6 +128,53 @@ export declare function runCompanyHotChannelSync(input: CompanyHotChannelSyncInp
|
|
|
128
128
|
* line under `companies/{company}/sources/`, and nothing else.
|
|
129
129
|
*/
|
|
130
130
|
export declare function runSync(options: SyncOptions, overrides?: SyncOverrides): Promise<OnDemandSyncReport>;
|
|
131
|
+
export interface SignalsRedoOptions {
|
|
132
|
+
company?: string;
|
|
133
|
+
since?: string;
|
|
134
|
+
key?: string[];
|
|
135
|
+
channel?: string;
|
|
136
|
+
maxDocs?: number;
|
|
137
|
+
dryRun?: boolean;
|
|
138
|
+
json?: boolean;
|
|
139
|
+
hqRoot: string;
|
|
140
|
+
}
|
|
141
|
+
export interface SignalsRedoOverrides {
|
|
142
|
+
now?: () => Date;
|
|
143
|
+
/** The signed-in person; defaults to the local Cognito session. */
|
|
144
|
+
author?: {
|
|
145
|
+
email: string;
|
|
146
|
+
} | null;
|
|
147
|
+
/** The signals pass; defaults to the local `claude -p` pass with `maxDocs`. */
|
|
148
|
+
createPass?: (maxDocs: number) => SignalsGeneratedFn;
|
|
149
|
+
worklistScript?: string;
|
|
150
|
+
}
|
|
151
|
+
export interface SignalsRedoReport {
|
|
152
|
+
company: string;
|
|
153
|
+
dryRun: boolean;
|
|
154
|
+
/** Keys queued per channel, by how they were queued. */
|
|
155
|
+
queued: {
|
|
156
|
+
pending: Record<string, string[]>;
|
|
157
|
+
worklist: Record<string, string[]>;
|
|
158
|
+
};
|
|
159
|
+
signalsWritten: number;
|
|
160
|
+
/** Keys still waiting in each channel's pending file after the run. */
|
|
161
|
+
pendingLeft: Record<string, number>;
|
|
162
|
+
notices: string[];
|
|
163
|
+
error: string | null;
|
|
164
|
+
exitCode: number;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* `hq sources signals`: queue landed documents for the signals pass and run
|
|
168
|
+
* the pass now, under the same per-person lock as `hq sources sync`. The pass
|
|
169
|
+
* keeps its cap (`--max-docs`, default 20); what the cap leaves stays queued
|
|
170
|
+
* and the next run (this command or the daily sync) picks it up.
|
|
171
|
+
*/
|
|
172
|
+
export declare function runSignalsRedo(options: SignalsRedoOptions, overrides?: SignalsRedoOverrides): Promise<SignalsRedoReport>;
|
|
173
|
+
/** A usage or setup failure `runSignalsRedo` already printed; the exit code is set. */
|
|
174
|
+
export declare class SignalsRedoExit extends Error {
|
|
175
|
+
readonly exitCode: number;
|
|
176
|
+
constructor(message: string, exitCode: number);
|
|
177
|
+
}
|
|
131
178
|
export interface ScheduleOptions {
|
|
132
179
|
company?: string;
|
|
133
180
|
at?: string;
|