@indigoai-us/hq-cli 5.109.15 → 5.110.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 +35 -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/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 +3 -2
|
@@ -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
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
export const defaultCompanyBindFs = {
|
|
17
|
+
existsSync: (p) => fs.existsSync(p),
|
|
18
|
+
readFileSync: (p) => fs.readFileSync(p, "utf8"),
|
|
19
|
+
writeFileSync: (p, body) => fs.writeFileSync(p, body, { mode: 0o644 }),
|
|
20
|
+
mkdirSync: (p) => fs.mkdirSync(p, { recursive: true }),
|
|
21
|
+
};
|
|
22
|
+
const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
23
|
+
const COMPANY_SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
24
|
+
export function botSessionMetaRelPath(sessionId) {
|
|
25
|
+
return path.join("workspace", "sessions", sessionId, "meta.yaml");
|
|
26
|
+
}
|
|
27
|
+
export function ensureBotSessionMeta(hqRoot, sessionId, companySlug, io = defaultCompanyBindFs, now = () => new Date()) {
|
|
28
|
+
if (!SESSION_ID_RE.test(sessionId))
|
|
29
|
+
throw new Error(`Invalid session id for company bind: "${sessionId}"`);
|
|
30
|
+
if (!COMPANY_SLUG_RE.test(companySlug))
|
|
31
|
+
throw new Error(`Invalid company slug for company bind: "${companySlug}"`);
|
|
32
|
+
const file = path.join(hqRoot, botSessionMetaRelPath(sessionId));
|
|
33
|
+
io.mkdirSync(path.dirname(file));
|
|
34
|
+
if (!io.existsSync(file)) {
|
|
35
|
+
const body = `session_id: ${sessionId}\nstarted_at: "${now().toISOString()}"\ncompany_slug: ${companySlug}\n`;
|
|
36
|
+
io.writeFileSync(file, body);
|
|
37
|
+
return { path: file, created: true, changed: true };
|
|
38
|
+
}
|
|
39
|
+
const current = io.readFileSync(file);
|
|
40
|
+
const lines = current.split("\n");
|
|
41
|
+
let replaced = false;
|
|
42
|
+
const next = lines.map((line) => {
|
|
43
|
+
if (/^company_slug\s*:/.test(line)) {
|
|
44
|
+
replaced = true;
|
|
45
|
+
return `company_slug: ${companySlug}`;
|
|
46
|
+
}
|
|
47
|
+
return line;
|
|
48
|
+
});
|
|
49
|
+
if (!replaced) {
|
|
50
|
+
while (next.length > 0 && next[next.length - 1] === "")
|
|
51
|
+
next.pop();
|
|
52
|
+
next.push(`company_slug: ${companySlug}`);
|
|
53
|
+
}
|
|
54
|
+
if (!next.some((l) => /^session_id\s*:/.test(l)))
|
|
55
|
+
next.unshift(`session_id: ${sessionId}`);
|
|
56
|
+
const body = `${next.join("\n").replace(/\n+$/, "")}\n`;
|
|
57
|
+
if (body === current)
|
|
58
|
+
return { path: file, created: false, changed: false };
|
|
59
|
+
io.writeFileSync(file, body);
|
|
60
|
+
return { path: file, created: false, changed: true };
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=company-bind.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bot.json — non-secret per-bot configuration (local-bots US-003/US-005).
|
|
3
|
+
*/
|
|
4
|
+
export declare const BOT_RUNTIMES: readonly ["claude", "codex", "grok"];
|
|
5
|
+
export type BotRuntimeId = (typeof BOT_RUNTIMES)[number];
|
|
6
|
+
export declare function isBotRuntimeId(value: unknown): value is BotRuntimeId;
|
|
7
|
+
/** Longest `intro` (first-start hello DM) a bot may store. */
|
|
8
|
+
export declare const BOT_INTRO_MAX_CHARS = 500;
|
|
9
|
+
/** Longest `kickoff` (first-start model turn prompt) a bot may store. */
|
|
10
|
+
export declare const BOT_KICKOFF_MAX_CHARS = 2000;
|
|
11
|
+
/**
|
|
12
|
+
* Where a bot keeps its memory: "synced" = personal/workers/<name>/memory
|
|
13
|
+
* inside the HQ tree (follows the owner across machines); "local" =
|
|
14
|
+
* ~/.hq/bots/<name>/memory, outside the synced tree (this computer only).
|
|
15
|
+
*/
|
|
16
|
+
export declare const BOT_MEMORY_MODES: readonly ["synced", "local"];
|
|
17
|
+
export type BotMemoryMode = (typeof BOT_MEMORY_MODES)[number];
|
|
18
|
+
export declare function isBotMemoryMode(value: unknown): value is BotMemoryMode;
|
|
19
|
+
/**
|
|
20
|
+
* Thinking ("effort") levels each runtime's CLI accepts, verified 2026-09-12:
|
|
21
|
+
* `claude --effort`, `codex -c model_reasoning_effort=…`, `grok --reasoning-effort`.
|
|
22
|
+
*/
|
|
23
|
+
export declare const BOT_EFFORT_LEVELS: Record<BotRuntimeId, readonly string[]>;
|
|
24
|
+
/** Every bot thinks at medium unless its owner picks otherwise: quick chat replies. */
|
|
25
|
+
export declare const DEFAULT_BOT_EFFORT = "medium";
|
|
26
|
+
/** The thinking level a bot actually runs with. */
|
|
27
|
+
export declare function effectiveBotEffort(config: Pick<BotConfig, "effort">): string;
|
|
28
|
+
/**
|
|
29
|
+
* A thinking level for this runtime, or undefined for "default" / empty
|
|
30
|
+
* (the bot then runs at DEFAULT_BOT_EFFORT). Throws an expected error otherwise.
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateBotEffort(runtime: BotRuntimeId, value: string): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* A model id for the runtime CLI, or undefined for "default" / empty (the
|
|
35
|
+
* CLI's own default for the owner's account). Throws an expected error when
|
|
36
|
+
* it cannot be a model id.
|
|
37
|
+
*/
|
|
38
|
+
export declare function validateBotModel(value: string): string | undefined;
|
|
39
|
+
/** Trim and bound the intro text; throws an expected error when unusable. */
|
|
40
|
+
export declare function validateBotIntro(text: string): string;
|
|
41
|
+
/** Trim and bound the kickoff prompt; throws an expected error when unusable. */
|
|
42
|
+
export declare function validateBotKickoff(text: string): string;
|
|
43
|
+
export interface BotConfig {
|
|
44
|
+
v: 1;
|
|
45
|
+
name: string;
|
|
46
|
+
/** The bot's own agt_ identity (owned by `ownerUid`). */
|
|
47
|
+
agentUid: string;
|
|
48
|
+
/** The person (prs_) who created and owns the bot. */
|
|
49
|
+
ownerUid: string;
|
|
50
|
+
runtime: BotRuntimeId;
|
|
51
|
+
/** Optional model override handed to the runtime CLI (unset = the CLI's own default). */
|
|
52
|
+
model?: string;
|
|
53
|
+
/** Thinking level for the runtime CLI (see BOT_EFFORT_LEVELS; unset = DEFAULT_BOT_EFFORT). */
|
|
54
|
+
effort?: string;
|
|
55
|
+
/** HQ root the session starts in (and the cwd of every model turn). */
|
|
56
|
+
hqRoot: string;
|
|
57
|
+
/**
|
|
58
|
+
* Run the model with every permission pre-approved (default true): a bot
|
|
59
|
+
* answers DMs headlessly and cannot show an approval prompt, so a gated
|
|
60
|
+
* command simply fails. Set false to keep the runtime's normal gates.
|
|
61
|
+
*/
|
|
62
|
+
autoApprove?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Worker folder relative to hqRoot: personal/workers/<name> for a scaffolded
|
|
65
|
+
* bot, or companies/<co>/workers/<id> when the bot runs a company worker.
|
|
66
|
+
*/
|
|
67
|
+
workerDir: string;
|
|
68
|
+
/** Where the worker definition came from (default "scaffold"). */
|
|
69
|
+
workerSource?: "scaffold" | "worker";
|
|
70
|
+
/** Registry id of the company worker this bot runs (workerSource "worker"). */
|
|
71
|
+
workerId?: string;
|
|
72
|
+
/** Company the bot is bound to on every turn (workerSource "worker"). */
|
|
73
|
+
companySlug?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Memory folder: hqRoot-relative for "synced" memory (default
|
|
76
|
+
* `${workerDir}/memory`; a worker-sourced bot keeps its own memory in
|
|
77
|
+
* personal/workers/<name>/memory so the shared company tree is never
|
|
78
|
+
* written to), or an absolute path for "local" memory that lives outside
|
|
79
|
+
* the synced tree (~/.hq/bots/<name>/memory).
|
|
80
|
+
*/
|
|
81
|
+
memoryDir?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Verbatim hello the bot DMs its owner when it first comes online (and on
|
|
84
|
+
* `hq bot intro`). Unset = the default introduction.
|
|
85
|
+
*/
|
|
86
|
+
intro?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Prompt for ONE model turn the bot runs on its first start, right after the
|
|
89
|
+
* intro DM, as if the owner had sent it (same session, system prompt and
|
|
90
|
+
* permissions); the answer is DMed to the owner. Runs once, never on restart.
|
|
91
|
+
*/
|
|
92
|
+
kickoff?: string;
|
|
93
|
+
createdAt: string;
|
|
94
|
+
/** When `false` the run loop exits 0 immediately (launchd does not respawn). */
|
|
95
|
+
enabled: boolean;
|
|
96
|
+
/** Set once the bot has introduced itself to the owner (US-008 intro). */
|
|
97
|
+
introSentAt?: string;
|
|
98
|
+
}
|
|
99
|
+
export declare function readBotConfig(dir: string): BotConfig | null;
|
|
100
|
+
export declare function writeBotConfig(dir: string, config: BotConfig): void;
|
|
101
|
+
/** "local" when the memory folder is an absolute path outside the HQ tree. */
|
|
102
|
+
export declare function botMemoryMode(config: Pick<BotConfig, "memoryDir">): BotMemoryMode;
|
|
103
|
+
/** Absolute memory folder for a bot (synced memory is resolved against hqRoot). */
|
|
104
|
+
export declare function resolveBotMemoryDir(config: Pick<BotConfig, "hqRoot" | "workerDir" | "memoryDir">): string;
|
|
105
|
+
export declare function patchBotConfig(dir: string, patch: Partial<BotConfig>): BotConfig;
|
|
106
|
+
//# sourceMappingURL=config.d.ts.map
|