@indigoai-us/hq-cli 5.115.6 → 5.117.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +169 -11
  2. package/dist/command-catalog.generated.d.ts +220 -2
  3. package/dist/command-catalog.generated.js +281 -2
  4. package/dist/command-registration-plan.d.ts +6 -0
  5. package/dist/command-registration-plan.js +1 -0
  6. package/dist/commands/agent-enroll.d.ts +105 -0
  7. package/dist/commands/agent-enroll.js +273 -0
  8. package/dist/commands/agent-kit.d.ts +53 -0
  9. package/dist/commands/agent-kit.js +260 -0
  10. package/dist/commands/agent-mcp.d.ts +22 -0
  11. package/dist/commands/agent-mcp.js +104 -0
  12. package/dist/commands/agent-probe.d.ts +71 -0
  13. package/dist/commands/agent-probe.js +294 -0
  14. package/dist/commands/agent.d.ts +12 -0
  15. package/dist/commands/agent.js +23 -0
  16. package/dist/commands/agents.d.ts +27 -0
  17. package/dist/commands/agents.js +280 -6
  18. package/dist/commands/bot.d.ts +140 -1
  19. package/dist/commands/bot.js +757 -22
  20. package/dist/commands/secrets.js +17 -5
  21. package/dist/lib/agent-kit/creds.d.ts +60 -0
  22. package/dist/lib/agent-kit/creds.js +123 -0
  23. package/dist/lib/agent-kit/kit-config.d.ts +29 -0
  24. package/dist/lib/agent-kit/kit-config.js +54 -0
  25. package/dist/lib/agent-kit/log.d.ts +17 -0
  26. package/dist/lib/agent-kit/log.js +46 -0
  27. package/dist/lib/agent-kit/mcp/jsonrpc.d.ts +84 -0
  28. package/dist/lib/agent-kit/mcp/jsonrpc.js +164 -0
  29. package/dist/lib/agent-kit/mcp/tools.d.ts +45 -0
  30. package/dist/lib/agent-kit/mcp/tools.js +280 -0
  31. package/dist/lib/agent-kit/paths.d.ts +42 -0
  32. package/dist/lib/agent-kit/paths.js +56 -0
  33. package/dist/lib/agent-kit/run/heartbeat.d.ts +52 -0
  34. package/dist/lib/agent-kit/run/heartbeat.js +97 -0
  35. package/dist/lib/agent-kit/run/inbox.d.ts +59 -0
  36. package/dist/lib/agent-kit/run/inbox.js +152 -0
  37. package/dist/lib/agent-kit/run/mesh-listener.d.ts +58 -0
  38. package/dist/lib/agent-kit/run/mesh-listener.js +193 -0
  39. package/dist/lib/agent-kit/run/sync.d.ts +33 -0
  40. package/dist/lib/agent-kit/run/sync.js +58 -0
  41. package/dist/lib/agent-kit/services.d.ts +21 -0
  42. package/dist/lib/agent-kit/services.js +46 -0
  43. package/dist/lib/agent-kit/skills.d.ts +18 -0
  44. package/dist/lib/agent-kit/skills.js +149 -0
  45. package/dist/lib/bot/api.d.ts +51 -0
  46. package/dist/lib/bot/api.js +32 -0
  47. package/dist/lib/bot/daemon.d.ts +17 -0
  48. package/dist/lib/bot/daemon.js +44 -3
  49. package/dist/lib/bot/index.d.ts +4 -0
  50. package/dist/lib/bot/index.js +4 -0
  51. package/dist/lib/bot/inflight.d.ts +14 -0
  52. package/dist/lib/bot/local-config.d.ts +70 -0
  53. package/dist/lib/bot/local-config.js +147 -0
  54. package/dist/lib/bot/local-name.d.ts +54 -0
  55. package/dist/lib/bot/local-name.js +114 -0
  56. package/dist/lib/bot/run.d.ts +9 -0
  57. package/dist/lib/bot/run.js +117 -24
  58. package/dist/lib/bot/runnable.d.ts +51 -0
  59. package/dist/lib/bot/runnable.js +65 -0
  60. package/dist/lib/bot/self-heal.d.ts +52 -0
  61. package/dist/lib/bot/self-heal.js +79 -0
  62. package/dist/lib/bot/split.d.ts +32 -0
  63. package/dist/lib/bot/split.js +241 -0
  64. package/dist/lib/service-manager/index.d.ts +43 -0
  65. package/dist/lib/service-manager/index.js +114 -0
  66. package/dist/lib/service-manager/launchd.d.ts +23 -0
  67. package/dist/lib/service-manager/launchd.js +81 -0
  68. package/dist/lib/service-manager/systemd.d.ts +19 -0
  69. package/dist/lib/service-manager/systemd.js +72 -0
  70. package/dist/lib/service-manager/types.d.ts +32 -0
  71. package/dist/lib/service-manager/types.js +26 -0
  72. package/dist/utils/self-update.js +2 -30
  73. package/dist/utils/update-command-supervisor.cjs +194 -0
  74. package/dist/utils/version-gate.d.ts +18 -0
  75. package/dist/utils/version-gate.js +126 -7
  76. package/package.json +2 -2
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Kit service: inbox poller.
3
+ *
4
+ * Mirrors the poll half of the hosted box's hq-agent-inbox.sh: every
5
+ * `inboxPollMs` GET /v1/agents/{uid}/inbox, take `.messages[]`, and stamp
6
+ * `component-inbox` ok on a 200 (any other status → error, token re-minted
7
+ * on the next pass). The hosted watcher then dispatches each item to a
8
+ * provider and acks it; an external bot brings its own brain, so this
9
+ * service only MIRRORS: each new item is written once to
10
+ * ~/.hq-agent/inbox/<id>.json and appended to inbox.jsonl for the bot to
11
+ * consume, and the work-mesh conversation cache is refreshed so `hq dm`
12
+ * and the skills see the same state.
13
+ *
14
+ * Acking is opt-in (kit.json `inboxAck`): with it off the server keeps the
15
+ * item until the bot acks (`POST /v1/agents/{uid}/inbox/{id}/ack`), and the
16
+ * poller dedupes by id so the same item is never mirrored twice.
17
+ */
18
+ import * as fs from "node:fs";
19
+ import * as path from "node:path";
20
+ import { vaultApiFetch } from "../../../utils/vault-api.js";
21
+ import { warmMeshConversationCache } from "../../mesh/api.js";
22
+ import { writeComponentStatus } from "../creds.js";
23
+ export const SEEN_IDS_NAME = "seen-ids.json";
24
+ export const INBOX_JSONL_NAME = "inbox.jsonl";
25
+ export const MAX_SEEN_IDS = 5000;
26
+ /** Pull `{id}` out of each server item; items without a string id are dropped. */
27
+ export function extractInboxItems(body) {
28
+ const rec = body && typeof body === "object" ? body : {};
29
+ const messages = Array.isArray(rec.messages) ? rec.messages : [];
30
+ const items = [];
31
+ for (const m of messages) {
32
+ if (!m || typeof m !== "object")
33
+ continue;
34
+ const raw = m;
35
+ const id = typeof raw.id === "string" ? raw.id : typeof raw.messageId === "string" ? raw.messageId : null;
36
+ if (!id || !/^[A-Za-z0-9._:-]{1,200}$/.test(id))
37
+ continue;
38
+ items.push({ id, raw });
39
+ }
40
+ return items;
41
+ }
42
+ export function readSeenIds(paths) {
43
+ try {
44
+ const raw = JSON.parse(fs.readFileSync(path.join(paths.inboxDir, SEEN_IDS_NAME), "utf8"));
45
+ return new Set(Array.isArray(raw) ? raw.filter((x) => typeof x === "string") : []);
46
+ }
47
+ catch {
48
+ return new Set();
49
+ }
50
+ }
51
+ export function writeSeenIds(paths, seen) {
52
+ fs.mkdirSync(paths.inboxDir, { recursive: true, mode: 0o700 });
53
+ const list = [...seen].slice(-MAX_SEEN_IDS);
54
+ fs.writeFileSync(path.join(paths.inboxDir, SEEN_IDS_NAME), JSON.stringify(list), { mode: 0o600 });
55
+ }
56
+ export function mirrorInboxItem(paths, item) {
57
+ fs.mkdirSync(paths.inboxDir, { recursive: true, mode: 0o700 });
58
+ const dest = path.join(paths.inboxDir, `${item.id}.json`);
59
+ fs.writeFileSync(dest, `${JSON.stringify(item.raw, null, 2)}\n`, { mode: 0o600 });
60
+ fs.appendFileSync(path.join(paths.inboxDir, INBOX_JSONL_NAME), `${JSON.stringify({ id: item.id, mirroredAt: new Date().toISOString(), ...item.raw })}\n`, { mode: 0o600 });
61
+ return dest;
62
+ }
63
+ export function defaultFetchInbox(agentUid, apiBaseUrl) {
64
+ return async (token) => {
65
+ const res = await vaultApiFetch({
66
+ token,
67
+ baseUrl: apiBaseUrl,
68
+ path: `/v1/agents/${encodeURIComponent(agentUid)}/inbox`,
69
+ });
70
+ const body = await res.json().catch(() => ({}));
71
+ return { status: res.status, body };
72
+ };
73
+ }
74
+ export function defaultAckItem(agentUid, apiBaseUrl) {
75
+ return async (token, id) => {
76
+ const res = await vaultApiFetch({
77
+ token,
78
+ baseUrl: apiBaseUrl,
79
+ path: `/v1/agents/${encodeURIComponent(agentUid)}/inbox/${encodeURIComponent(id)}/ack`,
80
+ method: "POST",
81
+ });
82
+ return res.status;
83
+ };
84
+ }
85
+ export async function pollInboxOnce(deps) {
86
+ const fetchInbox = deps.fetchInbox ?? defaultFetchInbox(deps.agentUid, deps.apiBaseUrl);
87
+ const ackItem = deps.ackItem ?? defaultAckItem(deps.agentUid, deps.apiBaseUrl);
88
+ const warmCache = deps.warmCache ?? ((token, uid) => warmMeshConversationCache(token, uid));
89
+ let token;
90
+ try {
91
+ token = await deps.getToken();
92
+ }
93
+ catch (err) {
94
+ writeComponentStatus(deps.paths, "inbox", "error");
95
+ deps.log("error", `inbox token mint failed: ${err instanceof Error ? err.message : String(err)}`);
96
+ return { ok: false, mirrored: 0 };
97
+ }
98
+ let res;
99
+ try {
100
+ res = await fetchInbox(token);
101
+ }
102
+ catch (err) {
103
+ writeComponentStatus(deps.paths, "inbox", "error");
104
+ deps.log("error", `inbox fetch failed: ${err instanceof Error ? err.message : String(err)}`);
105
+ return { ok: false, mirrored: 0 };
106
+ }
107
+ if (res.status !== 200) {
108
+ writeComponentStatus(deps.paths, "inbox", "error");
109
+ deps.log("warn", `inbox poll ${res.status}`);
110
+ return { ok: false, mirrored: 0 };
111
+ }
112
+ writeComponentStatus(deps.paths, "inbox", "ok");
113
+ const seen = readSeenIds(deps.paths);
114
+ const fresh = extractInboxItems(res.body).filter((i) => !seen.has(i.id));
115
+ for (const item of fresh) {
116
+ mirrorInboxItem(deps.paths, item);
117
+ seen.add(item.id);
118
+ if (deps.ack) {
119
+ try {
120
+ const status = await ackItem(token, item.id);
121
+ if (status < 200 || status >= 300)
122
+ deps.log("warn", `ack ${item.id} → ${status}`);
123
+ }
124
+ catch (err) {
125
+ deps.log("warn", `ack ${item.id} failed: ${err instanceof Error ? err.message : String(err)}`);
126
+ }
127
+ }
128
+ }
129
+ if (fresh.length > 0) {
130
+ writeSeenIds(deps.paths, seen);
131
+ deps.log("info", `inbox mirrored ${fresh.length} item(s)`);
132
+ try {
133
+ await warmCache(token, deps.agentUid);
134
+ }
135
+ catch (err) {
136
+ deps.log("warn", `cache refresh failed: ${err instanceof Error ? err.message : String(err)}`);
137
+ }
138
+ }
139
+ return { ok: true, mirrored: fresh.length };
140
+ }
141
+ export async function runInboxLoop(deps) {
142
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
143
+ let polls = 0;
144
+ for (;;) {
145
+ await pollInboxOnce(deps);
146
+ polls += 1;
147
+ if (deps.maxPolls !== undefined && polls >= deps.maxPolls)
148
+ return;
149
+ await sleep(deps.pollMs);
150
+ }
151
+ }
152
+ //# sourceMappingURL=inbox.js.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Kit service: work-mesh doorbell listener.
3
+ *
4
+ * Policy hq-work-mesh-source-of-truth: the work mesh (REST) is the source of
5
+ * truth; MQTT carries IDS-ONLY doorbells on the agent's personal topics
6
+ * `hq/{agt_*}/{dm,work,sessions,notifications}`. This listener is the cache
7
+ * writer: on any doorbell it refetches through the REST API into
8
+ * ~/.hq/work-mesh/cache (warmMeshConversationCache) — message bodies are
9
+ * never taken from MQTT. A periodic refresh covers missed doorbells.
10
+ *
11
+ * Credentials come from the same contract-3 vend the mesh daemon uses
12
+ * (POST /v1/realtime/credentials); the WSS URL is SigV4-presigned. The
13
+ * connection is rebuilt before the vended credentials expire. `component-mesh`
14
+ * is stamped ok while subscribed and refreshing, error otherwise.
15
+ */
16
+ import { type IClientOptions } from "mqtt";
17
+ import { type CredentialsFetcher } from "../../mesh/live/daemon/credentials.js";
18
+ import type { KitLogger } from "../log.js";
19
+ import type { AgentKitPaths } from "../paths.js";
20
+ export declare const DOORBELL_KINDS: readonly ["dm", "work", "sessions", "notifications"];
21
+ export declare const DOORBELL_DEBOUNCE_MS = 2000;
22
+ export declare const RECONNECT_BASE_MS = 1000;
23
+ export declare const RECONNECT_MAX_MS = 60000;
24
+ export declare function doorbellTopics(actorUid: string): string[];
25
+ /** Minimal mqtt client surface the listener needs (test seam). */
26
+ export interface DoorbellMqttClient {
27
+ on(event: string, handler: (...args: any[]) => void): unknown;
28
+ subscribe(topics: string | string[], opts: {
29
+ qos: 0 | 1 | 2;
30
+ }, callback?: (err: Error | null) => void): unknown;
31
+ end(force?: boolean, opts?: unknown, cb?: () => void): unknown;
32
+ }
33
+ export type DoorbellConnectFn = (url: string, opts: IClientOptions) => DoorbellMqttClient;
34
+ export interface MeshListenerDeps {
35
+ paths: AgentKitPaths;
36
+ agentUid: string;
37
+ apiBaseUrl: string;
38
+ refreshMs: number;
39
+ getToken: () => Promise<string>;
40
+ log: KitLogger;
41
+ fetchCredentials?: CredentialsFetcher;
42
+ connect?: DoorbellConnectFn;
43
+ refetch?: (token: string, actorUid: string) => Promise<unknown>;
44
+ now?: () => Date;
45
+ random?: () => number;
46
+ setTimeout?: (fn: () => void, ms: number) => unknown;
47
+ clearTimeout?: (handle: unknown) => void;
48
+ }
49
+ export interface MeshListenerHandle {
50
+ stop: () => Promise<void>;
51
+ /** Force a cache refetch (tests / doorbell path). */
52
+ refetchNow: (reason: string) => Promise<void>;
53
+ /** Simulate a doorbell (tests). */
54
+ ring: (topic: string) => void;
55
+ state: () => "idle" | "connecting" | "subscribed" | "closed";
56
+ }
57
+ export declare function startMeshListener(deps: MeshListenerDeps): Promise<MeshListenerHandle>;
58
+ //# sourceMappingURL=mesh-listener.d.ts.map
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Kit service: work-mesh doorbell listener.
3
+ *
4
+ * Policy hq-work-mesh-source-of-truth: the work mesh (REST) is the source of
5
+ * truth; MQTT carries IDS-ONLY doorbells on the agent's personal topics
6
+ * `hq/{agt_*}/{dm,work,sessions,notifications}`. This listener is the cache
7
+ * writer: on any doorbell it refetches through the REST API into
8
+ * ~/.hq/work-mesh/cache (warmMeshConversationCache) — message bodies are
9
+ * never taken from MQTT. A periodic refresh covers missed doorbells.
10
+ *
11
+ * Credentials come from the same contract-3 vend the mesh daemon uses
12
+ * (POST /v1/realtime/credentials); the WSS URL is SigV4-presigned. The
13
+ * connection is rebuilt before the vended credentials expire. `component-mesh`
14
+ * is stamped ok while subscribed and refreshing, error otherwise.
15
+ */
16
+ import mqtt from "mqtt";
17
+ import { warmMeshConversationCache } from "../../mesh/api.js";
18
+ import { createContract3Fetcher, MQTT_KEEPALIVE_SECONDS, renewalDelayMs, } from "../../mesh/live/daemon/credentials.js";
19
+ import { presignIotWssUrl } from "../../mesh/live/daemon/presign.js";
20
+ import { writeComponentStatus } from "../creds.js";
21
+ export const DOORBELL_KINDS = ["dm", "work", "sessions", "notifications"];
22
+ export const DOORBELL_DEBOUNCE_MS = 2_000;
23
+ export const RECONNECT_BASE_MS = 1_000;
24
+ export const RECONNECT_MAX_MS = 60_000;
25
+ export function doorbellTopics(actorUid) {
26
+ return DOORBELL_KINDS.map((k) => `hq/${actorUid}/${k}`);
27
+ }
28
+ function backoff(attempt, random) {
29
+ const cap = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** attempt);
30
+ return Math.max(RECONNECT_BASE_MS, Math.floor(random() * cap));
31
+ }
32
+ export async function startMeshListener(deps) {
33
+ const now = deps.now ?? (() => new Date());
34
+ const random = deps.random ?? Math.random;
35
+ const setT = deps.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
36
+ const clearT = deps.clearTimeout ?? ((h) => clearTimeout(h));
37
+ const connect = deps.connect ?? ((url, opts) => mqtt.connect(url, opts));
38
+ const refetch = deps.refetch ?? ((token, uid) => warmMeshConversationCache(token, uid));
39
+ const fetchCredentials = deps.fetchCredentials ??
40
+ (async () => {
41
+ const token = await deps.getToken();
42
+ return createContract3Fetcher({ token, baseUrl: deps.apiBaseUrl })();
43
+ });
44
+ let state = "idle";
45
+ let client = null;
46
+ let stopped = false;
47
+ let attempt = 0;
48
+ let debounce = null;
49
+ let renewal = null;
50
+ let periodic = null;
51
+ let refetching = null;
52
+ let pendingReason = null;
53
+ const doRefetch = async (reason) => {
54
+ if (refetching) {
55
+ pendingReason = reason;
56
+ return refetching;
57
+ }
58
+ refetching = (async () => {
59
+ try {
60
+ const token = await deps.getToken();
61
+ await refetch(token, deps.agentUid);
62
+ writeComponentStatus(deps.paths, "mesh", state === "subscribed" ? "ok" : "error");
63
+ deps.log("info", `cache refetched (${reason})`);
64
+ }
65
+ catch (err) {
66
+ writeComponentStatus(deps.paths, "mesh", "error");
67
+ deps.log("error", `refetch failed (${reason}): ${err instanceof Error ? err.message : String(err)}`);
68
+ }
69
+ finally {
70
+ refetching = null;
71
+ }
72
+ if (pendingReason) {
73
+ const next = pendingReason;
74
+ pendingReason = null;
75
+ await doRefetch(next);
76
+ }
77
+ })();
78
+ return refetching;
79
+ };
80
+ const ring = (topic) => {
81
+ if (debounce)
82
+ clearT(debounce);
83
+ debounce = setT(() => {
84
+ debounce = null;
85
+ void doRefetch(`doorbell ${topic}`);
86
+ }, DOORBELL_DEBOUNCE_MS);
87
+ };
88
+ const schedulePeriodic = () => {
89
+ if (periodic)
90
+ clearT(periodic);
91
+ periodic = setT(() => {
92
+ periodic = null;
93
+ void doRefetch("periodic").then(schedulePeriodic);
94
+ }, deps.refreshMs);
95
+ };
96
+ const scheduleReconnect = () => {
97
+ if (stopped)
98
+ return;
99
+ const delay = backoff(attempt, random);
100
+ attempt += 1;
101
+ deps.log("warn", `mqtt reconnect in ${delay}ms (attempt ${attempt})`);
102
+ setT(() => void connectOnce(), delay);
103
+ };
104
+ const connectOnce = async () => {
105
+ if (stopped)
106
+ return;
107
+ state = "connecting";
108
+ let bundle;
109
+ try {
110
+ bundle = await fetchCredentials();
111
+ if (bundle.actorUid !== deps.agentUid) {
112
+ throw new Error(`realtime vend is for ${bundle.actorUid}, expected ${deps.agentUid}`);
113
+ }
114
+ }
115
+ catch (err) {
116
+ writeComponentStatus(deps.paths, "mesh", "error");
117
+ deps.log("error", `credential vend failed: ${err instanceof Error ? err.message : String(err)}`);
118
+ scheduleReconnect();
119
+ return;
120
+ }
121
+ const url = presignIotWssUrl(bundle.credentials, bundle.iotEndpoint, bundle.region, now());
122
+ const c = connect(url, {
123
+ clientId: bundle.clientId,
124
+ keepalive: MQTT_KEEPALIVE_SECONDS,
125
+ clean: true,
126
+ reconnectPeriod: 0,
127
+ protocolVersion: 4,
128
+ });
129
+ client = c;
130
+ let settled = false;
131
+ c.on("connect", () => {
132
+ c.subscribe(doorbellTopics(bundle.actorUid), { qos: 1 }, (err) => {
133
+ if (err) {
134
+ deps.log("error", `subscribe failed: ${err.message}`);
135
+ c.end(true);
136
+ return;
137
+ }
138
+ settled = true;
139
+ attempt = 0;
140
+ state = "subscribed";
141
+ writeComponentStatus(deps.paths, "mesh", "ok");
142
+ deps.log("info", `subscribed to ${doorbellTopics(bundle.actorUid).length} doorbell topics`);
143
+ void doRefetch("connect");
144
+ if (renewal)
145
+ clearT(renewal);
146
+ renewal = setT(() => {
147
+ deps.log("info", "renewing realtime credentials");
148
+ c.end(true);
149
+ }, renewalDelayMs(now().getTime(), bundle.expiresAt));
150
+ });
151
+ });
152
+ c.on("message", (topic) => {
153
+ // Ids-only doorbell: the payload is never parsed for content.
154
+ ring(topic);
155
+ });
156
+ c.on("error", (err) => {
157
+ deps.log("warn", `mqtt error: ${err.message}`);
158
+ });
159
+ c.on("close", () => {
160
+ if (client !== c)
161
+ return;
162
+ client = null;
163
+ const wasSubscribed = state === "subscribed";
164
+ state = stopped ? "closed" : "idle";
165
+ writeComponentStatus(deps.paths, "mesh", "error");
166
+ if (!settled || !wasSubscribed)
167
+ deps.log("warn", "mqtt closed before subscribe settled");
168
+ scheduleReconnect();
169
+ });
170
+ };
171
+ schedulePeriodic();
172
+ await connectOnce();
173
+ return {
174
+ stop: async () => {
175
+ stopped = true;
176
+ state = "closed";
177
+ if (debounce)
178
+ clearT(debounce);
179
+ if (renewal)
180
+ clearT(renewal);
181
+ if (periodic)
182
+ clearT(periodic);
183
+ const c = client;
184
+ client = null;
185
+ if (c)
186
+ await new Promise((r) => c.end(true, undefined, () => r()));
187
+ },
188
+ refetchNow: doRefetch,
189
+ ring,
190
+ state: () => state,
191
+ };
192
+ }
193
+ //# sourceMappingURL=mesh-listener.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Kit service: company vault sync loop.
3
+ *
4
+ * Mirrors the hosted box's hq-agent-sync.service, which runs hq-cloud's
5
+ * `hq-sync-runner --companies --direction both --on-conflict keep` on a
6
+ * loop and writes `component-sync`. Here the equivalent is the CLI's own
7
+ * `hq sync pull --all --on-conflict keep --hq-root <kit hqRoot>`, run as a
8
+ * child of the same node + hq binary this service was launched with, so the
9
+ * machine identity, creds path and version are exactly the service's own.
10
+ *
11
+ * Honest health: exit 0 = ok, anything else = error (the heartbeat turns a
12
+ * stale stamp into error on its own).
13
+ */
14
+ import type { KitLogger } from "../log.js";
15
+ import type { AgentKitPaths } from "../paths.js";
16
+ export interface SyncRunDeps {
17
+ paths: AgentKitPaths;
18
+ hqRoot: string;
19
+ intervalMs: number;
20
+ log: KitLogger;
21
+ nodeBinary?: string;
22
+ hqBinary?: string;
23
+ /** Injected child runner (tests). Resolves with the exit code. */
24
+ runPull?: (args: string[]) => Promise<number>;
25
+ sleep?: (ms: number) => Promise<void>;
26
+ /** Stop after this many passes (tests); default runs forever. */
27
+ maxPasses?: number;
28
+ }
29
+ export declare function syncPullArgs(hqRoot: string): string[];
30
+ export declare function defaultRunPull(nodeBinary: string, hqBinary: string, env: NodeJS.ProcessEnv): (args: string[]) => Promise<number>;
31
+ export declare function runSyncOnce(deps: SyncRunDeps): Promise<boolean>;
32
+ export declare function runSyncLoop(deps: SyncRunDeps): Promise<void>;
33
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Kit service: company vault sync loop.
3
+ *
4
+ * Mirrors the hosted box's hq-agent-sync.service, which runs hq-cloud's
5
+ * `hq-sync-runner --companies --direction both --on-conflict keep` on a
6
+ * loop and writes `component-sync`. Here the equivalent is the CLI's own
7
+ * `hq sync pull --all --on-conflict keep --hq-root <kit hqRoot>`, run as a
8
+ * child of the same node + hq binary this service was launched with, so the
9
+ * machine identity, creds path and version are exactly the service's own.
10
+ *
11
+ * Honest health: exit 0 = ok, anything else = error (the heartbeat turns a
12
+ * stale stamp into error on its own).
13
+ */
14
+ import { spawn } from "node:child_process";
15
+ import { writeComponentStatus } from "../creds.js";
16
+ export function syncPullArgs(hqRoot) {
17
+ return ["sync", "pull", "--all", "--on-conflict", "keep", "--hq-root", hqRoot];
18
+ }
19
+ export function defaultRunPull(nodeBinary, hqBinary, env) {
20
+ return (args) => new Promise((resolve) => {
21
+ const child = spawn(nodeBinary, [hqBinary, ...args], {
22
+ env,
23
+ stdio: ["ignore", "inherit", "inherit"],
24
+ });
25
+ child.on("error", () => resolve(127));
26
+ child.on("exit", (code) => resolve(code ?? 1));
27
+ });
28
+ }
29
+ export async function runSyncOnce(deps) {
30
+ const runPull = deps.runPull ??
31
+ defaultRunPull(deps.nodeBinary ?? process.execPath, deps.hqBinary ?? process.argv[1], process.env);
32
+ const args = syncPullArgs(deps.hqRoot);
33
+ deps.log("info", `sync start hq-root=${deps.hqRoot}`);
34
+ let code;
35
+ try {
36
+ code = await runPull(args);
37
+ }
38
+ catch (err) {
39
+ deps.log("error", `sync spawn failed: ${err instanceof Error ? err.message : String(err)}`);
40
+ code = 1;
41
+ }
42
+ const ok = code === 0;
43
+ writeComponentStatus(deps.paths, "sync", ok ? "ok" : "error");
44
+ deps.log(ok ? "info" : "error", `sync ${ok ? "ok" : `failed exit=${code}`}`);
45
+ return ok;
46
+ }
47
+ export async function runSyncLoop(deps) {
48
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
49
+ let passes = 0;
50
+ for (;;) {
51
+ await runSyncOnce(deps);
52
+ passes += 1;
53
+ if (deps.maxPasses !== undefined && passes >= deps.maxPasses)
54
+ return;
55
+ await sleep(deps.intervalMs);
56
+ }
57
+ }
58
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The four kit services and how each is launched. Every unit runs
3
+ * `node hq agent kit run <service>`; the service reads kit.json and the
4
+ * machine-creds file itself, so the units carry paths and nothing else.
5
+ */
6
+ import type { ServiceSpec } from "../service-manager/types.js";
7
+ import type { AgentKitPaths } from "./paths.js";
8
+ export type KitService = "sync" | "mesh" | "inbox" | "heartbeat";
9
+ export declare const KIT_SERVICES: readonly KitService[];
10
+ export declare const KIT_LABEL_PREFIX = "ai.getindigo.hq-agent";
11
+ export declare function isKitService(value: string): value is KitService;
12
+ /**
13
+ * Environment every service inherits. HQ_MACHINE_CREDS_FILE pins hq-cloud's
14
+ * mint to the kit's creds file; HQ_REQUIRE_MACHINE_IDENTITY makes a missing
15
+ * file a loud error instead of a browser-login hang; HQ_AGENT_DIR keeps a
16
+ * relocated kit consistent across restarts.
17
+ */
18
+ export declare function kitServiceEnv(paths: AgentKitPaths): Record<string, string>;
19
+ export declare function kitServiceSpec(service: KitService, paths: AgentKitPaths): ServiceSpec;
20
+ export declare function kitServiceSpecs(paths: AgentKitPaths): ServiceSpec[];
21
+ //# sourceMappingURL=services.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The four kit services and how each is launched. Every unit runs
3
+ * `node hq agent kit run <service>`; the service reads kit.json and the
4
+ * machine-creds file itself, so the units carry paths and nothing else.
5
+ */
6
+ import { serviceLogPath } from "./paths.js";
7
+ export const KIT_SERVICES = ["sync", "mesh", "inbox", "heartbeat"];
8
+ export const KIT_LABEL_PREFIX = "ai.getindigo.hq-agent";
9
+ export function isKitService(value) {
10
+ return KIT_SERVICES.includes(value);
11
+ }
12
+ const DESCRIPTIONS = {
13
+ sync: "HQ agent kit: company vault sync loop",
14
+ mesh: "HQ agent kit: work-mesh doorbell listener",
15
+ inbox: "HQ agent kit: inbox poller",
16
+ heartbeat: "HQ agent kit: heartbeat reporter",
17
+ };
18
+ /**
19
+ * Environment every service inherits. HQ_MACHINE_CREDS_FILE pins hq-cloud's
20
+ * mint to the kit's creds file; HQ_REQUIRE_MACHINE_IDENTITY makes a missing
21
+ * file a loud error instead of a browser-login hang; HQ_AGENT_DIR keeps a
22
+ * relocated kit consistent across restarts.
23
+ */
24
+ export function kitServiceEnv(paths) {
25
+ return {
26
+ HQ_AGENT_DIR: paths.agentDir,
27
+ HQ_MACHINE_CREDS_FILE: paths.machineCredsPath,
28
+ HQ_REQUIRE_MACHINE_IDENTITY: "1",
29
+ };
30
+ }
31
+ export function kitServiceSpec(service, paths) {
32
+ return {
33
+ name: service,
34
+ label: `${KIT_LABEL_PREFIX}.${service}`,
35
+ description: DESCRIPTIONS[service],
36
+ args: ["agent", "kit", "run", service],
37
+ logPath: serviceLogPath(paths, service),
38
+ workingDir: paths.agentDir,
39
+ env: kitServiceEnv(paths),
40
+ restartSec: 10,
41
+ };
42
+ }
43
+ export function kitServiceSpecs(paths) {
44
+ return KIT_SERVICES.map((s) => kitServiceSpec(s, paths));
45
+ }
46
+ //# sourceMappingURL=services.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The skills directory the kit ships to ~/.hq-agent/skills/<name>/SKILL.md.
3
+ *
4
+ * agentskills.io format: YAML frontmatter with `name` and `description`, then
5
+ * markdown the bot's framework loads as an instruction. Each skill drives
6
+ * the `hq` CLI, which authenticates as the machine identity on its own, so
7
+ * no skill ever embeds or asks for a token or secret value.
8
+ */
9
+ import type { AgentKitPaths } from "./paths.js";
10
+ export interface KitSkill {
11
+ name: string;
12
+ description: string;
13
+ body: string;
14
+ }
15
+ export declare const KIT_SKILLS: readonly KitSkill[];
16
+ export declare function renderSkillMarkdown(skill: KitSkill): string;
17
+ export declare function writeKitSkills(paths: Pick<AgentKitPaths, "skillsDir">): string[];
18
+ //# sourceMappingURL=skills.d.ts.map