@indigoai-us/hq-cli 5.345.169 → 5.345.171
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 +69 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/dist/command-catalog.generated.d.ts +181 -9
- package/dist/command-catalog.generated.js +224 -10
- package/dist/commands/access.d.ts +32 -1
- package/dist/commands/access.js +119 -5
- package/dist/commands/agent-kit.d.ts +1 -1
- package/dist/commands/agent-kit.js +3 -1
- package/dist/commands/agents.js +2 -1
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +141 -14
- package/dist/commands/cloud.d.ts +78 -1
- package/dist/commands/cloud.js +274 -9
- package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
- package/dist/commands/core-company-index-scope-flag.js +9 -2
- package/dist/commands/core-delegate-w1b.js +5 -2
- package/dist/commands/core-dm-bind-native.js +19 -4
- package/dist/commands/core-rebuild-index-fast.js +24 -6
- package/dist/commands/core.js +18 -6
- package/dist/commands/dm.d.ts +17 -3
- package/dist/commands/dm.js +121 -26
- package/dist/commands/feedback-triage.d.ts +43 -1
- package/dist/commands/feedback-triage.js +123 -23
- package/dist/commands/feedback.d.ts +40 -1
- package/dist/commands/feedback.js +184 -4
- package/dist/commands/files.d.ts +42 -0
- package/dist/commands/files.js +223 -30
- package/dist/commands/integrations-api.d.ts +4 -0
- package/dist/commands/integrations-api.js +18 -2
- package/dist/commands/integrations-connect.d.ts +10 -3
- package/dist/commands/integrations-connect.js +70 -6
- package/dist/commands/integrations-core.d.ts +23 -0
- package/dist/commands/integrations-core.js +100 -0
- package/dist/commands/integrations-manage.js +8 -1
- package/dist/commands/integrations.js +49 -5
- package/dist/commands/lanes-loop.d.ts +20 -0
- package/dist/commands/lanes-loop.js +69 -11
- package/dist/commands/lanes.js +9 -1
- package/dist/commands/mesh.js +6 -0
- package/dist/commands/rescue.d.ts +9 -0
- package/dist/commands/rescue.js +31 -0
- package/dist/commands/run.js +115 -40
- package/dist/commands/scan.d.ts +5 -1
- package/dist/commands/scan.js +16 -4
- package/dist/commands/secrets.js +50 -9
- package/dist/commands/sync-manifest.d.ts +16 -0
- package/dist/commands/sync-manifest.js +17 -1
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2367 -2109
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
- package/dist/lib/agent-kit/inbox-state.js +41 -20
- package/dist/lib/agent-kit/kit-config.d.ts +2 -0
- package/dist/lib/agent-kit/kit-config.js +6 -0
- package/dist/lib/agent-kit/mcp/tools.js +12 -5
- package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
- package/dist/lib/agent-kit/run/heartbeat.js +6 -3
- package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
- package/dist/lib/agent-kit/run/inbox.js +42 -10
- package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
- package/dist/lib/agent-kit/run/request-timeout.js +51 -0
- package/dist/lib/bot/api.d.ts +1 -1
- package/dist/lib/bot/config.d.ts +10 -0
- package/dist/lib/bot/config.js +17 -1
- package/dist/lib/bot/decisions.d.ts +62 -0
- package/dist/lib/bot/decisions.js +228 -0
- package/dist/lib/bot/fleet-local.d.ts +127 -0
- package/dist/lib/bot/fleet-local.js +638 -0
- package/dist/lib/bot/index.d.ts +2 -0
- package/dist/lib/bot/index.js +2 -0
- package/dist/lib/bot/local-config.d.ts +3 -2
- package/dist/lib/bot/local-config.js +5 -2
- package/dist/lib/bot/promote.d.ts +10 -0
- package/dist/lib/bot/promote.js +3 -2
- package/dist/lib/bot/run.d.ts +7 -0
- package/dist/lib/bot/run.js +104 -1
- package/dist/lib/bot/runtime/index.js +8 -4
- package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
- package/dist/lib/bot/runtime/scratch-dir.js +41 -0
- package/dist/lib/caller-principal.d.ts +4 -0
- package/dist/lib/caller-principal.js +40 -0
- package/dist/lib/capacity/collectors/grok.js +8 -2
- package/dist/lib/cli-flag-keys.d.ts +2 -0
- package/dist/lib/cli-flag-keys.js +2 -0
- package/dist/lib/daemon/catalog.d.ts +4 -0
- package/dist/lib/daemon/catalog.js +63 -29
- package/dist/lib/daemon/config.d.ts +7 -0
- package/dist/lib/daemon/config.js +32 -0
- package/dist/lib/daemon/run.d.ts +3 -2
- package/dist/lib/daemon/run.js +23 -3
- package/dist/lib/daemon/supervisor.d.ts +1 -0
- package/dist/lib/daemon/supervisor.js +4 -0
- package/dist/lib/daemon/sync-core-canary.d.ts +12 -0
- package/dist/lib/daemon/sync-core-canary.js +61 -0
- package/dist/lib/doctor/checks/release-links.d.ts +62 -1
- package/dist/lib/doctor/checks/release-links.js +188 -2
- package/dist/lib/doctor/checks/sync-health.d.ts +32 -0
- package/dist/lib/doctor/checks/sync-health.js +54 -1
- package/dist/lib/doctor/fix/apply.d.ts +26 -4
- package/dist/lib/doctor/fix/apply.js +149 -11
- package/dist/lib/doctor/fix/remediation.d.ts +8 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/doctor/types.d.ts +1 -1
- package/dist/lib/doctor/types.js +1 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/index-render/index.d.ts +9 -0
- package/dist/lib/index-render/index.js +65 -5
- package/dist/lib/index-render/session-scope.d.ts +69 -0
- package/dist/lib/index-render/session-scope.js +279 -0
- package/dist/lib/lanes/admission.js +7 -20
- package/dist/lib/lanes/reconcile.js +13 -15
- package/dist/lib/lanes/stale-lock.d.ts +50 -0
- package/dist/lib/lanes/stale-lock.js +105 -0
- package/dist/lib/lanes/store.js +11 -76
- package/dist/lib/mesh/live/daemon/work-subscribe.js +31 -0
- package/dist/lib/notify-recipient.d.ts +10 -4
- package/dist/lib/notify-recipient.js +16 -4
- package/dist/lib/secret-input-link.d.ts +42 -0
- package/dist/lib/secret-input-link.js +93 -0
- package/dist/lib/secret-scan/range.d.ts +57 -0
- package/dist/lib/secret-scan/range.js +266 -0
- package/dist/lib/secret-scan/repo-config.d.ts +18 -3
- package/dist/lib/secret-scan/repo-config.js +135 -2
- package/dist/lib/work-context/organize.d.ts +7 -0
- package/dist/lib/work-context/organize.js +21 -1
- package/dist/run/script-bound-schema.d.ts +27 -0
- package/dist/run/script-bound-schema.js +97 -0
- package/dist/secrets-env-action.d.ts +2 -0
- package/dist/secrets-env-action.js +7 -2
- package/dist/sentry-before-send.js +1 -0
- package/dist/utils/cognito-session.d.ts +3 -30
- package/dist/utils/cognito-session.js +2 -100
- package/dist/utils/machine-principal-state.d.ts +38 -0
- package/dist/utils/machine-principal-state.js +110 -0
- package/dist/utils/safe-link.js +9 -0
- package/dist/utils/script-bound-exec.d.ts +158 -0
- package/dist/utils/script-bound-exec.js +568 -0
- package/dist/utils/sync-watchdog.d.ts +92 -0
- package/dist/utils/sync-watchdog.js +250 -0
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.
|
|
1
|
+
{"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.102","bundleSha256":"54baedb34f8744f3dbbfa970785c6de703a33145081969bed5b266a2fc4d4d9e","formatVersion":1}
|
|
@@ -84,6 +84,23 @@ export interface MergedInbox {
|
|
|
84
84
|
*/
|
|
85
85
|
source: "server" | "server-partial" | "local-mirror";
|
|
86
86
|
}
|
|
87
|
+
/** Notes for a server inbox body that cannot certify a complete read. */
|
|
88
|
+
export declare const AGENT_INBOX_INVALID_RESPONSE_NOTE = "server inbox returned an invalid response; showing the local mirror only, which may be stale";
|
|
89
|
+
export declare const AGENT_INBOX_INVALID_ITEMS_NOTE = "server inbox contained invalid messages; showing valid server messages and the local mirror, which may be incomplete";
|
|
90
|
+
/**
|
|
91
|
+
* Classify a decoded 200 body from GET /v1/agents/{uid}/inbox. Pass
|
|
92
|
+
* `undefined` for a body that failed to decode. `invalid` means the body had
|
|
93
|
+
* no messages array, so nothing from it can be trusted; `partial` means some
|
|
94
|
+
* items were dropped (non-objects or missing id/messageId). Valid items keep
|
|
95
|
+
* their raw fields with `id` set; callers redact before output. Never puts
|
|
96
|
+
* response bytes in a diagnostic. Pure.
|
|
97
|
+
*/
|
|
98
|
+
export declare function classifyAgentInboxBody(body: unknown): {
|
|
99
|
+
status: "ok" | "partial" | "invalid";
|
|
100
|
+
items: Array<Record<string, unknown> & {
|
|
101
|
+
id: string;
|
|
102
|
+
}>;
|
|
103
|
+
};
|
|
87
104
|
export declare function mergeAgentInbox(paths: Pick<AgentKitPaths, "inboxDir">, fetchServerInbox: () => Promise<{
|
|
88
105
|
status: number;
|
|
89
106
|
body: unknown;
|
|
@@ -138,6 +138,38 @@ export function summarizeInboxEntry(e, done) {
|
|
|
138
138
|
done,
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
|
+
/** Notes for a server inbox body that cannot certify a complete read. */
|
|
142
|
+
export const AGENT_INBOX_INVALID_RESPONSE_NOTE = "server inbox returned an invalid response; showing the local mirror only, which may be stale";
|
|
143
|
+
export const AGENT_INBOX_INVALID_ITEMS_NOTE = "server inbox contained invalid messages; showing valid server messages and the local mirror, which may be incomplete";
|
|
144
|
+
/**
|
|
145
|
+
* Classify a decoded 200 body from GET /v1/agents/{uid}/inbox. Pass
|
|
146
|
+
* `undefined` for a body that failed to decode. `invalid` means the body had
|
|
147
|
+
* no messages array, so nothing from it can be trusted; `partial` means some
|
|
148
|
+
* items were dropped (non-objects or missing id/messageId). Valid items keep
|
|
149
|
+
* their raw fields with `id` set; callers redact before output. Never puts
|
|
150
|
+
* response bytes in a diagnostic. Pure.
|
|
151
|
+
*/
|
|
152
|
+
export function classifyAgentInboxBody(body) {
|
|
153
|
+
const messages = body?.messages;
|
|
154
|
+
if (!Array.isArray(messages))
|
|
155
|
+
return { status: "invalid", items: [] };
|
|
156
|
+
const items = [];
|
|
157
|
+
let invalidItems = 0;
|
|
158
|
+
for (const m of messages) {
|
|
159
|
+
if (!m || typeof m !== "object" || Array.isArray(m)) {
|
|
160
|
+
invalidItems += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const raw = m;
|
|
164
|
+
const id = typeof raw.id === "string" && raw.id ? raw.id : typeof raw.messageId === "string" && raw.messageId ? raw.messageId : null;
|
|
165
|
+
if (!id) {
|
|
166
|
+
invalidItems += 1;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
items.push({ ...raw, id });
|
|
170
|
+
}
|
|
171
|
+
return { status: invalidItems > 0 ? "partial" : "ok", items };
|
|
172
|
+
}
|
|
141
173
|
export async function mergeAgentInbox(paths, fetchServerInbox) {
|
|
142
174
|
const byId = new Map();
|
|
143
175
|
for (const e of readMirroredInbox(paths))
|
|
@@ -147,32 +179,21 @@ export async function mergeAgentInbox(paths, fetchServerInbox) {
|
|
|
147
179
|
try {
|
|
148
180
|
const res = await fetchServerInbox();
|
|
149
181
|
if (res.status === 200) {
|
|
150
|
-
const messages = res.body?.messages;
|
|
151
182
|
// A successful HTTP status alone cannot certify an inbox read. An error
|
|
152
183
|
// envelope or an undecodable body must remain visibly unverified; never
|
|
153
184
|
// include response bytes or parser errors in this diagnostic.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
185
|
+
const classified = classifyAgentInboxBody(res.body);
|
|
186
|
+
for (const item of classified.items) {
|
|
187
|
+
if (!byId.has(item.id))
|
|
188
|
+
byId.set(item.id, redactAgentInboxItem(item));
|
|
157
189
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
invalidItems += 1;
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
const raw = m;
|
|
165
|
-
const id = typeof raw.id === "string" ? raw.id : typeof raw.messageId === "string" ? raw.messageId : null;
|
|
166
|
-
if (!id) {
|
|
167
|
-
invalidItems += 1;
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
if (!byId.has(id))
|
|
171
|
-
byId.set(id, redactAgentInboxItem({ ...raw, id }));
|
|
190
|
+
if (classified.status === "invalid") {
|
|
191
|
+
source = "local-mirror";
|
|
192
|
+
note = AGENT_INBOX_INVALID_RESPONSE_NOTE;
|
|
172
193
|
}
|
|
173
|
-
if (
|
|
194
|
+
else if (classified.status === "partial") {
|
|
174
195
|
source = "server-partial";
|
|
175
|
-
note =
|
|
196
|
+
note = AGENT_INBOX_INVALID_ITEMS_NOTE;
|
|
176
197
|
}
|
|
177
198
|
}
|
|
178
199
|
else {
|
|
@@ -12,6 +12,8 @@ export interface KitConfig {
|
|
|
12
12
|
inboxPollMs: number;
|
|
13
13
|
heartbeatIntervalMs: number;
|
|
14
14
|
meshRefreshMs: number;
|
|
15
|
+
/** Per-request timeout for the inbox and heartbeat network calls. */
|
|
16
|
+
requestTimeoutMs: number;
|
|
15
17
|
/** Ack mirrored inbox items on the server after writing them locally. */
|
|
16
18
|
inboxAck: boolean;
|
|
17
19
|
/**
|
|
@@ -8,10 +8,12 @@ import * as fs from "node:fs";
|
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { KIT_COMPONENTS } from "./paths.js";
|
|
10
10
|
import { writePrivateFile } from "./private-file.js";
|
|
11
|
+
import { KIT_REQUEST_TIMEOUT_MIN_MS, KIT_REQUEST_TIMEOUT_MS } from "./run/request-timeout.js";
|
|
11
12
|
const DEFAULT_SYNC_INTERVAL_MS = 5 * 60_000;
|
|
12
13
|
const DEFAULT_INBOX_POLL_MS = 15_000;
|
|
13
14
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 60_000;
|
|
14
15
|
const DEFAULT_MESH_REFRESH_MS = 5 * 60_000;
|
|
16
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = KIT_REQUEST_TIMEOUT_MS;
|
|
15
17
|
/**
|
|
16
18
|
* Kit components whose health this install actually reports. `sync` drops out
|
|
17
19
|
* when the vault is not mirrored to disk — there is no loop to be healthy or
|
|
@@ -30,6 +32,7 @@ export function defaultKitConfig(paths, cliVersion, now = () => new Date()) {
|
|
|
30
32
|
inboxPollMs: DEFAULT_INBOX_POLL_MS,
|
|
31
33
|
heartbeatIntervalMs: DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
32
34
|
meshRefreshMs: DEFAULT_MESH_REFRESH_MS,
|
|
35
|
+
requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
|
|
33
36
|
inboxAck: false,
|
|
34
37
|
syncFiles: false,
|
|
35
38
|
installedAt: now().toISOString(),
|
|
@@ -56,6 +59,9 @@ export function readKitConfig(paths, cliVersion) {
|
|
|
56
59
|
inboxPollMs: num("inboxPollMs"),
|
|
57
60
|
heartbeatIntervalMs: num("heartbeatIntervalMs"),
|
|
58
61
|
meshRefreshMs: num("meshRefreshMs"),
|
|
62
|
+
// 0, negative or missing keeps the default; a tiny positive value would
|
|
63
|
+
// time out every poll, so it is raised to the minimum.
|
|
64
|
+
requestTimeoutMs: Math.max(KIT_REQUEST_TIMEOUT_MIN_MS, num("requestTimeoutMs")),
|
|
59
65
|
inboxAck: raw.inboxAck === true,
|
|
60
66
|
syncFiles: raw.syncFiles === true,
|
|
61
67
|
installedAt: typeof raw.installedAt === "string" ? raw.installedAt : base.installedAt,
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { CLI_VERSION } from "../../../cli-version.js";
|
|
19
19
|
import { peekIdToken } from "../../../utils/id-token.js";
|
|
20
|
-
import { defaultAudienceFor, isAgentUid, isRecipientUid } from "../../../lib/notify-recipient.js";
|
|
20
|
+
import { PERSON_AGENT_AUDIENCE_MESSAGE, callerMayUseAudience, defaultAudienceFor, isAgentUid, isRecipientUid } from "../../../lib/notify-recipient.js";
|
|
21
|
+
import { tokenIsMachinePrincipal } from "../../../lib/caller-principal.js";
|
|
21
22
|
import { redactAgentInboxItem } from "../inbox-redact.js";
|
|
22
23
|
import { markInboxDone, mergeAgentInbox, readDoneIds, summarizeInboxEntry } from "../inbox-state.js";
|
|
23
24
|
import { AttachmentCompanyUnresolvedError, fetchChatAttachment } from "./attachments.js";
|
|
@@ -232,13 +233,20 @@ export function buildAgentMcpTools(clients) {
|
|
|
232
233
|
if (audience !== undefined && !["human", "agent", "both"].includes(audience)) {
|
|
233
234
|
throw new McpToolInputError('"audience" must be human, agent, or both');
|
|
234
235
|
}
|
|
236
|
+
// The token decides who is sending. Normally the agent's machine
|
|
237
|
+
// identity; if it ever resolves to a person login, people only send
|
|
238
|
+
// human messages, so refuse agent/both before any send.
|
|
239
|
+
const token = await clients.getToken();
|
|
240
|
+
const machineCaller = tokenIsMachinePrincipal(token);
|
|
241
|
+
if (!callerMayUseAudience(audience, machineCaller)) {
|
|
242
|
+
return errorResult(PERSON_AGENT_AUDIENCE_MESSAGE);
|
|
243
|
+
}
|
|
235
244
|
if (/^chn_[A-Za-z0-9]+$/.test(to)) {
|
|
236
245
|
// A channel or group DM: post into it, so a reply lands where the
|
|
237
246
|
// message arrived instead of in a private DM to the sender.
|
|
238
|
-
const token = await clients.getToken();
|
|
239
247
|
const res = await clients.apiJson(token, `/v1/notify/channels/${encodeURIComponent(to)}/messages`, {
|
|
240
248
|
method: "POST",
|
|
241
|
-
body: { body: message, audience: audience ?? defaultAudienceFor("channel") },
|
|
249
|
+
body: { body: message, audience: audience ?? defaultAudienceFor("channel", machineCaller) },
|
|
242
250
|
});
|
|
243
251
|
if (res.status < 200 || res.status >= 300) {
|
|
244
252
|
return errorResult(`Channel send failed (${res.status}): ${describeError(res.body)}`);
|
|
@@ -252,8 +260,7 @@ export function buildAgentMcpTools(clients) {
|
|
|
252
260
|
: (() => {
|
|
253
261
|
throw new McpToolInputError('"to" must be an email, prs_… or agt_… uid, or a chn_… channel id');
|
|
254
262
|
})();
|
|
255
|
-
body.audience = audience ?? defaultAudienceFor(isAgentUid(to) ? "agent" : "person");
|
|
256
|
-
const token = await clients.getToken();
|
|
263
|
+
body.audience = audience ?? defaultAudienceFor(isAgentUid(to) ? "agent" : "person", machineCaller);
|
|
257
264
|
const res = await clients.apiJson(token, "/v1/notify/dm", { method: "POST", body });
|
|
258
265
|
if (res.status < 200 || res.status >= 300) {
|
|
259
266
|
return errorResult(`DM send failed (${res.status}): ${describeError(res.body)}`);
|
|
@@ -54,11 +54,14 @@ export interface HeartbeatDeps {
|
|
|
54
54
|
agentUid: string;
|
|
55
55
|
apiBaseUrl: string;
|
|
56
56
|
intervalMs: number;
|
|
57
|
-
|
|
57
|
+
/** Mints or refreshes the machine token; the signal cancels a stuck refresh. */
|
|
58
|
+
getToken: (signal?: AbortSignal) => Promise<string>;
|
|
58
59
|
log: KitLogger;
|
|
59
60
|
/** Components this kit runs; defaults to all of them. */
|
|
60
61
|
components?: readonly KitComponent[];
|
|
61
|
-
post?: (token: string, body: HeartbeatBody, identity?: HeartbeatIdentity) => Promise<number | HeartbeatPostResult>;
|
|
62
|
+
post?: (token: string, body: HeartbeatBody, identity?: HeartbeatIdentity, signal?: AbortSignal) => Promise<number | HeartbeatPostResult>;
|
|
63
|
+
/** Per-request timeout for the token mint and the heartbeat POST (kit.json `requestTimeoutMs`). */
|
|
64
|
+
requestTimeoutMs?: number;
|
|
62
65
|
/** Re-read the configured identity and endpoint once when a freshly minted token differs. */
|
|
63
66
|
getConfiguredAgentIdentity?: () => HeartbeatIdentity;
|
|
64
67
|
now?: () => Date;
|
|
@@ -72,7 +75,7 @@ export interface HeartbeatPostResult {
|
|
|
72
75
|
status: number;
|
|
73
76
|
bodyText?: string;
|
|
74
77
|
}
|
|
75
|
-
export declare function defaultHeartbeatPost(identity: HeartbeatIdentity): (token: string, body: HeartbeatBody, requestIdentity?: HeartbeatIdentity) => Promise<HeartbeatPostResult>;
|
|
78
|
+
export declare function defaultHeartbeatPost(identity: HeartbeatIdentity): (token: string, body: HeartbeatBody, requestIdentity?: HeartbeatIdentity, signal?: AbortSignal) => Promise<HeartbeatPostResult>;
|
|
76
79
|
export declare function beatOnce(deps: HeartbeatDeps): Promise<boolean>;
|
|
77
80
|
export declare function runHeartbeatLoop(deps: HeartbeatDeps): Promise<void>;
|
|
78
81
|
//# sourceMappingURL=heartbeat.d.ts.map
|
|
@@ -17,6 +17,7 @@ import { vaultApiFetch } from "../../../utils/vault-api.js";
|
|
|
17
17
|
import { readComponentStatus } from "../creds.js";
|
|
18
18
|
import { KIT_COMPONENTS } from "../paths.js";
|
|
19
19
|
import { writePrivateFile } from "./../private-file.js";
|
|
20
|
+
import { KIT_REQUEST_TIMEOUT_MS, withRequestTimeout } from "./request-timeout.js";
|
|
20
21
|
/** A component stamp older than this reads as error. */
|
|
21
22
|
export const COMPONENT_STALE_AFTER_MS = {
|
|
22
23
|
sync: 3 * 60 * 60_000, // sync passes every 5 min; three hours of silence is real trouble
|
|
@@ -51,13 +52,14 @@ export function readLastHeartbeat(paths) {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
export function defaultHeartbeatPost(identity) {
|
|
54
|
-
return async (token, body, requestIdentity = identity) => {
|
|
55
|
+
return async (token, body, requestIdentity = identity, signal) => {
|
|
55
56
|
const res = await vaultApiFetch({
|
|
56
57
|
token,
|
|
57
58
|
baseUrl: requestIdentity.apiBaseUrl,
|
|
58
59
|
path: `/v1/agents/${encodeURIComponent(requestIdentity.agentUid)}/heartbeat`,
|
|
59
60
|
method: "POST",
|
|
60
61
|
body: body,
|
|
62
|
+
...(signal ? { signal } : {}),
|
|
61
63
|
});
|
|
62
64
|
return { status: res.status, bodyText: await res.text().catch(() => "") };
|
|
63
65
|
};
|
|
@@ -85,7 +87,8 @@ async function beatOnceResult(deps) {
|
|
|
85
87
|
const now = deps.now ?? (() => new Date());
|
|
86
88
|
const body = buildHeartbeatBody(deps.paths, now, undefined, deps.components ?? KIT_COMPONENTS);
|
|
87
89
|
try {
|
|
88
|
-
const
|
|
90
|
+
const timeoutMs = deps.requestTimeoutMs ?? KIT_REQUEST_TIMEOUT_MS;
|
|
91
|
+
const token = await withRequestTimeout(timeoutMs, undefined, (signal) => deps.getToken(signal));
|
|
89
92
|
const tokenUid = tokenAgentUid(token);
|
|
90
93
|
let identity = { agentUid: deps.agentUid, apiBaseUrl: deps.apiBaseUrl };
|
|
91
94
|
if (tokenUid !== undefined && tokenUid !== identity.agentUid) {
|
|
@@ -103,7 +106,7 @@ async function beatOnceResult(deps) {
|
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
const post = deps.post ?? defaultHeartbeatPost(identity);
|
|
106
|
-
const response = await post(token, body, identity);
|
|
109
|
+
const response = await withRequestTimeout(timeoutMs, undefined, (signal) => post(token, body, identity, signal));
|
|
107
110
|
const result = typeof response === "number" ? { status: response } : response;
|
|
108
111
|
const status = result.status;
|
|
109
112
|
const ok = status >= 200 && status < 300;
|
|
@@ -22,6 +22,8 @@ export declare const SEEN_IDS_NAME = "seen-ids.json";
|
|
|
22
22
|
export declare const INBOX_JSONL_NAME = "inbox.jsonl";
|
|
23
23
|
export declare const MAX_SEEN_IDS = 5000;
|
|
24
24
|
export declare const INBOX_POLL_BACKOFF_MAX_MS = 30000;
|
|
25
|
+
/** A poll slower than this logs one line with its duration and outcome. */
|
|
26
|
+
export declare const INBOX_SLOW_POLL_MS = 10000;
|
|
25
27
|
export interface InboxItem {
|
|
26
28
|
id: string;
|
|
27
29
|
raw: Record<string, unknown>;
|
|
@@ -37,14 +39,17 @@ export interface InboxDeps {
|
|
|
37
39
|
apiBaseUrl: string;
|
|
38
40
|
pollMs: number;
|
|
39
41
|
ack: boolean;
|
|
40
|
-
|
|
42
|
+
/** Mints or refreshes the machine token; the signal cancels a stuck refresh. */
|
|
43
|
+
getToken: (signal?: AbortSignal) => Promise<string>;
|
|
41
44
|
log: KitLogger;
|
|
42
45
|
fetchInbox?: (token: string, signal?: AbortSignal) => Promise<InboxFetchResult>;
|
|
43
|
-
ackItem?: (token: string, id: string) => Promise<number>;
|
|
46
|
+
ackItem?: (token: string, id: string, signal?: AbortSignal) => Promise<number>;
|
|
44
47
|
warmCache?: (token: string, agentUid: string) => Promise<unknown>;
|
|
45
48
|
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
46
49
|
readInboxBackoffEnabled?: () => Promise<boolean>;
|
|
47
50
|
signal?: AbortSignal;
|
|
51
|
+
/** Per-request timeout for the token mint, inbox GET and acks (kit.json `requestTimeoutMs`). */
|
|
52
|
+
requestTimeoutMs?: number;
|
|
48
53
|
companySlug?: string;
|
|
49
54
|
maxPolls?: number;
|
|
50
55
|
/** Override wake config lookup (tests). `null` disables the wake. */
|
|
@@ -71,7 +76,7 @@ export declare function agentApiUrl(apiBaseUrl: string, route: string): string;
|
|
|
71
76
|
export declare function agentInboxUrl(apiBaseUrl: string, agentUid: string): string;
|
|
72
77
|
export declare function truncateBody(text: string, max?: number): string;
|
|
73
78
|
export declare function defaultFetchInbox(agentUid: string, apiBaseUrl: string): (token: string, signal?: AbortSignal) => Promise<InboxFetchResult>;
|
|
74
|
-
export declare function defaultAckItem(agentUid: string, apiBaseUrl: string): (token: string, id: string) => Promise<number>;
|
|
79
|
+
export declare function defaultAckItem(agentUid: string, apiBaseUrl: string): (token: string, id: string, signal?: AbortSignal) => Promise<number>;
|
|
75
80
|
export declare function pollInboxOnce(deps: InboxDeps): Promise<{
|
|
76
81
|
ok: boolean;
|
|
77
82
|
mirrored: number;
|
|
@@ -25,10 +25,13 @@ import { redactAgentInboxItem, scrubInboxMirror, withInboxMirrorLock } from "../
|
|
|
25
25
|
import { mkdirPrivate, writePrivateFile } from "./../private-file.js";
|
|
26
26
|
import { createAgentKitInboxBackoffFlagReader } from "../inbox-poll-backoff-flag.js";
|
|
27
27
|
import { buildWakeNotice, currentWakeState, fireWake, readWakeConfig, shouldWake, } from "../wake.js";
|
|
28
|
+
import { KIT_REQUEST_TIMEOUT_MS, KitRequestTimeoutError, withRequestTimeout } from "./request-timeout.js";
|
|
28
29
|
export const SEEN_IDS_NAME = "seen-ids.json";
|
|
29
30
|
export const INBOX_JSONL_NAME = "inbox.jsonl";
|
|
30
31
|
export const MAX_SEEN_IDS = 5000;
|
|
31
32
|
export const INBOX_POLL_BACKOFF_MAX_MS = 30_000;
|
|
33
|
+
/** A poll slower than this logs one line with its duration and outcome. */
|
|
34
|
+
export const INBOX_SLOW_POLL_MS = 10_000;
|
|
32
35
|
/** Pull `{id}` out of each server item; items without a string id are dropped. */
|
|
33
36
|
export function extractInboxItems(body) {
|
|
34
37
|
const rec = body && typeof body === "object" ? body : {};
|
|
@@ -141,12 +144,13 @@ export function defaultFetchInbox(agentUid, apiBaseUrl) {
|
|
|
141
144
|
};
|
|
142
145
|
}
|
|
143
146
|
export function defaultAckItem(agentUid, apiBaseUrl) {
|
|
144
|
-
return async (token, id) => {
|
|
147
|
+
return async (token, id, signal) => {
|
|
145
148
|
const res = await vaultApiFetch({
|
|
146
149
|
token,
|
|
147
150
|
baseUrl: apiBaseUrl,
|
|
148
151
|
path: `/v1/agents/${encodeURIComponent(agentUid)}/inbox/${encodeURIComponent(id)}/ack`,
|
|
149
152
|
method: "POST",
|
|
153
|
+
...(signal ? { signal } : {}),
|
|
150
154
|
});
|
|
151
155
|
return res.status;
|
|
152
156
|
};
|
|
@@ -154,6 +158,27 @@ export function defaultAckItem(agentUid, apiBaseUrl) {
|
|
|
154
158
|
export async function pollInboxOnce(deps) {
|
|
155
159
|
if (deps.signal?.aborted)
|
|
156
160
|
return { ok: false, mirrored: 0 };
|
|
161
|
+
const startedAt = Date.now();
|
|
162
|
+
const result = await pollInboxOnceTimed(deps);
|
|
163
|
+
const elapsed = Date.now() - startedAt;
|
|
164
|
+
// One line per slow or timed-out poll: duration and outcome only, never a
|
|
165
|
+
// token or message content. A timeout already logged its own line.
|
|
166
|
+
if (result.outcome !== "timeout" && result.outcome !== "aborted" && elapsed >= INBOX_SLOW_POLL_MS) {
|
|
167
|
+
deps.log("warn", `inbox poll slow: ${elapsed}ms outcome=${result.outcome}`);
|
|
168
|
+
}
|
|
169
|
+
return { ok: result.ok, mirrored: result.mirrored };
|
|
170
|
+
}
|
|
171
|
+
async function pollInboxOnceTimed(deps) {
|
|
172
|
+
const startedAt = Date.now();
|
|
173
|
+
const timeoutMs = deps.requestTimeoutMs ?? KIT_REQUEST_TIMEOUT_MS;
|
|
174
|
+
const bounded = (fn) => withRequestTimeout(timeoutMs, deps.signal, fn);
|
|
175
|
+
// A timed-out step is a failed poll: stamp error and let the loop retry on
|
|
176
|
+
// the base interval. It never falls through to the empty-inbox path.
|
|
177
|
+
const timedOut = (step) => {
|
|
178
|
+
writeComponentStatus(deps.paths, "inbox", "error");
|
|
179
|
+
deps.log("error", `inbox poll timed out: ${step} after ${Date.now() - startedAt}ms (limit ${timeoutMs}ms) outcome=timeout`);
|
|
180
|
+
return { ok: false, mirrored: 0, outcome: "timeout" };
|
|
181
|
+
};
|
|
157
182
|
// Mirrors written before credential redaction are scrubbed on the next poll,
|
|
158
183
|
// before a wake can send the bot at the files. A write or rename error is
|
|
159
184
|
// counted and retried on the next poll. The log is a count and errno codes,
|
|
@@ -178,23 +203,29 @@ export async function pollInboxOnce(deps) {
|
|
|
178
203
|
const warmCache = deps.warmCache ?? (async () => { });
|
|
179
204
|
let token;
|
|
180
205
|
try {
|
|
181
|
-
token = await deps.getToken();
|
|
206
|
+
token = await bounded((signal) => deps.getToken(signal));
|
|
182
207
|
}
|
|
183
208
|
catch (err) {
|
|
209
|
+
if (deps.signal?.aborted)
|
|
210
|
+
return { ok: false, mirrored: 0, outcome: "aborted" };
|
|
211
|
+
if (err instanceof KitRequestTimeoutError)
|
|
212
|
+
return timedOut("token mint");
|
|
184
213
|
writeComponentStatus(deps.paths, "inbox", "error");
|
|
185
214
|
deps.log("error", `inbox token mint failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
186
|
-
return { ok: false, mirrored: 0 };
|
|
215
|
+
return { ok: false, mirrored: 0, outcome: "error" };
|
|
187
216
|
}
|
|
188
217
|
let res;
|
|
189
218
|
try {
|
|
190
|
-
res = await fetchInbox(token,
|
|
219
|
+
res = await bounded((signal) => fetchInbox(token, signal));
|
|
191
220
|
}
|
|
192
221
|
catch (err) {
|
|
193
222
|
if (deps.signal?.aborted)
|
|
194
|
-
return { ok: false, mirrored: 0 };
|
|
223
|
+
return { ok: false, mirrored: 0, outcome: "aborted" };
|
|
224
|
+
if (err instanceof KitRequestTimeoutError)
|
|
225
|
+
return timedOut("inbox GET");
|
|
195
226
|
writeComponentStatus(deps.paths, "inbox", "error");
|
|
196
227
|
deps.log("error", `inbox fetch failed: GET ${agentInboxUrl(deps.apiBaseUrl, deps.agentUid)}: ${err instanceof Error ? err.message : String(err)}`);
|
|
197
|
-
return { ok: false, mirrored: 0 };
|
|
228
|
+
return { ok: false, mirrored: 0, outcome: "error" };
|
|
198
229
|
}
|
|
199
230
|
if (res.status !== 200) {
|
|
200
231
|
writeComponentStatus(deps.paths, "inbox", "error");
|
|
@@ -202,7 +233,7 @@ export async function pollInboxOnce(deps) {
|
|
|
202
233
|
// Non-OK bodies are not constrained to an error envelope and could carry
|
|
203
234
|
// inbox items. Keep the status and route, but do not copy the body to logs.
|
|
204
235
|
deps.log("warn", `inbox poll GET ${url} → ${res.status}`);
|
|
205
|
-
return { ok: false, mirrored: 0 };
|
|
236
|
+
return { ok: false, mirrored: 0, outcome: "error" };
|
|
206
237
|
}
|
|
207
238
|
writeComponentStatus(deps.paths, "inbox", "ok");
|
|
208
239
|
const seen = readSeenIds(deps.paths);
|
|
@@ -211,12 +242,13 @@ export async function pollInboxOnce(deps) {
|
|
|
211
242
|
mirrorInboxItem(deps.paths, item);
|
|
212
243
|
if (deps.ack) {
|
|
213
244
|
try {
|
|
214
|
-
const status = await ackItem(token, item.id);
|
|
245
|
+
const status = await bounded((signal) => ackItem(token, item.id, signal));
|
|
215
246
|
if (status < 200 || status >= 300)
|
|
216
247
|
deps.log("warn", `ack ${item.id} → ${status}`);
|
|
217
248
|
}
|
|
218
249
|
catch (err) {
|
|
219
|
-
|
|
250
|
+
const reason = err instanceof KitRequestTimeoutError ? err.message : err instanceof Error ? err.message : String(err);
|
|
251
|
+
deps.log("warn", `ack ${item.id} failed: ${reason}`);
|
|
220
252
|
}
|
|
221
253
|
}
|
|
222
254
|
// Persist each completed item before starting another one. If shutdown
|
|
@@ -234,7 +266,7 @@ export async function pollInboxOnce(deps) {
|
|
|
234
266
|
}
|
|
235
267
|
}
|
|
236
268
|
await maybeWake(deps, fresh.length);
|
|
237
|
-
return { ok: true, mirrored: fresh.length };
|
|
269
|
+
return { ok: true, mirrored: fresh.length, outcome: "ok" };
|
|
238
270
|
}
|
|
239
271
|
/** Poke the bot's framework when mail is new, or still pending past the retry window. */
|
|
240
272
|
async function maybeWake(deps, freshCount) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-request bound for the kit services' network calls.
|
|
3
|
+
*
|
|
4
|
+
* A poll on a stale or half-open connection otherwise waits until the OS
|
|
5
|
+
* drops the socket, which can take hours (row 66ce5718). Each call gets its
|
|
6
|
+
* own deadline combined with the service's shutdown signal. The deadline
|
|
7
|
+
* uses setTimeout, not AbortSignal.timeout, so tests can drive it with fake
|
|
8
|
+
* timers; `anySignal` keeps the CLI working on Node 20.0.
|
|
9
|
+
*/
|
|
10
|
+
/** Default per-request timeout for kit network calls (kit.json `requestTimeoutMs`). */
|
|
11
|
+
export declare const KIT_REQUEST_TIMEOUT_MS = 30000;
|
|
12
|
+
/** Lowest accepted kit.json `requestTimeoutMs`; smaller values are raised to this. */
|
|
13
|
+
export declare const KIT_REQUEST_TIMEOUT_MIN_MS = 1000;
|
|
14
|
+
export declare class KitRequestTimeoutError extends Error {
|
|
15
|
+
readonly timeoutMs: number;
|
|
16
|
+
constructor(timeoutMs: number);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run `fn` with a signal that aborts on shutdown or after `timeoutMs`.
|
|
20
|
+
* The result is also raced against that signal, so a call that ignores the
|
|
21
|
+
* signal (such as a token refresh waiting on a lock) still returns on time.
|
|
22
|
+
* Rejects with KitRequestTimeoutError on the deadline, or with the shutdown
|
|
23
|
+
* signal's reason on shutdown.
|
|
24
|
+
*/
|
|
25
|
+
export declare function withRequestTimeout<T>(timeoutMs: number, shutdown: AbortSignal | undefined, fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
26
|
+
//# sourceMappingURL=request-timeout.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-request bound for the kit services' network calls.
|
|
3
|
+
*
|
|
4
|
+
* A poll on a stale or half-open connection otherwise waits until the OS
|
|
5
|
+
* drops the socket, which can take hours (row 66ce5718). Each call gets its
|
|
6
|
+
* own deadline combined with the service's shutdown signal. The deadline
|
|
7
|
+
* uses setTimeout, not AbortSignal.timeout, so tests can drive it with fake
|
|
8
|
+
* timers; `anySignal` keeps the CLI working on Node 20.0.
|
|
9
|
+
*/
|
|
10
|
+
import { anySignal } from "../../../utils/vault-api.js";
|
|
11
|
+
/** Default per-request timeout for kit network calls (kit.json `requestTimeoutMs`). */
|
|
12
|
+
export const KIT_REQUEST_TIMEOUT_MS = 30_000;
|
|
13
|
+
/** Lowest accepted kit.json `requestTimeoutMs`; smaller values are raised to this. */
|
|
14
|
+
export const KIT_REQUEST_TIMEOUT_MIN_MS = 1_000;
|
|
15
|
+
export class KitRequestTimeoutError extends Error {
|
|
16
|
+
timeoutMs;
|
|
17
|
+
constructor(timeoutMs) {
|
|
18
|
+
super(`request timed out after ${timeoutMs}ms`);
|
|
19
|
+
this.timeoutMs = timeoutMs;
|
|
20
|
+
this.name = "KitRequestTimeoutError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run `fn` with a signal that aborts on shutdown or after `timeoutMs`.
|
|
25
|
+
* The result is also raced against that signal, so a call that ignores the
|
|
26
|
+
* signal (such as a token refresh waiting on a lock) still returns on time.
|
|
27
|
+
* Rejects with KitRequestTimeoutError on the deadline, or with the shutdown
|
|
28
|
+
* signal's reason on shutdown.
|
|
29
|
+
*/
|
|
30
|
+
export async function withRequestTimeout(timeoutMs, shutdown, fn) {
|
|
31
|
+
const deadline = new AbortController();
|
|
32
|
+
const timer = setTimeout(() => deadline.abort(new KitRequestTimeoutError(timeoutMs)), timeoutMs);
|
|
33
|
+
const combined = anySignal(shutdown ? [shutdown, deadline.signal] : [deadline.signal]);
|
|
34
|
+
let onAbort;
|
|
35
|
+
try {
|
|
36
|
+
const aborted = new Promise((_, reject) => {
|
|
37
|
+
if (combined.signal.aborted)
|
|
38
|
+
return reject(combined.signal.reason);
|
|
39
|
+
onAbort = () => reject(combined.signal.reason);
|
|
40
|
+
combined.signal.addEventListener("abort", onAbort, { once: true });
|
|
41
|
+
});
|
|
42
|
+
return await Promise.race([fn(combined.signal), aborted]);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
if (onAbort)
|
|
47
|
+
combined.signal.removeEventListener("abort", onAbort);
|
|
48
|
+
combined.dispose();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=request-timeout.js.map
|
package/dist/lib/bot/api.d.ts
CHANGED
package/dist/lib/bot/config.d.ts
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare const BOT_RUNTIMES: readonly ["claude", "codex", "grok"];
|
|
5
5
|
export type BotRuntimeId = (typeof BOT_RUNTIMES)[number];
|
|
6
|
+
export declare const HQ_FLEET_RUNTIME_ENGINE_ID: "hq-fleet-runtime";
|
|
7
|
+
export interface BotEngineMetadata {
|
|
8
|
+
id: typeof HQ_FLEET_RUNTIME_ENGINE_ID;
|
|
9
|
+
distribution: "hq-fleet-local";
|
|
10
|
+
version: string;
|
|
11
|
+
artifactSha256: string;
|
|
12
|
+
runtimeCommit: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function isBotEngineMetadata(value: unknown): value is BotEngineMetadata;
|
|
6
15
|
export declare function isBotRuntimeId(value: unknown): value is BotRuntimeId;
|
|
7
16
|
/**
|
|
8
17
|
* What a bot is (owner decisions, 2026-09-15):
|
|
@@ -80,6 +89,7 @@ export interface BotConfig {
|
|
|
80
89
|
/** The person (prs_) who created and owns the bot. */
|
|
81
90
|
ownerUid: string;
|
|
82
91
|
runtime: BotRuntimeId;
|
|
92
|
+
engine?: BotEngineMetadata;
|
|
83
93
|
/**
|
|
84
94
|
* "personal" (acts as its owner everywhere) or "company" (acts as itself,
|
|
85
95
|
* member of `companies`). Missing on bots created before kinds existed:
|
package/dist/lib/bot/config.js
CHANGED
|
@@ -5,6 +5,20 @@ import * as fs from "node:fs";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { botConfigPath } from "./paths.js";
|
|
7
7
|
export const BOT_RUNTIMES = ["claude", "codex", "grok"];
|
|
8
|
+
export const HQ_FLEET_RUNTIME_ENGINE_ID = "hq-fleet-runtime";
|
|
9
|
+
export function isBotEngineMetadata(value) {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
11
|
+
return false;
|
|
12
|
+
const engine = value;
|
|
13
|
+
return (engine.id === HQ_FLEET_RUNTIME_ENGINE_ID &&
|
|
14
|
+
engine.distribution === "hq-fleet-local" &&
|
|
15
|
+
typeof engine.version === "string" &&
|
|
16
|
+
/^\d+\.\d+\.\d+$/.test(engine.version) &&
|
|
17
|
+
typeof engine.artifactSha256 === "string" &&
|
|
18
|
+
/^[a-f0-9]{64}$/.test(engine.artifactSha256) &&
|
|
19
|
+
typeof engine.runtimeCommit === "string" &&
|
|
20
|
+
/^[a-f0-9]{40}$/.test(engine.runtimeCommit));
|
|
21
|
+
}
|
|
8
22
|
export function isBotRuntimeId(value) {
|
|
9
23
|
return typeof value === "string" && BOT_RUNTIMES.includes(value);
|
|
10
24
|
}
|
|
@@ -188,9 +202,10 @@ export function readBotConfig(dir) {
|
|
|
188
202
|
}
|
|
189
203
|
if (!isBotRuntimeId(raw.runtime))
|
|
190
204
|
return null;
|
|
191
|
-
const { intro: rawIntro, kickoff: rawKickoff, kind: _kind, companies: _companies, ...rest } = raw;
|
|
205
|
+
const { intro: rawIntro, kickoff: rawKickoff, kind: _kind, companies: _companies, engine: rawEngine, ...rest } = raw;
|
|
192
206
|
const intro = typeof rawIntro === "string" && rawIntro.trim() ? rawIntro : undefined;
|
|
193
207
|
const kickoff = typeof rawKickoff === "string" && rawKickoff.trim() ? rawKickoff : undefined;
|
|
208
|
+
const engine = isBotEngineMetadata(rawEngine) ? rawEngine : undefined;
|
|
194
209
|
// Older bot.json files carry no kind: they read (and, on the first patch,
|
|
195
210
|
// persist) as personal, so every reader sees a kind.
|
|
196
211
|
const kind = effectiveBotKind(raw);
|
|
@@ -201,6 +216,7 @@ export function readBotConfig(dir) {
|
|
|
201
216
|
...(kind === "company" ? { companies } : {}),
|
|
202
217
|
...(intro ? { intro } : {}),
|
|
203
218
|
...(kickoff ? { kickoff } : {}),
|
|
219
|
+
...(engine ? { engine } : {}),
|
|
204
220
|
enabled: raw.enabled !== false,
|
|
205
221
|
};
|
|
206
222
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Decision, type DecisionOption } from "../decisions/client.js";
|
|
2
|
+
export interface DecisionDmClient {
|
|
3
|
+
list(open?: boolean): Promise<Decision[]>;
|
|
4
|
+
answer(id: string, choiceId: string, source: "thread"): Promise<Decision>;
|
|
5
|
+
}
|
|
6
|
+
export interface DecisionDmInput {
|
|
7
|
+
text: string;
|
|
8
|
+
speakerUid: string | undefined;
|
|
9
|
+
rootEventId?: string;
|
|
10
|
+
ownerUid: string;
|
|
11
|
+
agentUid: string;
|
|
12
|
+
/** The owner's company uids. Null when they could not be read: refuse. */
|
|
13
|
+
companyUids: readonly string[] | null;
|
|
14
|
+
nowMs: number;
|
|
15
|
+
/** Decision ids already restated after 24 hours. */
|
|
16
|
+
restated: ReadonlySet<string>;
|
|
17
|
+
}
|
|
18
|
+
export type DecisionDmResult = {
|
|
19
|
+
kind: "none";
|
|
20
|
+
notLive?: true;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "reply";
|
|
23
|
+
text: string;
|
|
24
|
+
decisionId?: string;
|
|
25
|
+
recorded: boolean;
|
|
26
|
+
notLive?: true;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "model";
|
|
29
|
+
note: string;
|
|
30
|
+
decisionId: string;
|
|
31
|
+
restate?: string;
|
|
32
|
+
};
|
|
33
|
+
export declare const DECISIONS_NOT_LIVE_REPLY = "Decisions are not live on this HQ server yet (the decisions API returned 404), so I did not record that pick.";
|
|
34
|
+
export declare const RESTATE_AFTER_MS: number;
|
|
35
|
+
/** Same key hq-pro uses for the owner-bot DM pair. */
|
|
36
|
+
export declare function decisionTalkThreadId(ownerUid: string, botUid: string): string;
|
|
37
|
+
export declare function looksLikePick(text: string): boolean;
|
|
38
|
+
export declare function matchDecisionPick(text: string, options: readonly DecisionOption[], opts?: {
|
|
39
|
+
inCardThread?: boolean;
|
|
40
|
+
}): {
|
|
41
|
+
kind: "pick";
|
|
42
|
+
option: DecisionOption;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "ambiguous";
|
|
45
|
+
options: DecisionOption[];
|
|
46
|
+
} | {
|
|
47
|
+
kind: "none";
|
|
48
|
+
};
|
|
49
|
+
/** Full step, for the one restate after 24 hours. Never says "waiting on". */
|
|
50
|
+
export declare function restateDecision(decision: Decision): string;
|
|
51
|
+
export declare function decisionContextNote(decision: Decision): string;
|
|
52
|
+
export declare function handleDecisionDm(input: DecisionDmInput, client: DecisionDmClient): Promise<DecisionDmResult>;
|
|
53
|
+
export interface DecisionTalkState {
|
|
54
|
+
v: 1;
|
|
55
|
+
restated: Record<string, string>;
|
|
56
|
+
/** Replies decided but not yet delivered, by inbox message id. Resent on retry so a saved pick is never re-run. */
|
|
57
|
+
pending?: Record<string, string>;
|
|
58
|
+
}
|
|
59
|
+
export declare function decisionTalkStatePath(dir: string): string;
|
|
60
|
+
export declare function readDecisionTalkState(dir: string): DecisionTalkState;
|
|
61
|
+
export declare function writeDecisionTalkState(dir: string, state: DecisionTalkState): void;
|
|
62
|
+
//# sourceMappingURL=decisions.d.ts.map
|