@indigoai-us/hq-cli 5.117.0 → 5.117.2
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 +52 -0
- package/assets/bot-workers/setup/context/USER-GUIDE.md +44 -3
- package/assets/bot-workers/setup/context/quick-reference.md +1 -1
- package/dist/command-catalog.generated.d.ts +6 -3
- package/dist/command-catalog.generated.js +7 -3
- package/dist/commands/agent-kit.d.ts +23 -3
- package/dist/commands/agent-kit.js +126 -14
- package/dist/commands/agent-probe.d.ts +15 -9
- package/dist/commands/agent-probe.js +55 -35
- package/dist/commands/billing.js +1 -1
- package/dist/commands/db-provision.js +4 -4
- package/dist/commands/dm.d.ts +10 -0
- package/dist/commands/dm.js +80 -0
- package/dist/commands/meetings.js +2 -2
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +55 -1
- package/dist/lib/agent-kit/fallback.d.ts +63 -0
- package/dist/lib/agent-kit/fallback.js +129 -0
- package/dist/lib/agent-kit/run/inbox.d.ts +18 -6
- package/dist/lib/agent-kit/run/inbox.js +40 -8
- package/dist/lib/agent-kit/run/mesh-listener.d.ts +22 -6
- package/dist/lib/agent-kit/run/mesh-listener.js +55 -10
- package/dist/lib/agent-kit/run/supervisor.d.ts +35 -0
- package/dist/lib/agent-kit/run/supervisor.js +85 -0
- package/dist/lib/agent-kit/skills.js +2 -2
- package/dist/lib/billing/plan-lock.d.ts +99 -0
- package/dist/lib/billing/plan-lock.js +230 -0
- package/dist/lib/mesh/live/daemon/credentials.d.ts +27 -0
- package/dist/lib/mesh/live/daemon/credentials.js +95 -0
- package/dist/lib/plan-limit-nag.js +1 -1
- package/dist/utils/plan-gate-error.js +9 -9
- package/dist/utils/team-upgrade.d.ts +1 -1
- package/dist/utils/team-upgrade.js +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.117.2] — 2026-09-16
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- External agents: the work-mesh listener no longer fails every refresh with
|
|
10
|
+
"Agent callers may only use DM, channel comms, and reaction routes". It now
|
|
11
|
+
re-reads the agent's own inbox on each doorbell instead of the person-only
|
|
12
|
+
conversation routes.
|
|
13
|
+
- External agents: `hq agent probe` no longer waits for a DM to itself to show up
|
|
14
|
+
in the inbox, which the server never delivers. The dm check passes when the
|
|
15
|
+
send is accepted and the agent inbox is readable.
|
|
16
|
+
- External agents: kit service logs no longer write every line twice when the
|
|
17
|
+
services run under a supervisor.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- HQ now calls the paid plan by its real name everywhere you see it. If your
|
|
22
|
+
workspace went over its Starter limit, the notice, the error you got when you
|
|
23
|
+
tried to write something, and the upgrade prompt used to give three different
|
|
24
|
+
names for the same plan — "HQ Workforce", "HQ Team", and "Team" — which made
|
|
25
|
+
it genuinely unclear what you were being asked to buy. They all say HQ
|
|
26
|
+
Workforce now. The free plan is called Starter rather than "free plan", to
|
|
27
|
+
match the pricing page.
|
|
28
|
+
|
|
29
|
+
## [5.117.1] — 2026-09-16
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- `hq agent probe` works for external agents. The work-mesh check no longer
|
|
34
|
+
reads the owner-only company roster (which answered "Not found" to an agent);
|
|
35
|
+
it passes when the realtime credential vend names the agent, the agent's own
|
|
36
|
+
inbox answers, and this host's heartbeat and mesh listener are live. The DM
|
|
37
|
+
check reads the probe message back from the agent's inbox instead of the
|
|
38
|
+
human-only thread view that refused agents. Output format is unchanged.
|
|
39
|
+
- The agent kit's mesh listener stays connected. It now asks for the personal
|
|
40
|
+
realtime credentials that cover the agent's own doorbell topics (the company
|
|
41
|
+
presence credentials it used made the connection drop before subscribing),
|
|
42
|
+
subscribes only to topics those credentials grant, and polls the inbox
|
|
43
|
+
immediately when an inbox doorbell rings.
|
|
44
|
+
- Inbox poll failures are diagnosable: the log names the method, full URL,
|
|
45
|
+
status and the first 300 characters of the response, and the inbox URL keeps
|
|
46
|
+
any base path from `apiBaseUrl` without doubling `/v1`.
|
|
47
|
+
- `hq dm inbox` works for agent identities by reading the agent inbox;
|
|
48
|
+
`hq dm thread` tells agents to use `hq dm inbox` instead of failing with a
|
|
49
|
+
server refusal.
|
|
50
|
+
- `hq agent kit install` works on hosts with no systemd user session or
|
|
51
|
+
launchd: it starts the services under one background `hq agent kit run all`
|
|
52
|
+
supervisor (restarting any service that exits), records its pid in
|
|
53
|
+
`~/.hq-agent/kit.pid`, and prints the `@reboot` crontab line that keeps it
|
|
54
|
+
running after a restart. `kit status` and `kit uninstall` understand this
|
|
55
|
+
mode.
|
|
56
|
+
|
|
5
57
|
## [5.117.0] — 2026-09-16
|
|
6
58
|
|
|
7
59
|
### Added
|
|
@@ -109,9 +109,9 @@ Structured storage for agent and app state — not a replacement for markdown kn
|
|
|
109
109
|
| `hq db status --company {co}` | Create/open local SQLite at `~/.hq/db/{co}/vault.db` (WAL) |
|
|
110
110
|
| `hq db sql --company {co} -- 'SELECT …'` | Query the company local DB (read-only by default) |
|
|
111
111
|
| `hq db migrate --company {co} --hq-root {HQ}` | Apply reviewable SQL under `companies/{co}/db/migrations/` |
|
|
112
|
-
| `hq db provision --company {co}` | Remote DB on **HQ
|
|
112
|
+
| `hq db provision --company {co}` | Remote DB on **HQ Workforce** ($500/mo) or Enterprise only (secrets never printed) |
|
|
113
113
|
|
|
114
|
-
Use local for single-machine agent skills and implementer standards. Use
|
|
114
|
+
Use local for single-machine agent skills and implementer standards. Use the remote DB for multi-machine shared data and deploy SecretBinding. Never commit `*.db` files into the vault tree.
|
|
115
115
|
|
|
116
116
|
### HQ CLI: Direct messages (`hq dm`)
|
|
117
117
|
|
|
@@ -318,6 +318,47 @@ HQ/
|
|
|
318
318
|
└── threads/ # Session threads + handoff.json
|
|
319
319
|
```
|
|
320
320
|
|
|
321
|
+
## Plans and limits
|
|
322
|
+
|
|
323
|
+
HQ has three plans: **Starter**, **HQ Workforce** ($500/month per workspace), and
|
|
324
|
+
**Enterprise**.
|
|
325
|
+
|
|
326
|
+
Starter is free. It holds up to 5 members, and it does not include integrations
|
|
327
|
+
or MCP, Atlas, or telemetry. Everything else — knowledge, projects, policies,
|
|
328
|
+
workers, secrets, vault storage, `hq deploy`, and `hq share` — works the same as
|
|
329
|
+
on the paid plans.
|
|
330
|
+
|
|
331
|
+
### When a workspace goes over
|
|
332
|
+
|
|
333
|
+
A Starter workspace locks as soon as it goes over 5 members. There is no grace
|
|
334
|
+
period and no countdown.
|
|
335
|
+
|
|
336
|
+
A locked workspace is read-only. Nothing is deleted or hidden, and everyone can
|
|
337
|
+
still read everything. Writes are refused until the workspace is back inside the
|
|
338
|
+
limits.
|
|
339
|
+
|
|
340
|
+
There are two ways out, and only the workspace owner can do either:
|
|
341
|
+
|
|
342
|
+
1. Remove members until there are 5 or fewer. Removing someone from a workspace
|
|
343
|
+
does not delete their personal HQ or the work they saved into the workspace.
|
|
344
|
+
2. Upgrade to HQ Workforce at $500/month from the workspace billing page.
|
|
345
|
+
|
|
346
|
+
Either one unlocks the workspace right away.
|
|
347
|
+
|
|
348
|
+
Existing integration connections on a locked Starter workspace are disabled, not
|
|
349
|
+
deleted. Upgrading turns them back on without reconnecting anything.
|
|
350
|
+
|
|
351
|
+
### What you'll see
|
|
352
|
+
|
|
353
|
+
The owner gets one email when the workspace locks, a reminder at most once every
|
|
354
|
+
7 days while it stays locked, and one confirmation when it unlocks. Billing mail
|
|
355
|
+
comes from billing@hqforwork.com, and billing links go to hq.computer, which is
|
|
356
|
+
the console.
|
|
357
|
+
|
|
358
|
+
In the console you'll see a lock banner, an upgrade wall, and a team roster that
|
|
359
|
+
counts members against the limit. In a Claude Code session, `/startwork` shows a
|
|
360
|
+
short notice on every turn while the workspace is over its limits.
|
|
361
|
+
|
|
321
362
|
## Meeting notes, signals & ontology
|
|
322
363
|
|
|
323
364
|
HQ captures these **natively, per company** — check HQ first, not your email or a third-party notetaker.
|
|
@@ -334,7 +375,7 @@ HQ captures these **natively, per company** — check HQ first, not your email o
|
|
|
334
375
|
|
|
335
376
|
**Your preference for "meeting notes":** defaults to HQ-native. To point a company at email instead, set `meeting_notes_source: email` in `companies/{co}/settings/knowledge/preferences.yaml` (global default lives in `personal/settings/knowledge-preferences.yaml`).
|
|
336
377
|
|
|
337
|
-
> Signals extraction and the ontology gardener run on HQ cloud and
|
|
378
|
+
> Signals extraction and the ontology gardener run on HQ cloud and are part of HQ Workforce ($500/mo) and Enterprise. They are not included in the free Starter plan. Cloud-back a company with `/designate-team` to turn them on. Reference: `core/knowledge/public/hq-core/native-knowledge-stores.md`.
|
|
338
379
|
|
|
339
380
|
## Typical Session
|
|
340
381
|
|
|
@@ -148,7 +148,7 @@ Share-session URLs are encrypted single-use 15-minute capabilities — never per
|
|
|
148
148
|
|
|
149
149
|
## CLI: `hq db` (vault databases)
|
|
150
150
|
|
|
151
|
-
Local SQLite per company (always) + optional remote Postgres-class on **HQ
|
|
151
|
+
Local SQLite per company (always, Starter included) + optional remote Postgres-class on **HQ Workforce** ($500/mo) or Enterprise. Guide: `core/knowledge/public/hq-core/vault-databases.md`. Requires `@indigoai-us/hq-cli` ≥ 5.62.0.
|
|
152
152
|
|
|
153
153
|
| Command | Use |
|
|
154
154
|
|---------|-----|
|
|
@@ -800,6 +800,9 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
800
800
|
readonly options: readonly [{
|
|
801
801
|
readonly flags: "--json";
|
|
802
802
|
readonly description: "Output identity metadata as JSON (never tokens)";
|
|
803
|
+
}, {
|
|
804
|
+
readonly flags: "--company <slug>";
|
|
805
|
+
readonly description: "Also report this company's workspace plan state (Starter lock)";
|
|
803
806
|
}];
|
|
804
807
|
readonly subcommands: readonly [];
|
|
805
808
|
}, {
|
|
@@ -1276,7 +1279,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
1276
1279
|
readonly subcommands: readonly [];
|
|
1277
1280
|
}, {
|
|
1278
1281
|
readonly name: "provision";
|
|
1279
|
-
readonly description: "Provision (or re-bind) the company remote vault DB via HQ control plane (
|
|
1282
|
+
readonly description: "Provision (or re-bind) the company remote vault DB via HQ control plane (HQ Workforce plan)";
|
|
1280
1283
|
readonly aliases: readonly [];
|
|
1281
1284
|
readonly hidden: false;
|
|
1282
1285
|
readonly usage: "[options]";
|
|
@@ -4217,7 +4220,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
4217
4220
|
readonly subcommands: readonly [];
|
|
4218
4221
|
}, {
|
|
4219
4222
|
readonly name: "run";
|
|
4220
|
-
readonly description: "Run one kit service in the foreground (sync|mesh|inbox|heartbeat)";
|
|
4223
|
+
readonly description: "Run one kit service in the foreground (sync|mesh|inbox|heartbeat), or `all` to supervise every service in one process";
|
|
4221
4224
|
readonly aliases: readonly [];
|
|
4222
4225
|
readonly hidden: false;
|
|
4223
4226
|
readonly usage: "[options] <service>";
|
|
@@ -4603,7 +4606,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
4603
4606
|
readonly subcommands: readonly [];
|
|
4604
4607
|
}, {
|
|
4605
4608
|
readonly name: "upgrade";
|
|
4606
|
-
readonly description: "Open Stripe Checkout to upgrade this company to HQ
|
|
4609
|
+
readonly description: "Open Stripe Checkout to upgrade this company to HQ Workforce ($500/mo)";
|
|
4607
4610
|
readonly aliases: readonly [];
|
|
4608
4611
|
readonly hidden: false;
|
|
4609
4612
|
readonly usage: "[options]";
|
|
@@ -1021,6 +1021,10 @@ export const COMMAND_CATALOG = [
|
|
|
1021
1021
|
{
|
|
1022
1022
|
"flags": "--json",
|
|
1023
1023
|
"description": "Output identity metadata as JSON (never tokens)"
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
"flags": "--company <slug>",
|
|
1027
|
+
"description": "Also report this company's workspace plan state (Starter lock)"
|
|
1024
1028
|
}
|
|
1025
1029
|
],
|
|
1026
1030
|
"subcommands": []
|
|
@@ -1628,7 +1632,7 @@ export const COMMAND_CATALOG = [
|
|
|
1628
1632
|
},
|
|
1629
1633
|
{
|
|
1630
1634
|
"name": "provision",
|
|
1631
|
-
"description": "Provision (or re-bind) the company remote vault DB via HQ control plane (
|
|
1635
|
+
"description": "Provision (or re-bind) the company remote vault DB via HQ control plane (HQ Workforce plan)",
|
|
1632
1636
|
"aliases": [],
|
|
1633
1637
|
"hidden": false,
|
|
1634
1638
|
"usage": "[options]",
|
|
@@ -5455,7 +5459,7 @@ export const COMMAND_CATALOG = [
|
|
|
5455
5459
|
},
|
|
5456
5460
|
{
|
|
5457
5461
|
"name": "run",
|
|
5458
|
-
"description": "Run one kit service in the foreground (sync|mesh|inbox|heartbeat)",
|
|
5462
|
+
"description": "Run one kit service in the foreground (sync|mesh|inbox|heartbeat), or `all` to supervise every service in one process",
|
|
5459
5463
|
"aliases": [],
|
|
5460
5464
|
"hidden": false,
|
|
5461
5465
|
"usage": "[options] <service>",
|
|
@@ -5951,7 +5955,7 @@ export const COMMAND_CATALOG = [
|
|
|
5951
5955
|
},
|
|
5952
5956
|
{
|
|
5953
5957
|
"name": "upgrade",
|
|
5954
|
-
"description": "Open Stripe Checkout to upgrade this company to HQ
|
|
5958
|
+
"description": "Open Stripe Checkout to upgrade this company to HQ Workforce ($500/mo)",
|
|
5955
5959
|
"aliases": [],
|
|
5956
5960
|
"hidden": false,
|
|
5957
5961
|
"usage": "[options]",
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
* `install` renders one user-level unit per service (LaunchAgent on macOS,
|
|
11
11
|
* systemd --user on Linux), each of which just runs
|
|
12
12
|
* `node hq agent kit run <service>`; the Docker image runs the same four
|
|
13
|
-
* commands.
|
|
13
|
+
* commands. Hosts where the service manager is unusable (no systemd user
|
|
14
|
+
* session bus, permission refused, launchd unavailable) fall back to one
|
|
15
|
+
* detached `hq agent kit run all` supervisor (pid in ~/.hq-agent/kit.pid,
|
|
16
|
+
* output in ~/.hq-agent/logs/kit.log). Logs: ~/.hq-agent/logs/<service>.log. Skills:
|
|
14
17
|
* ~/.hq-agent/skills/<name>/SKILL.md.
|
|
15
18
|
*/
|
|
16
19
|
import { Command } from "commander";
|
|
@@ -19,6 +22,7 @@ import { type KitConfig } from "../lib/agent-kit/kit-config.js";
|
|
|
19
22
|
import { type KitLogger } from "../lib/agent-kit/log.js";
|
|
20
23
|
import { type AgentKitPaths } from "../lib/agent-kit/paths.js";
|
|
21
24
|
import { type KitService } from "../lib/agent-kit/services.js";
|
|
25
|
+
import { type DetachedSpawn, type KillFn } from "../lib/agent-kit/fallback.js";
|
|
22
26
|
import { type ServiceHostPaths, type ServiceManagerDeps, type ServiceSetResult } from "../lib/service-manager/index.js";
|
|
23
27
|
export declare class KitError extends Error {
|
|
24
28
|
constructor(message: string);
|
|
@@ -32,12 +36,25 @@ export interface KitInstallOptions {
|
|
|
32
36
|
inboxAck?: boolean;
|
|
33
37
|
activate?: boolean;
|
|
34
38
|
}
|
|
39
|
+
export interface KitFallbackInfo {
|
|
40
|
+
mode: "fallback";
|
|
41
|
+
pid: number;
|
|
42
|
+
pidFile: string;
|
|
43
|
+
logPath: string;
|
|
44
|
+
/** Add to `crontab -e` so the supervisor survives a reboot. */
|
|
45
|
+
crontab: string;
|
|
46
|
+
}
|
|
35
47
|
export interface KitInstallResult {
|
|
36
48
|
config: KitConfig;
|
|
37
49
|
skills: string[];
|
|
38
50
|
services: ServiceSetResult;
|
|
51
|
+
fallback?: KitFallbackInfo;
|
|
52
|
+
}
|
|
53
|
+
export interface KitFallbackDeps {
|
|
54
|
+
spawn?: DetachedSpawn;
|
|
55
|
+
kill?: KillFn;
|
|
39
56
|
}
|
|
40
|
-
export declare function installKit(paths: AgentKitPaths, opts: KitInstallOptions, host: ServiceHostPaths, deps?: ServiceManagerDeps): KitInstallResult;
|
|
57
|
+
export declare function installKit(paths: AgentKitPaths, opts: KitInstallOptions, host: ServiceHostPaths, deps?: ServiceManagerDeps, fallbackDeps?: KitFallbackDeps): KitInstallResult;
|
|
41
58
|
export declare function formatServiceSet(result: ServiceSetResult): string[];
|
|
42
59
|
/** Shared runtime for `kit run <service>`. */
|
|
43
60
|
export interface KitRuntime {
|
|
@@ -47,7 +64,10 @@ export interface KitRuntime {
|
|
|
47
64
|
getToken: () => Promise<string>;
|
|
48
65
|
log: KitLogger;
|
|
49
66
|
}
|
|
50
|
-
export declare function buildKitRuntime(service: KitService): KitRuntime;
|
|
67
|
+
export declare function buildKitRuntime(service: KitService | "all"): KitRuntime;
|
|
51
68
|
export declare function runKitService(service: KitService, rt: KitRuntime): Promise<void>;
|
|
69
|
+
/** `kit run all`: supervise the four services as child processes with restart backoff. */
|
|
70
|
+
export declare function runAllKitServices(rt: KitRuntime, host: ServiceHostPaths): Promise<void>;
|
|
71
|
+
export declare function formatFallbackStatus(paths: AgentKitPaths, kill?: KillFn): string[] | null;
|
|
52
72
|
export declare function registerAgentKitCommand(agent: Command): void;
|
|
53
73
|
//# sourceMappingURL=agent-kit.d.ts.map
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
* `install` renders one user-level unit per service (LaunchAgent on macOS,
|
|
11
11
|
* systemd --user on Linux), each of which just runs
|
|
12
12
|
* `node hq agent kit run <service>`; the Docker image runs the same four
|
|
13
|
-
* commands.
|
|
13
|
+
* commands. Hosts where the service manager is unusable (no systemd user
|
|
14
|
+
* session bus, permission refused, launchd unavailable) fall back to one
|
|
15
|
+
* detached `hq agent kit run all` supervisor (pid in ~/.hq-agent/kit.pid,
|
|
16
|
+
* output in ~/.hq-agent/logs/kit.log). Logs: ~/.hq-agent/logs/<service>.log. Skills:
|
|
14
17
|
* ~/.hq-agent/skills/<name>/SKILL.md.
|
|
15
18
|
*/
|
|
16
19
|
import chalk from "chalk";
|
|
@@ -25,7 +28,11 @@ import { agentKitPaths } from "../lib/agent-kit/paths.js";
|
|
|
25
28
|
import { isKitService, KIT_SERVICES, kitServiceSpecs, } from "../lib/agent-kit/services.js";
|
|
26
29
|
import { writeKitSkills } from "../lib/agent-kit/skills.js";
|
|
27
30
|
import { runHeartbeatLoop } from "../lib/agent-kit/run/heartbeat.js";
|
|
28
|
-
import { runInboxLoop } from "../lib/agent-kit/run/inbox.js";
|
|
31
|
+
import { pollInboxOnce, runInboxLoop } from "../lib/agent-kit/run/inbox.js";
|
|
32
|
+
import { superviseKitServices } from "../lib/agent-kit/run/supervisor.js";
|
|
33
|
+
import { fallbackStatus, rebootCrontabLine, shouldUseFallback, startFallback, stopFallback, } from "../lib/agent-kit/fallback.js";
|
|
34
|
+
import { serviceLogPath } from "../lib/agent-kit/paths.js";
|
|
35
|
+
import { spawn } from "node:child_process";
|
|
29
36
|
import { startMeshListener } from "../lib/agent-kit/run/mesh-listener.js";
|
|
30
37
|
import { runSyncLoop } from "../lib/agent-kit/run/sync.js";
|
|
31
38
|
import { resolveHqBinary, resolveNodeBinary, } from "../lib/mesh/live/daemon/install.js";
|
|
@@ -57,7 +64,7 @@ export function resolveServiceHost(paths) {
|
|
|
57
64
|
hqBinary: resolveHqBinary(),
|
|
58
65
|
};
|
|
59
66
|
}
|
|
60
|
-
export function installKit(paths, opts, host, deps = {}) {
|
|
67
|
+
export function installKit(paths, opts, host, deps = {}, fallbackDeps = {}) {
|
|
61
68
|
requireExternalCreds(paths);
|
|
62
69
|
ensureKitDirs(paths);
|
|
63
70
|
const existing = fs.existsSync(paths.kitConfigPath)
|
|
@@ -72,10 +79,20 @@ export function installKit(paths, opts, host, deps = {}) {
|
|
|
72
79
|
fs.mkdirSync(config.hqRoot, { recursive: true, mode: 0o700 });
|
|
73
80
|
writeKitConfig(paths, config);
|
|
74
81
|
const skills = writeKitSkills(paths);
|
|
82
|
+
const activate = opts.activate ?? deps.activate ?? true;
|
|
75
83
|
const services = installServices(kitServiceSpecs(paths), host, {
|
|
76
84
|
...deps,
|
|
77
|
-
activate
|
|
85
|
+
activate,
|
|
78
86
|
});
|
|
87
|
+
if (shouldUseFallback(services, activate)) {
|
|
88
|
+
const started = startFallback(paths, host, fallbackDeps);
|
|
89
|
+
return {
|
|
90
|
+
config,
|
|
91
|
+
skills,
|
|
92
|
+
services,
|
|
93
|
+
fallback: { mode: "fallback", ...started, crontab: rebootCrontabLine(paths, host) },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
79
96
|
return { config, skills, services };
|
|
80
97
|
}
|
|
81
98
|
export function formatServiceSet(result) {
|
|
@@ -107,7 +124,7 @@ export function buildKitRuntime(service) {
|
|
|
107
124
|
process.env.HQ_MACHINE_CREDS_FILE = paths.machineCredsPath;
|
|
108
125
|
process.env.HQ_REQUIRE_MACHINE_IDENTITY = "1";
|
|
109
126
|
process.env.HQ_VAULT_API_URL = creds.apiBaseUrl;
|
|
110
|
-
const log = createKitLogger(paths, service, { echo: true });
|
|
127
|
+
const log = createKitLogger(paths, service, { echo: process.stdout.isTTY === true });
|
|
111
128
|
const getToken = () => ensureCognitoToken({ tokenSource: "machine", interactive: false });
|
|
112
129
|
return { paths, creds, config, getToken, log };
|
|
113
130
|
}
|
|
@@ -151,6 +168,31 @@ export async function runKitService(service, rt) {
|
|
|
151
168
|
refreshMs: rt.config.meshRefreshMs,
|
|
152
169
|
getToken: rt.getToken,
|
|
153
170
|
log: rt.log,
|
|
171
|
+
// Any doorbell (and the periodic refresh) re-polls the agent inbox, the
|
|
172
|
+
// only conversation source agent callers are authorized to read.
|
|
173
|
+
refetch: async () => {
|
|
174
|
+
const r = await pollInboxOnce({
|
|
175
|
+
paths: rt.paths,
|
|
176
|
+
agentUid: rt.creds.entityUid,
|
|
177
|
+
apiBaseUrl: rt.creds.apiBaseUrl,
|
|
178
|
+
pollMs: rt.config.inboxPollMs,
|
|
179
|
+
ack: rt.config.inboxAck,
|
|
180
|
+
getToken: rt.getToken,
|
|
181
|
+
log: rt.log,
|
|
182
|
+
});
|
|
183
|
+
if (!r.ok)
|
|
184
|
+
throw new Error("agent inbox poll failed; see inbox.log");
|
|
185
|
+
},
|
|
186
|
+
// An inbox doorbell means "poll now" rather than waiting for the loop.
|
|
187
|
+
onInboxDoorbell: () => pollInboxOnce({
|
|
188
|
+
paths: rt.paths,
|
|
189
|
+
agentUid: rt.creds.entityUid,
|
|
190
|
+
apiBaseUrl: rt.creds.apiBaseUrl,
|
|
191
|
+
pollMs: rt.config.inboxPollMs,
|
|
192
|
+
ack: rt.config.inboxAck,
|
|
193
|
+
getToken: rt.getToken,
|
|
194
|
+
log: rt.log,
|
|
195
|
+
}),
|
|
154
196
|
});
|
|
155
197
|
const stop = () => {
|
|
156
198
|
void handle.stop().then(() => process.exit(0));
|
|
@@ -164,6 +206,45 @@ export async function runKitService(service, rt) {
|
|
|
164
206
|
}
|
|
165
207
|
}
|
|
166
208
|
}
|
|
209
|
+
/** `kit run all`: supervise the four services as child processes with restart backoff. */
|
|
210
|
+
export async function runAllKitServices(rt, host) {
|
|
211
|
+
rt.log("info", `supervisor starting ${KIT_SERVICES.join(",")} agent=${rt.creds.entityUid} cli=${CLI_VERSION}`);
|
|
212
|
+
const handle = superviseKitServices({
|
|
213
|
+
services: KIT_SERVICES,
|
|
214
|
+
log: rt.log,
|
|
215
|
+
spawnService: (service) => {
|
|
216
|
+
const fd = fs.openSync(serviceLogPath(rt.paths, service), "a", 0o600);
|
|
217
|
+
try {
|
|
218
|
+
return spawn(host.nodeBinary, [host.hqBinary, "agent", "kit", "run", service], {
|
|
219
|
+
stdio: ["ignore", fd, fd],
|
|
220
|
+
env: process.env,
|
|
221
|
+
cwd: rt.paths.agentDir,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
fs.closeSync(fd);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
await new Promise((resolve) => {
|
|
230
|
+
const stop = (signal) => {
|
|
231
|
+
rt.log("info", `supervisor stopping (${signal})`);
|
|
232
|
+
handle.stop("SIGTERM");
|
|
233
|
+
resolve();
|
|
234
|
+
};
|
|
235
|
+
process.once("SIGTERM", () => stop("SIGTERM"));
|
|
236
|
+
process.once("SIGINT", () => stop("SIGINT"));
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
export function formatFallbackStatus(paths, kill) {
|
|
240
|
+
const st = fallbackStatus(paths, kill);
|
|
241
|
+
if (!st)
|
|
242
|
+
return null;
|
|
243
|
+
const state = st.running
|
|
244
|
+
? chalk.green(`running (pid ${st.pid})`)
|
|
245
|
+
: chalk.yellow(st.pid === null ? "pidfile unreadable" : `not running (stale pid ${st.pid})`);
|
|
246
|
+
return [` mode: fallback supervisor (hq agent kit run all) ${state}${chalk.dim(` ${st.pidFile}`)}`];
|
|
247
|
+
}
|
|
167
248
|
function fail(err) {
|
|
168
249
|
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
169
250
|
process.exit(1);
|
|
@@ -191,9 +272,19 @@ export function registerAgentKitCommand(agent) {
|
|
|
191
272
|
console.log(` hq root: ${result.config.hqRoot}`);
|
|
192
273
|
console.log(` logs: ${paths.logsDir}`);
|
|
193
274
|
console.log(` skills: ${paths.skillsDir} (${result.skills.length} skills)`);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
275
|
+
if (result.fallback) {
|
|
276
|
+
console.log(chalk.yellow(" services: service manager unavailable — running in fallback mode " +
|
|
277
|
+
`(hq agent kit run all, pid ${result.fallback.pid})`));
|
|
278
|
+
console.log(` pidfile: ${result.fallback.pidFile}`);
|
|
279
|
+
console.log(` kit log: ${result.fallback.logPath}`);
|
|
280
|
+
console.log(" To restart it after a reboot, add this line with `crontab -e`:");
|
|
281
|
+
console.log(` ${result.fallback.crontab}`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
console.log(" services:");
|
|
285
|
+
for (const line of formatServiceSet(result.services))
|
|
286
|
+
console.log(line);
|
|
287
|
+
}
|
|
197
288
|
console.log("");
|
|
198
289
|
console.log("Next: hq agent probe");
|
|
199
290
|
}
|
|
@@ -208,13 +299,19 @@ export function registerAgentKitCommand(agent) {
|
|
|
208
299
|
.action((opts) => {
|
|
209
300
|
try {
|
|
210
301
|
const paths = agentKitPaths(os.homedir(), process.env);
|
|
302
|
+
const fallback = fallbackStatus(paths);
|
|
211
303
|
const result = servicesStatus(kitServiceSpecs(paths), resolveServiceHost(paths));
|
|
212
304
|
if (opts.json) {
|
|
213
|
-
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
305
|
+
console.log(JSON.stringify({ ok: true, ...result, ...(fallback ? { fallback } : {}) }, null, 2));
|
|
214
306
|
return;
|
|
215
307
|
}
|
|
216
|
-
|
|
217
|
-
|
|
308
|
+
const fallbackLines = formatFallbackStatus(paths);
|
|
309
|
+
if (fallbackLines)
|
|
310
|
+
for (const line of fallbackLines)
|
|
311
|
+
console.log(line);
|
|
312
|
+
else
|
|
313
|
+
for (const line of formatServiceSet(result))
|
|
314
|
+
console.log(line);
|
|
218
315
|
}
|
|
219
316
|
catch (err) {
|
|
220
317
|
fail(err);
|
|
@@ -227,11 +324,15 @@ export function registerAgentKitCommand(agent) {
|
|
|
227
324
|
.action((opts) => {
|
|
228
325
|
try {
|
|
229
326
|
const paths = agentKitPaths(os.homedir(), process.env);
|
|
327
|
+
const fallback = stopFallback(paths);
|
|
230
328
|
const result = uninstallServices(kitServiceSpecs(paths), resolveServiceHost(paths));
|
|
231
329
|
if (opts.json) {
|
|
232
|
-
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
330
|
+
console.log(JSON.stringify({ ok: true, ...result, fallback }, null, 2));
|
|
233
331
|
return;
|
|
234
332
|
}
|
|
333
|
+
if (fallback.pid !== null) {
|
|
334
|
+
console.log(` fallback supervisor pid ${fallback.pid} ${fallback.killed ? "stopped" : "was not running"}; pidfile removed`);
|
|
335
|
+
}
|
|
235
336
|
console.log(chalk.green("HQ agent kit services removed."));
|
|
236
337
|
for (const line of formatServiceSet(result))
|
|
237
338
|
console.log(line);
|
|
@@ -242,10 +343,21 @@ export function registerAgentKitCommand(agent) {
|
|
|
242
343
|
});
|
|
243
344
|
kit
|
|
244
345
|
.command("run <service>")
|
|
245
|
-
.description(`Run one kit service in the foreground (${KIT_SERVICES.join("|")})`)
|
|
346
|
+
.description(`Run one kit service in the foreground (${KIT_SERVICES.join("|")}), or \`all\` to supervise every service in one process`)
|
|
246
347
|
.action(async (service) => {
|
|
348
|
+
if (service === "all") {
|
|
349
|
+
try {
|
|
350
|
+
const paths = agentKitPaths(os.homedir(), process.env);
|
|
351
|
+
const rt = buildKitRuntime("all");
|
|
352
|
+
await runAllKitServices(rt, resolveServiceHost(paths));
|
|
353
|
+
process.exit(0);
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
fail(err);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
247
359
|
if (!isKitService(service)) {
|
|
248
|
-
console.error(chalk.red(`Unknown kit service "${service}". Expected one of: ${KIT_SERVICES.join(", ")}`));
|
|
360
|
+
console.error(chalk.red(`Unknown kit service "${service}". Expected one of: all, ${KIT_SERVICES.join(", ")}`));
|
|
249
361
|
process.exit(1);
|
|
250
362
|
}
|
|
251
363
|
try {
|
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* whoami mint as the machine identity; token names this agent
|
|
6
6
|
* team-sync `hq sync pull --all` succeeds and the company folder exists
|
|
7
|
-
* work-mesh realtime
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* work-mesh the personal (contract-2) realtime vend names this agent, the
|
|
8
|
+
* agent-authorized inbox route answers 200, and this host's own
|
|
9
|
+
* presence is live: a fresh last-heartbeat.json plus an ok
|
|
10
|
+
* component-mesh stamp from the doorbell listener. (The company
|
|
11
|
+
* roster route is owner/admin-only and 404s for agt_ callers.)
|
|
12
|
+
* dm send a DM to self via POST /v1/notify/dm (accepted) and read the agent inbox (200)
|
|
13
|
+
* through GET /v1/agents/{uid}/inbox (the agent read surface;
|
|
14
|
+
* /v1/notify/thread is intentionally closed to agents)
|
|
10
15
|
* secrets `GET /secrets/{companyUid}` answers 200
|
|
11
16
|
*
|
|
12
17
|
* Output is the exact shape the console recipes promise: one
|
|
@@ -18,6 +23,7 @@
|
|
|
18
23
|
* the kit's last heartbeat.
|
|
19
24
|
*/
|
|
20
25
|
import { Command } from "commander";
|
|
26
|
+
import { type InboxFetchResult } from "../lib/agent-kit/run/inbox.js";
|
|
21
27
|
import { type AgentKitPaths } from "../lib/agent-kit/paths.js";
|
|
22
28
|
import type { ExternalMachineCreds } from "../lib/agent-kit/creds.js";
|
|
23
29
|
export interface ProbeCheck {
|
|
@@ -32,8 +38,8 @@ export interface ProbeResult {
|
|
|
32
38
|
/** Seconds since the kit's last successful heartbeat, or null if never. */
|
|
33
39
|
heartbeatAgeSeconds: number | null;
|
|
34
40
|
}
|
|
35
|
-
|
|
36
|
-
export declare const
|
|
41
|
+
/** A heartbeat older than this does not count as live presence (3 missed 60 s beats). */
|
|
42
|
+
export declare const PRESENCE_FRESH_SECONDS = 180;
|
|
37
43
|
export interface ProbeDeps {
|
|
38
44
|
paths: AgentKitPaths;
|
|
39
45
|
creds: ExternalMachineCreds;
|
|
@@ -44,11 +50,9 @@ export interface ProbeDeps {
|
|
|
44
50
|
vendRealtime: (token: string) => Promise<{
|
|
45
51
|
actorUid: string;
|
|
46
52
|
}>;
|
|
47
|
-
|
|
53
|
+
/** GET /v1/agents/{self}/inbox — agent-authorized, self-scoped. */
|
|
54
|
+
readInbox: (token: string) => Promise<InboxFetchResult>;
|
|
48
55
|
sendDm: (token: string, toUid: string, body: string) => Promise<void>;
|
|
49
|
-
readThread: (token: string, withUid: string) => Promise<Array<{
|
|
50
|
-
body: string;
|
|
51
|
-
}>>;
|
|
52
56
|
listSecrets: (token: string, companyUid: string) => Promise<number>;
|
|
53
57
|
postResult: (token: string, result: ProbeResult) => Promise<number>;
|
|
54
58
|
now?: () => Date;
|
|
@@ -58,6 +62,8 @@ export interface ProbeDeps {
|
|
|
58
62
|
}
|
|
59
63
|
export declare function heartbeatAgeSeconds(paths: Pick<AgentKitPaths, "lastHeartbeatPath">, now?: () => Date): number | null;
|
|
60
64
|
export declare function runProbe(deps: ProbeDeps): Promise<ProbeResult>;
|
|
65
|
+
/** True when any inbox item (whatever its channel shape) carries the nonce. */
|
|
66
|
+
export declare function inboxContainsNonce(body: unknown, nonce: string): boolean;
|
|
61
67
|
export declare function formatHeartbeatAge(age: number | null): string;
|
|
62
68
|
/**
|
|
63
69
|
* Recipe contract: `PASS <check> <detail>` / `FAIL <check> <detail>` per
|