@indigoai-us/hq-cli 5.109.16 → 5.111.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/assets/bot-workers/setup/context/USER-GUIDE.md +363 -0
- package/assets/bot-workers/setup/context/quick-reference.md +199 -0
- package/assets/bot-workers/setup/skills/first-company.md +71 -0
- package/assets/bot-workers/setup/skills/standing-help.md +74 -0
- package/assets/bot-workers/setup/worker.yaml +422 -0
- package/dist/commands/bot-continuity.d.ts +28 -0
- package/dist/commands/bot-continuity.js +68 -0
- package/dist/commands/bot.d.ts +73 -0
- package/dist/commands/bot.js +776 -0
- package/dist/commands/skill.d.ts +12 -3
- package/dist/commands/skill.js +79 -3
- package/dist/commands/workers.d.ts +2 -14
- package/dist/commands/workers.js +2 -8
- package/dist/lib/bot/api.d.ts +202 -0
- package/dist/lib/bot/api.js +202 -0
- package/dist/lib/bot/company-bind.d.ts +27 -0
- package/dist/lib/bot/company-bind.js +62 -0
- package/dist/lib/bot/config.d.ts +106 -0
- package/dist/lib/bot/config.js +141 -0
- package/dist/lib/bot/continuity-download.d.ts +28 -0
- package/dist/lib/bot/continuity-download.js +75 -0
- package/dist/lib/bot/continuity-install.d.ts +14 -0
- package/dist/lib/bot/continuity-install.js +101 -0
- package/dist/lib/bot/continuity.d.ts +66 -0
- package/dist/lib/bot/continuity.js +301 -0
- package/dist/lib/bot/creds.d.ts +24 -0
- package/dist/lib/bot/creds.js +51 -0
- package/dist/lib/bot/daemon.d.ts +75 -0
- package/dist/lib/bot/daemon.js +316 -0
- package/dist/lib/bot/inbox-state.d.ts +18 -0
- package/dist/lib/bot/inbox-state.js +51 -0
- package/dist/lib/bot/index.d.ts +16 -0
- package/dist/lib/bot/index.js +16 -0
- package/dist/lib/bot/inflight.d.ts +40 -0
- package/dist/lib/bot/inflight.js +44 -0
- package/dist/lib/bot/log.d.ts +13 -0
- package/dist/lib/bot/log.js +59 -0
- package/dist/lib/bot/owner-context.d.ts +75 -0
- package/dist/lib/bot/owner-context.js +151 -0
- package/dist/lib/bot/paths.d.ts +61 -0
- package/dist/lib/bot/paths.js +103 -0
- package/dist/lib/bot/progress.d.ts +84 -0
- package/dist/lib/bot/progress.js +167 -0
- package/dist/lib/bot/promote.d.ts +16 -0
- package/dist/lib/bot/promote.js +106 -0
- package/dist/lib/bot/promotion-hold.d.ts +24 -0
- package/dist/lib/bot/promotion-hold.js +103 -0
- package/dist/lib/bot/promotion-receipt.d.ts +9 -0
- package/dist/lib/bot/promotion-receipt.js +56 -0
- package/dist/lib/bot/promotion-upload.d.ts +16 -0
- package/dist/lib/bot/promotion-upload.js +65 -0
- package/dist/lib/bot/prompt.d.ts +103 -0
- package/dist/lib/bot/prompt.js +329 -0
- package/dist/lib/bot/room-policy.d.ts +53 -0
- package/dist/lib/bot/room-policy.js +73 -0
- package/dist/lib/bot/run.d.ts +98 -0
- package/dist/lib/bot/run.js +787 -0
- package/dist/lib/bot/runtime/claude.d.ts +49 -0
- package/dist/lib/bot/runtime/claude.js +151 -0
- package/dist/lib/bot/runtime/codex.d.ts +28 -0
- package/dist/lib/bot/runtime/codex.js +147 -0
- package/dist/lib/bot/runtime/grok.d.ts +16 -0
- package/dist/lib/bot/runtime/grok.js +67 -0
- package/dist/lib/bot/runtime/index.d.ts +35 -0
- package/dist/lib/bot/runtime/index.js +279 -0
- package/dist/lib/bot/runtime/messages-stream.d.ts +27 -0
- package/dist/lib/bot/runtime/messages-stream.js +85 -0
- package/dist/lib/bot/runtime/types.d.ts +136 -0
- package/dist/lib/bot/runtime/types.js +51 -0
- package/dist/lib/bot/scaffold.d.ts +38 -0
- package/dist/lib/bot/scaffold.js +94 -0
- package/dist/lib/bot/session.d.ts +19 -0
- package/dist/lib/bot/session.js +39 -0
- package/dist/lib/bot/status.d.ts +40 -0
- package/dist/lib/bot/status.js +66 -0
- package/dist/lib/bot/worker-source.d.ts +66 -0
- package/dist/lib/bot/worker-source.js +283 -0
- package/dist/lib/workers-registry/read.d.ts +15 -0
- package/dist/lib/workers-registry/read.js +17 -0
- package/dist/register-all.js +2 -0
- package/package.json +2 -1
package/dist/commands/skill.d.ts
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* immutable UID, surfaces its generated runtime wrapper, and syncs it.
|
|
6
6
|
* `hq skill propose <uid|path> --message "…"` posts a whole-skill comment to
|
|
7
7
|
* the same improvement thread shown in HQ Console. It never uploads a modified
|
|
8
|
-
* SKILL.md and cannot overwrite live content.
|
|
9
|
-
*
|
|
8
|
+
* SKILL.md and cannot overwrite live content. `hq skill delete <target>` removes
|
|
9
|
+
* a skill and its files for the whole company (confirmation unless --yes).
|
|
10
|
+
* Structured suggest/list/review commands intentionally are not registered.
|
|
10
11
|
*/
|
|
11
12
|
import { Command } from "commander";
|
|
12
13
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
13
|
-
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
14
|
+
import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
|
|
14
15
|
import { surfaceCompanySkill } from "../lib/company-skill-wrapper.js";
|
|
15
16
|
export declare const SKILL_UID_PATTERN: RegExp;
|
|
16
17
|
export declare const SKILL_SLUG_PATTERN: RegExp;
|
|
@@ -98,9 +99,17 @@ export declare function mapSkillError(status: number, body: Record<string, unkno
|
|
|
98
99
|
export declare function skillApiError(status: number, body: Record<string, unknown>, opts?: {
|
|
99
100
|
machineIdentity?: boolean;
|
|
100
101
|
}): Error;
|
|
102
|
+
export type SkillConfirmFn = (message: string) => Promise<boolean>;
|
|
103
|
+
/**
|
|
104
|
+
* Resolve a delete target: a `skl_…` uid, a SKILL.md path or its directory, or
|
|
105
|
+
* a bare skill slug under `companies/<company>/skills/<slug>/`.
|
|
106
|
+
*/
|
|
107
|
+
export declare function resolveDeleteTarget(target: string, cwd: string, hqRoot: string, companySlug: string): string;
|
|
101
108
|
interface SkillCommandDeps {
|
|
102
109
|
ensureToken?: typeof ensureCognitoToken;
|
|
103
110
|
apiFetch?: typeof vaultApiFetch;
|
|
111
|
+
confirm?: SkillConfirmFn;
|
|
112
|
+
resolveCompanyUid?: typeof getCompanyUid;
|
|
104
113
|
cwd?: () => string;
|
|
105
114
|
hqRoot?: string;
|
|
106
115
|
syncFile?: (input: SkillSyncInput) => Promise<SkillSyncResult>;
|
package/dist/commands/skill.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* immutable UID, surfaces its generated runtime wrapper, and syncs it.
|
|
6
6
|
* `hq skill propose <uid|path> --message "…"` posts a whole-skill comment to
|
|
7
7
|
* the same improvement thread shown in HQ Console. It never uploads a modified
|
|
8
|
-
* SKILL.md and cannot overwrite live content.
|
|
9
|
-
*
|
|
8
|
+
* SKILL.md and cannot overwrite live content. `hq skill delete <target>` removes
|
|
9
|
+
* a skill and its files for the whole company (confirmation unless --yes).
|
|
10
|
+
* Structured suggest/list/review commands intentionally are not registered.
|
|
10
11
|
*/
|
|
11
12
|
import * as fs from "node:fs";
|
|
12
13
|
import * as path from "node:path";
|
|
@@ -14,7 +15,8 @@ import chalk from "chalk";
|
|
|
14
15
|
import yaml from "js-yaml";
|
|
15
16
|
import { share } from "@indigoai-us/hq-cloud";
|
|
16
17
|
import { ensureCognitoToken, DEFAULT_HQ_ROOT, buildVaultConfig, isMachineIdentity, } from "../utils/cognito-session.js";
|
|
17
|
-
import
|
|
18
|
+
import * as readline from "node:readline";
|
|
19
|
+
import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
|
|
18
20
|
import { surfaceCompanySkill } from "../lib/company-skill-wrapper.js";
|
|
19
21
|
import { AuthError } from "../utils/auth-error.js";
|
|
20
22
|
import { redactErrorText } from "../utils/redact-error-text.js";
|
|
@@ -322,6 +324,31 @@ export function skillApiError(status, body, opts = {}) {
|
|
|
322
324
|
}
|
|
323
325
|
return new Error(message);
|
|
324
326
|
}
|
|
327
|
+
function realConfirm(message) {
|
|
328
|
+
const rl = readline.createInterface({
|
|
329
|
+
input: process.stdin,
|
|
330
|
+
output: process.stdout,
|
|
331
|
+
});
|
|
332
|
+
return new Promise((resolve) => {
|
|
333
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
334
|
+
rl.close();
|
|
335
|
+
resolve(/^y(es)?$/i.test(answer.trim()));
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Resolve a delete target: a `skl_…` uid, a SKILL.md path or its directory, or
|
|
341
|
+
* a bare skill slug under `companies/<company>/skills/<slug>/`.
|
|
342
|
+
*/
|
|
343
|
+
export function resolveDeleteTarget(target, cwd, hqRoot, companySlug) {
|
|
344
|
+
if (SKILL_UID_PATTERN.test(target))
|
|
345
|
+
return target;
|
|
346
|
+
const asPath = path.resolve(cwd, target);
|
|
347
|
+
if (!fs.existsSync(asPath) && SKILL_SLUG_PATTERN.test(target)) {
|
|
348
|
+
return resolveSkillUid(canonicalCompanySkillPath(hqRoot, companySlug, target), cwd);
|
|
349
|
+
}
|
|
350
|
+
return resolveSkillUid(target, cwd);
|
|
351
|
+
}
|
|
325
352
|
export function registerSkillCommand(program, deps = {}) {
|
|
326
353
|
const ensureToken = deps.ensureToken ?? ensureCognitoToken;
|
|
327
354
|
const apiFetch = deps.apiFetch ?? vaultApiFetch;
|
|
@@ -329,6 +356,8 @@ export function registerSkillCommand(program, deps = {}) {
|
|
|
329
356
|
const hqRoot = deps.hqRoot ?? DEFAULT_HQ_ROOT;
|
|
330
357
|
const syncFile = deps.syncFile ?? defaultSyncFile;
|
|
331
358
|
const surfaceSkillFn = deps.surfaceSkillFn ?? surfaceCompanySkill;
|
|
359
|
+
const confirm = deps.confirm ?? realConfirm;
|
|
360
|
+
const resolveCompanyUid = deps.resolveCompanyUid ?? getCompanyUid;
|
|
332
361
|
const skill = program
|
|
333
362
|
.command("skill")
|
|
334
363
|
.description("Create company skills and discuss improvements")
|
|
@@ -516,6 +545,53 @@ export function registerSkillCommand(program, deps = {}) {
|
|
|
516
545
|
console.log(` Message: ${posted.body}`);
|
|
517
546
|
console.log(chalk.dim(" Review it in HQ Console → Skills → Improvements"));
|
|
518
547
|
});
|
|
548
|
+
skill
|
|
549
|
+
.command("delete <target>")
|
|
550
|
+
.description("Delete a company skill and all of its files for everyone (skl_… uid, SKILL.md path, or skill slug). Prompts for confirmation unless --yes.")
|
|
551
|
+
.option("-y, --yes", "Skip the confirmation prompt (for scripts)")
|
|
552
|
+
.action(async (target, opts) => {
|
|
553
|
+
const parentOpts = skill.opts();
|
|
554
|
+
const resolvedRoot = path.resolve(parentOpts.hqRoot ?? hqRoot);
|
|
555
|
+
const companySlug = resolveCompanySlug(parentOpts.company, resolvedRoot);
|
|
556
|
+
const skillUid = resolveDeleteTarget(target, cwd(), resolvedRoot, companySlug);
|
|
557
|
+
const token = await ensureToken();
|
|
558
|
+
const companyUid = await resolveCompanyUid(token, companySlug);
|
|
559
|
+
const skillPath = `/v1/skills/${encodeURIComponent(companyUid)}/${encodeURIComponent(skillUid)}`;
|
|
560
|
+
const detail = await apiFetch({ token, path: skillPath, method: "GET" });
|
|
561
|
+
if (!detail.ok) {
|
|
562
|
+
const body = (await detail.json().catch(() => ({})));
|
|
563
|
+
throw skillApiError(detail.status, body, {
|
|
564
|
+
machineIdentity: isMachineIdentity(),
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const { skill: record } = (await detail.json());
|
|
568
|
+
const name = typeof record?.name === "string" ? record.name : skillUid;
|
|
569
|
+
if (opts.yes !== true) {
|
|
570
|
+
if (!process.stdin.isTTY) {
|
|
571
|
+
throw localSkillError(`Refusing to delete '${name}' without confirmation. Re-run with --yes.`);
|
|
572
|
+
}
|
|
573
|
+
const ok = await confirm(`Delete skill '${name}' (${skillUid}) and all of its files for everyone in ${companySlug}? This can't be undone from the CLI.`);
|
|
574
|
+
if (!ok) {
|
|
575
|
+
console.log("Cancelled — nothing was deleted.");
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const response = await apiFetch({ token, path: skillPath, method: "DELETE" });
|
|
580
|
+
if (!response.ok) {
|
|
581
|
+
const body = (await response.json().catch(() => ({})));
|
|
582
|
+
throw skillApiError(response.status, body, {
|
|
583
|
+
machineIdentity: isMachineIdentity(),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
const result = (await response.json());
|
|
587
|
+
console.log(chalk.green(`Skill deleted: ${name}`));
|
|
588
|
+
console.log(` Skill: ${skillUid}`);
|
|
589
|
+
console.log(` Files: ${typeof result.filesDeleted === "number" ? result.filesDeleted : 0} removed`);
|
|
590
|
+
if (result.tombstoneIncomplete === true) {
|
|
591
|
+
console.warn(chalk.yellow("⚠ Some synced computers may not be told to remove their copy. Delete it locally if it reappears."));
|
|
592
|
+
}
|
|
593
|
+
console.log(chalk.dim(" Local copies are removed on the next sync (hq sync now)."));
|
|
594
|
+
});
|
|
519
595
|
return skill;
|
|
520
596
|
}
|
|
521
597
|
//# sourceMappingURL=skill.js.map
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
path: string;
|
|
5
|
-
type: string;
|
|
6
|
-
visibility: string;
|
|
7
|
-
company?: string;
|
|
8
|
-
team?: string;
|
|
9
|
-
status?: string;
|
|
10
|
-
description?: string;
|
|
11
|
-
triggers?: string;
|
|
12
|
-
grants?: string;
|
|
13
|
-
}
|
|
14
|
-
/** Read + parse the worker registry. Empty array if it does not exist. */
|
|
15
|
-
export declare function readWorkerRegistry(hqRoot: string): RegistryWorker[];
|
|
2
|
+
export { readWorkerRegistry, type RegistryWorker } from "../lib/workers-registry/read.js";
|
|
3
|
+
import { type RegistryWorker } from "../lib/workers-registry/read.js";
|
|
16
4
|
/**
|
|
17
5
|
* Best-effort active company: workspace/sessions/.current -> meta.yaml
|
|
18
6
|
* company_slug. Undefined when no session context is set. Pure/read-only.
|
package/dist/commands/workers.js
CHANGED
|
@@ -7,14 +7,8 @@ import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
|
7
7
|
import { vaultApiFetch, getCompanyUid } from "./secrets.js";
|
|
8
8
|
import { GROUP_ID_PATTERN, EMAIL_PATTERN, normalizeFilePrefix } from "./_patterns.js";
|
|
9
9
|
import { findHqRoot } from "../utils/manifest.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const p = path.join(hqRoot, "core/workers/registry.yaml");
|
|
13
|
-
if (!fs.existsSync(p))
|
|
14
|
-
return [];
|
|
15
|
-
const doc = parseUserYaml(fs.readFileSync(p, "utf8"), p);
|
|
16
|
-
return doc?.workers ?? [];
|
|
17
|
-
}
|
|
10
|
+
export { readWorkerRegistry } from "../lib/workers-registry/read.js";
|
|
11
|
+
import { readWorkerRegistry } from "../lib/workers-registry/read.js";
|
|
18
12
|
/**
|
|
19
13
|
* Best-effort active company: workspace/sessions/.current -> meta.yaml
|
|
20
14
|
* company_slug. Undefined when no session context is set. Pure/read-only.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed HQ API client for the bot runtime (local-bots US-004).
|
|
3
|
+
*
|
|
4
|
+
* Every call goes through the shared `vaultApiFetch` helper with a token
|
|
5
|
+
* supplier so the bot's machine identity (agt_) is minted and refreshed by the
|
|
6
|
+
* existing Cognito machinery. `fetch` is injectable for tests.
|
|
7
|
+
*/
|
|
8
|
+
import { vaultApiFetch } from "../../utils/vault-api.js";
|
|
9
|
+
export type TokenSupplier = () => Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* One agent-inbox item. DMs carry the base fields; room (channel / group chat)
|
|
12
|
+
* items add the optional room fields (server: hq-pro-core
|
|
13
|
+
* room-agent-delivery.ts `RoomInboxMessage`).
|
|
14
|
+
*/
|
|
15
|
+
export interface InboxItem {
|
|
16
|
+
messageId: string;
|
|
17
|
+
channel: string;
|
|
18
|
+
eventId?: string;
|
|
19
|
+
fromPersonUid?: string;
|
|
20
|
+
fromDisplayName?: string;
|
|
21
|
+
text?: string;
|
|
22
|
+
rootEventId?: string;
|
|
23
|
+
receivedAt?: string;
|
|
24
|
+
/** Room items only (channel === "room"). */
|
|
25
|
+
channelId?: string;
|
|
26
|
+
channelName?: string;
|
|
27
|
+
channelScope?: string;
|
|
28
|
+
mentionedMe?: boolean;
|
|
29
|
+
memberCount?: number;
|
|
30
|
+
senderTrust?: "member";
|
|
31
|
+
}
|
|
32
|
+
/** @deprecated use InboxItem */
|
|
33
|
+
export type DmInboxItem = InboxItem;
|
|
34
|
+
/** A simplified channel message for the "Recent messages" context block. */
|
|
35
|
+
export interface ChannelMessageSummary {
|
|
36
|
+
eventId?: string;
|
|
37
|
+
fromPersonUid?: string;
|
|
38
|
+
fromDisplayName?: string;
|
|
39
|
+
body: string;
|
|
40
|
+
createdAt?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface AgentRecord {
|
|
43
|
+
uid: string;
|
|
44
|
+
name?: string;
|
|
45
|
+
ownerUid?: string | null;
|
|
46
|
+
computeMode?: string;
|
|
47
|
+
botKind?: string;
|
|
48
|
+
online?: boolean;
|
|
49
|
+
runtime?: {
|
|
50
|
+
status?: string;
|
|
51
|
+
lastHeartbeat?: {
|
|
52
|
+
at?: string;
|
|
53
|
+
} | null;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export declare class BotApiError extends Error {
|
|
57
|
+
readonly status: number;
|
|
58
|
+
readonly path: string;
|
|
59
|
+
readonly code?: string | undefined;
|
|
60
|
+
constructor(status: number, path: string, code?: string | undefined, detail?: string);
|
|
61
|
+
}
|
|
62
|
+
export interface BotApiOptions {
|
|
63
|
+
token: TokenSupplier;
|
|
64
|
+
baseUrl?: string;
|
|
65
|
+
fetchImpl?: typeof vaultApiFetch;
|
|
66
|
+
}
|
|
67
|
+
export interface BotPromotionState {
|
|
68
|
+
version: number;
|
|
69
|
+
operationId: string;
|
|
70
|
+
agentUid: string;
|
|
71
|
+
ownerUid: string;
|
|
72
|
+
companyUid: string;
|
|
73
|
+
snapshotSha256: string;
|
|
74
|
+
phase: "preparing" | "local-stopped" | "imported" | "cloud-ready" | "active" | "cancelled";
|
|
75
|
+
updatedAt: string;
|
|
76
|
+
}
|
|
77
|
+
export interface BotPromotionUpload {
|
|
78
|
+
url: string;
|
|
79
|
+
method: "PUT";
|
|
80
|
+
headers: Record<string, string>;
|
|
81
|
+
bytes: number;
|
|
82
|
+
sha256: string;
|
|
83
|
+
expiresInSeconds: number;
|
|
84
|
+
}
|
|
85
|
+
export declare class BotApi {
|
|
86
|
+
private readonly opts;
|
|
87
|
+
private readonly fetchImpl;
|
|
88
|
+
constructor(opts: BotApiOptions);
|
|
89
|
+
private call;
|
|
90
|
+
/** GET /v1/agents/{uid} — owner-only local bot record (403/404 for others). */
|
|
91
|
+
getAgent(agentUid: string): Promise<AgentRecord>;
|
|
92
|
+
/** POST /v1/agents/{uid}/heartbeat — agent-JWT only. */
|
|
93
|
+
heartbeat(agentUid: string, components: Record<string, "ok" | "degraded" | "failed" | "unknown">): Promise<{
|
|
94
|
+
ok: boolean;
|
|
95
|
+
at?: string;
|
|
96
|
+
local?: boolean;
|
|
97
|
+
}>;
|
|
98
|
+
/** GET /v1/agents/{uid}/inbox → { messages } (agent-JWT only). */
|
|
99
|
+
pullInbox(agentUid: string): Promise<InboxItem[]>;
|
|
100
|
+
/** POST /v1/agents/{uid}/inbox/{messageId}/ack */
|
|
101
|
+
ackInbox(agentUid: string, messageId: string): Promise<void>;
|
|
102
|
+
/** POST /v1/notify/dm — send as the bot (agent identity). */
|
|
103
|
+
sendDm(input: {
|
|
104
|
+
toPersonUid: string;
|
|
105
|
+
body: string;
|
|
106
|
+
rootEventId?: string;
|
|
107
|
+
}): Promise<{
|
|
108
|
+
eventId?: string;
|
|
109
|
+
createdAt?: string;
|
|
110
|
+
state?: string;
|
|
111
|
+
}>;
|
|
112
|
+
/** POST|DELETE /v1/notify/reactions on a DM message (best-effort thinking status). */
|
|
113
|
+
setReaction(input: {
|
|
114
|
+
peerUid: string;
|
|
115
|
+
messageId: string;
|
|
116
|
+
emoji: string;
|
|
117
|
+
}, present: boolean): Promise<boolean>;
|
|
118
|
+
/**
|
|
119
|
+
* POST /v1/notify/channels/{id}/claims — first-claim-wins on a room unit.
|
|
120
|
+
* 200 → true (this bot answers), 409 THREAD_ALREADY_CLAIMED → false
|
|
121
|
+
* (someone else does). Anything else throws.
|
|
122
|
+
*/
|
|
123
|
+
claimChannelUnit(channelId: string, unitId: string): Promise<boolean>;
|
|
124
|
+
/** POST /v1/notify/channels/{id}/messages — post in a room as the bot. */
|
|
125
|
+
sendChannelMessage(input: {
|
|
126
|
+
channelId: string;
|
|
127
|
+
body: string;
|
|
128
|
+
rootEventId?: string;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
eventId?: string;
|
|
131
|
+
createdAt?: string;
|
|
132
|
+
rootEventId?: string;
|
|
133
|
+
}>;
|
|
134
|
+
/**
|
|
135
|
+
* POST /v1/notify/channels/{id}/agent-status — ephemeral "thinking" hint in
|
|
136
|
+
* a room (never durable; the server clears it on the next event). Best-effort.
|
|
137
|
+
*/
|
|
138
|
+
postAgentStatus(channelId: string, status: string, threadRoot?: string): Promise<boolean>;
|
|
139
|
+
/**
|
|
140
|
+
* GET /v1/notify/channels/{id}/messages?limit=N — the newest N messages,
|
|
141
|
+
* returned oldest-first so they read as a transcript.
|
|
142
|
+
*/
|
|
143
|
+
fetchChannelMessages(channelId: string, limit?: number): Promise<ChannelMessageSummary[]>;
|
|
144
|
+
/** POST /v1/agents — person identity creates a local bot (US-002). */
|
|
145
|
+
/** Promotion mutations must use a human owner token, not bot credentials. */
|
|
146
|
+
preparePromotion(agentUid: string, input: {
|
|
147
|
+
companyUid: string;
|
|
148
|
+
operationId: string;
|
|
149
|
+
snapshotSha256: string;
|
|
150
|
+
}): Promise<BotPromotionState>;
|
|
151
|
+
promotionDetails(agentUid: string, companyUid: string): Promise<{
|
|
152
|
+
promotion: BotPromotionState | null;
|
|
153
|
+
failure?: string;
|
|
154
|
+
pairing: {
|
|
155
|
+
url: string;
|
|
156
|
+
code: string;
|
|
157
|
+
capturedAt?: string;
|
|
158
|
+
} | null;
|
|
159
|
+
}>;
|
|
160
|
+
promotionStatus(agentUid: string, companyUid: string): Promise<BotPromotionState | null>;
|
|
161
|
+
resumePromotion(agentUid: string, input: {
|
|
162
|
+
companyUid: string;
|
|
163
|
+
operationId: string;
|
|
164
|
+
}): Promise<{
|
|
165
|
+
promotion: BotPromotionState;
|
|
166
|
+
pending: boolean;
|
|
167
|
+
}>;
|
|
168
|
+
confirmPromotionStopped(agentUid: string, input: {
|
|
169
|
+
companyUid: string;
|
|
170
|
+
operationId: string;
|
|
171
|
+
proof: string;
|
|
172
|
+
}): Promise<BotPromotionState>;
|
|
173
|
+
promotionUpload(agentUid: string, input: {
|
|
174
|
+
companyUid: string;
|
|
175
|
+
operationId: string;
|
|
176
|
+
sha256: string;
|
|
177
|
+
bytes: number;
|
|
178
|
+
kind: "snapshot" | "machine-credential";
|
|
179
|
+
}): Promise<{
|
|
180
|
+
promotion: BotPromotionState;
|
|
181
|
+
upload: BotPromotionUpload;
|
|
182
|
+
}>;
|
|
183
|
+
createLocalBot(input: {
|
|
184
|
+
name: string;
|
|
185
|
+
slug?: string;
|
|
186
|
+
}): Promise<{
|
|
187
|
+
agent: AgentRecord;
|
|
188
|
+
identity: {
|
|
189
|
+
cognitoUsername: string;
|
|
190
|
+
secret: string;
|
|
191
|
+
};
|
|
192
|
+
}>;
|
|
193
|
+
/** DELETE /v1/agents/{uid} — owner tears the identity down. */
|
|
194
|
+
deleteLocalBot(agentUid: string): Promise<{
|
|
195
|
+
uid: string;
|
|
196
|
+
terminal: boolean;
|
|
197
|
+
compensations: string[];
|
|
198
|
+
failures?: Record<string, string>;
|
|
199
|
+
code?: string;
|
|
200
|
+
}>;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed HQ API client for the bot runtime (local-bots US-004).
|
|
3
|
+
*
|
|
4
|
+
* Every call goes through the shared `vaultApiFetch` helper with a token
|
|
5
|
+
* supplier so the bot's machine identity (agt_) is minted and refreshed by the
|
|
6
|
+
* existing Cognito machinery. `fetch` is injectable for tests.
|
|
7
|
+
*/
|
|
8
|
+
import { vaultApiFetch } from "../../utils/vault-api.js";
|
|
9
|
+
export class BotApiError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
path;
|
|
12
|
+
code;
|
|
13
|
+
constructor(status, path, code, detail) {
|
|
14
|
+
super(`HQ API ${path} → ${status}${code ? ` ${code}` : ""}${detail ? `: ${detail}` : ""}`);
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.path = path;
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.name = "BotApiError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function readError(res) {
|
|
22
|
+
try {
|
|
23
|
+
const body = (await res.json());
|
|
24
|
+
return {
|
|
25
|
+
code: typeof body.code === "string" ? body.code : undefined,
|
|
26
|
+
detail: typeof body.error === "string"
|
|
27
|
+
? body.error
|
|
28
|
+
: typeof body.message === "string"
|
|
29
|
+
? body.message
|
|
30
|
+
: undefined,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class BotApi {
|
|
38
|
+
opts;
|
|
39
|
+
fetchImpl;
|
|
40
|
+
constructor(opts) {
|
|
41
|
+
this.opts = opts;
|
|
42
|
+
this.fetchImpl = opts.fetchImpl ?? vaultApiFetch;
|
|
43
|
+
}
|
|
44
|
+
async call(path, init = {}, accept = [200, 201, 202]) {
|
|
45
|
+
const token = await this.opts.token();
|
|
46
|
+
const res = await this.fetchImpl({
|
|
47
|
+
token,
|
|
48
|
+
path,
|
|
49
|
+
method: init.method,
|
|
50
|
+
body: init.body,
|
|
51
|
+
query: init.query,
|
|
52
|
+
...(this.opts.baseUrl ? { baseUrl: this.opts.baseUrl } : {}),
|
|
53
|
+
});
|
|
54
|
+
if (!accept.includes(res.status)) {
|
|
55
|
+
const err = await readError(res);
|
|
56
|
+
throw new BotApiError(res.status, path, err.code, err.detail);
|
|
57
|
+
}
|
|
58
|
+
const text = await res.text();
|
|
59
|
+
return { status: res.status, body: (text ? JSON.parse(text) : {}) };
|
|
60
|
+
}
|
|
61
|
+
/** GET /v1/agents/{uid} — owner-only local bot record (403/404 for others). */
|
|
62
|
+
async getAgent(agentUid) {
|
|
63
|
+
const { body } = await this.call(`/v1/agents/${encodeURIComponent(agentUid)}`);
|
|
64
|
+
return body.agent ?? body;
|
|
65
|
+
}
|
|
66
|
+
/** POST /v1/agents/{uid}/heartbeat — agent-JWT only. */
|
|
67
|
+
async heartbeat(agentUid, components) {
|
|
68
|
+
const { body } = await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/heartbeat`, { method: "POST", body: { components } });
|
|
69
|
+
return body;
|
|
70
|
+
}
|
|
71
|
+
/** GET /v1/agents/{uid}/inbox → { messages } (agent-JWT only). */
|
|
72
|
+
async pullInbox(agentUid) {
|
|
73
|
+
const { body } = await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/inbox`);
|
|
74
|
+
return body.messages ?? body.items ?? [];
|
|
75
|
+
}
|
|
76
|
+
/** POST /v1/agents/{uid}/inbox/{messageId}/ack */
|
|
77
|
+
async ackInbox(agentUid, messageId) {
|
|
78
|
+
await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/inbox/${encodeURIComponent(messageId)}/ack`, { method: "POST", body: {} });
|
|
79
|
+
}
|
|
80
|
+
/** POST /v1/notify/dm — send as the bot (agent identity). */
|
|
81
|
+
async sendDm(input) {
|
|
82
|
+
const payload = { toPersonUid: input.toPersonUid, body: input.body };
|
|
83
|
+
if (input.rootEventId)
|
|
84
|
+
payload.rootEventId = input.rootEventId;
|
|
85
|
+
const { body } = await this.call("/v1/notify/dm", { method: "POST", body: payload });
|
|
86
|
+
return body;
|
|
87
|
+
}
|
|
88
|
+
/** POST|DELETE /v1/notify/reactions on a DM message (best-effort thinking status). */
|
|
89
|
+
async setReaction(input, present) {
|
|
90
|
+
try {
|
|
91
|
+
await this.call("/v1/notify/reactions", {
|
|
92
|
+
method: present ? "POST" : "DELETE",
|
|
93
|
+
body: { messageScope: `dm:${input.peerUid}`, messageId: input.messageId, emoji: input.emoji },
|
|
94
|
+
}, [200, 201, 204]);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* POST /v1/notify/channels/{id}/claims — first-claim-wins on a room unit.
|
|
103
|
+
* 200 → true (this bot answers), 409 THREAD_ALREADY_CLAIMED → false
|
|
104
|
+
* (someone else does). Anything else throws.
|
|
105
|
+
*/
|
|
106
|
+
async claimChannelUnit(channelId, unitId) {
|
|
107
|
+
try {
|
|
108
|
+
await this.call(`/v1/notify/channels/${encodeURIComponent(channelId)}/claims`, { method: "POST", body: { unitId } }, [200]);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (err instanceof BotApiError && err.status === 409)
|
|
113
|
+
return false;
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** POST /v1/notify/channels/{id}/messages — post in a room as the bot. */
|
|
118
|
+
async sendChannelMessage(input) {
|
|
119
|
+
const payload = { body: input.body };
|
|
120
|
+
if (input.rootEventId)
|
|
121
|
+
payload.rootEventId = input.rootEventId;
|
|
122
|
+
const { body } = await this.call(`/v1/notify/channels/${encodeURIComponent(input.channelId)}/messages`, { method: "POST", body: payload });
|
|
123
|
+
return body;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* POST /v1/notify/channels/{id}/agent-status — ephemeral "thinking" hint in
|
|
127
|
+
* a room (never durable; the server clears it on the next event). Best-effort.
|
|
128
|
+
*/
|
|
129
|
+
async postAgentStatus(channelId, status, threadRoot) {
|
|
130
|
+
try {
|
|
131
|
+
await this.call(`/v1/notify/channels/${encodeURIComponent(channelId)}/agent-status`, { method: "POST", body: { status, ...(threadRoot ? { threadRoot } : {}) } }, [200]);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* GET /v1/notify/channels/{id}/messages?limit=N — the newest N messages,
|
|
140
|
+
* returned oldest-first so they read as a transcript.
|
|
141
|
+
*/
|
|
142
|
+
async fetchChannelMessages(channelId, limit = 15) {
|
|
143
|
+
const { body } = await this.call(`/v1/notify/channels/${encodeURIComponent(channelId)}/messages`, { query: { limit: String(limit) } });
|
|
144
|
+
const rows = Array.isArray(body.messages) ? body.messages : [];
|
|
145
|
+
const out = [];
|
|
146
|
+
for (const row of rows) {
|
|
147
|
+
const text = typeof row.body === "string" ? row.body.trim() : "";
|
|
148
|
+
if (!text)
|
|
149
|
+
continue;
|
|
150
|
+
out.push({
|
|
151
|
+
...(typeof row.eventId === "string" ? { eventId: row.eventId } : {}),
|
|
152
|
+
...(typeof row.fromPersonUid === "string" ? { fromPersonUid: row.fromPersonUid } : {}),
|
|
153
|
+
...(typeof row.fromDisplayName === "string" && row.fromDisplayName.trim()
|
|
154
|
+
? { fromDisplayName: row.fromDisplayName.trim() }
|
|
155
|
+
: {}),
|
|
156
|
+
body: text,
|
|
157
|
+
...(typeof row.createdAt === "string" ? { createdAt: row.createdAt } : {}),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
out.sort((a, b) => (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
/** POST /v1/agents — person identity creates a local bot (US-002). */
|
|
164
|
+
/** Promotion mutations must use a human owner token, not bot credentials. */
|
|
165
|
+
async preparePromotion(agentUid, input) {
|
|
166
|
+
return (await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/promote/prepare`, { method: "POST", body: input })).body.promotion;
|
|
167
|
+
}
|
|
168
|
+
async promotionDetails(agentUid, companyUid) {
|
|
169
|
+
return (await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/promote?companyUid=${encodeURIComponent(companyUid)}`, { method: "GET" })).body;
|
|
170
|
+
}
|
|
171
|
+
async promotionStatus(agentUid, companyUid) {
|
|
172
|
+
return (await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/promote?companyUid=${encodeURIComponent(companyUid)}`, { method: "GET" })).body.promotion;
|
|
173
|
+
}
|
|
174
|
+
async resumePromotion(agentUid, input) {
|
|
175
|
+
return (await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/promote/resume`, { method: "POST", body: input })).body;
|
|
176
|
+
}
|
|
177
|
+
async confirmPromotionStopped(agentUid, input) {
|
|
178
|
+
return (await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/promote/local-stopped`, { method: "POST", body: input })).body.promotion;
|
|
179
|
+
}
|
|
180
|
+
async promotionUpload(agentUid, input) {
|
|
181
|
+
return (await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/promote/snapshot-upload`, { method: "POST", body: input })).body;
|
|
182
|
+
}
|
|
183
|
+
async createLocalBot(input) {
|
|
184
|
+
const { body } = await this.call("/v1/agents", {
|
|
185
|
+
method: "POST",
|
|
186
|
+
body: {
|
|
187
|
+
name: input.name,
|
|
188
|
+
...(input.slug ? { slug: input.slug } : {}),
|
|
189
|
+
computeMode: "local",
|
|
190
|
+
botKind: "personal",
|
|
191
|
+
role: "local-bot",
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
return body;
|
|
195
|
+
}
|
|
196
|
+
/** DELETE /v1/agents/{uid} — owner tears the identity down. */
|
|
197
|
+
async deleteLocalBot(agentUid) {
|
|
198
|
+
const { body } = await this.call(`/v1/agents/${encodeURIComponent(agentUid)}`, { method: "DELETE" });
|
|
199
|
+
return body;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Company bind for worker-sourced bots (local-bots Feature 4).
|
|
3
|
+
*
|
|
4
|
+
* HQ's company hooks key on workspace/sessions/<sid>/meta.yaml `company_slug`,
|
|
5
|
+
* where <sid> is the Claude session id carried in the hook payload. A bot that
|
|
6
|
+
* runs a company worker writes that file for the session id it is about to
|
|
7
|
+
* use, before every turn, so the hooks resolve the company exactly as they do
|
|
8
|
+
* for the owner. Idempotent: an existing file keeps its other keys and only
|
|
9
|
+
* has `company_slug` replaced.
|
|
10
|
+
*
|
|
11
|
+
* Coverage note: Claude bots get the full company hook set (their session id
|
|
12
|
+
* is in the hook payload). Codex/Grok bots only see HQ_SESSION_ID in the env.
|
|
13
|
+
*/
|
|
14
|
+
export interface CompanyBindFs {
|
|
15
|
+
existsSync: (p: string) => boolean;
|
|
16
|
+
readFileSync: (p: string) => string;
|
|
17
|
+
writeFileSync: (p: string, body: string) => void;
|
|
18
|
+
mkdirSync: (p: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare const defaultCompanyBindFs: CompanyBindFs;
|
|
21
|
+
export declare function botSessionMetaRelPath(sessionId: string): string;
|
|
22
|
+
export declare function ensureBotSessionMeta(hqRoot: string, sessionId: string, companySlug: string, io?: CompanyBindFs, now?: () => Date): {
|
|
23
|
+
path: string;
|
|
24
|
+
created: boolean;
|
|
25
|
+
changed: boolean;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=company-bind.d.ts.map
|