@indigoai-us/hq-cli 5.114.0 → 5.115.1
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 +45 -0
- package/dist/command-catalog.generated.d.ts +28 -0
- package/dist/command-catalog.generated.js +37 -0
- package/dist/commands/bot-companies.d.ts +40 -0
- package/dist/commands/bot-companies.js +64 -0
- package/dist/commands/bot.d.ts +33 -1
- package/dist/commands/bot.js +181 -8
- package/dist/commands/onboard-bot-membership.d.ts +32 -0
- package/dist/commands/onboard-bot-membership.js +40 -0
- package/dist/commands/onboard.js +10 -0
- package/dist/lib/bot/api.d.ts +11 -0
- package/dist/lib/bot/api.js +8 -1
- package/dist/lib/bot/config.d.ts +35 -0
- package/dist/lib/bot/config.js +69 -2
- package/dist/lib/bot/prompt.d.ts +13 -1
- package/dist/lib/bot/prompt.js +37 -12
- package/dist/lib/bot/run.d.ts +30 -3
- package/dist/lib/bot/run.js +137 -14
- package/dist/lib/bot/runtime-sign-in.d.ts +24 -0
- package/dist/lib/bot/runtime-sign-in.js +31 -0
- package/dist/lib/bot/status.d.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A company created from a local bot's conversation is owned by the person
|
|
3
|
+
* (the turn runs with the owner's sign-in), so the bot that helped create it
|
|
4
|
+
* is not in it. The bot passes its own id along (HQ_BOT_AGENT_UID — an id,
|
|
5
|
+
* never a credential), and `hq onboard create-company` adds that bot as a
|
|
6
|
+
* member right after, so it can keep helping inside the new company.
|
|
7
|
+
*
|
|
8
|
+
* Best effort: a failure here never fails the company that was just created.
|
|
9
|
+
*/
|
|
10
|
+
import { inviteMember } from "./members.js";
|
|
11
|
+
export declare const HQ_BOT_AGENT_UID_ENV = "HQ_BOT_AGENT_UID";
|
|
12
|
+
/** The bot to add to `companyUid`, or null when this is not a bot's turn. */
|
|
13
|
+
export declare function botToAddAfterCreateCompany(env: NodeJS.ProcessEnv, companyUid: string | undefined): {
|
|
14
|
+
agentUid: string;
|
|
15
|
+
companyUid: string;
|
|
16
|
+
} | null;
|
|
17
|
+
export interface AddBotDeps {
|
|
18
|
+
invite: typeof inviteMember;
|
|
19
|
+
callerUid: (token: string) => Promise<string>;
|
|
20
|
+
}
|
|
21
|
+
/** Add the bot as a member. Returns a one-line outcome for the log; never throws. */
|
|
22
|
+
export declare function addBotToNewCompany(input: {
|
|
23
|
+
agentUid: string;
|
|
24
|
+
companyUid: string;
|
|
25
|
+
token: string;
|
|
26
|
+
}, deps?: AddBotDeps): Promise<{
|
|
27
|
+
ok: true;
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
reason: string;
|
|
31
|
+
}>;
|
|
32
|
+
//# sourceMappingURL=onboard-bot-membership.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A company created from a local bot's conversation is owned by the person
|
|
3
|
+
* (the turn runs with the owner's sign-in), so the bot that helped create it
|
|
4
|
+
* is not in it. The bot passes its own id along (HQ_BOT_AGENT_UID — an id,
|
|
5
|
+
* never a credential), and `hq onboard create-company` adds that bot as a
|
|
6
|
+
* member right after, so it can keep helping inside the new company.
|
|
7
|
+
*
|
|
8
|
+
* Best effort: a failure here never fails the company that was just created.
|
|
9
|
+
*/
|
|
10
|
+
import { getCallerPersonUid, inviteMember } from "./members.js";
|
|
11
|
+
export const HQ_BOT_AGENT_UID_ENV = "HQ_BOT_AGENT_UID";
|
|
12
|
+
const AGENT_UID_RE = /^agt_[A-Za-z0-9]{1,64}$/;
|
|
13
|
+
/** The bot to add to `companyUid`, or null when this is not a bot's turn. */
|
|
14
|
+
export function botToAddAfterCreateCompany(env, companyUid) {
|
|
15
|
+
const agentUid = env[HQ_BOT_AGENT_UID_ENV]?.trim() ?? "";
|
|
16
|
+
const company = companyUid?.trim() ?? "";
|
|
17
|
+
if (!AGENT_UID_RE.test(agentUid) || !company.startsWith("cmp_"))
|
|
18
|
+
return null;
|
|
19
|
+
return { agentUid, companyUid: company };
|
|
20
|
+
}
|
|
21
|
+
const defaultDeps = { invite: inviteMember, callerUid: getCallerPersonUid };
|
|
22
|
+
/** Add the bot as a member. Returns a one-line outcome for the log; never throws. */
|
|
23
|
+
export async function addBotToNewCompany(input, deps = defaultDeps) {
|
|
24
|
+
try {
|
|
25
|
+
const callerUid = await deps.callerUid(input.token);
|
|
26
|
+
await deps.invite({
|
|
27
|
+
target: input.agentUid,
|
|
28
|
+
role: "member",
|
|
29
|
+
sendEmail: false,
|
|
30
|
+
companyUid: input.companyUid,
|
|
31
|
+
callerUid,
|
|
32
|
+
token: input.token,
|
|
33
|
+
});
|
|
34
|
+
return { ok: true };
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=onboard-bot-membership.js.map
|
package/dist/commands/onboard.js
CHANGED
|
@@ -25,6 +25,7 @@ import { createDefaultVaultClient } from "./cloud-provision.js";
|
|
|
25
25
|
import { detectOnboardingIdentityMismatch } from "./onboard-identity-guard.js";
|
|
26
26
|
import { planOnboardJoin } from "./onboard-join.js";
|
|
27
27
|
import { buildCreateCompanyWarning } from "./onboard-warning.js";
|
|
28
|
+
import { addBotToNewCompany, botToAddAfterCreateCompany } from "./onboard-bot-membership.js";
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
29
30
|
// Command registration
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
@@ -63,6 +64,15 @@ export function registerOnboardCommand(program) {
|
|
|
63
64
|
console.error(chalk.red(`\n✗ Onboarding failed: ${result.error}`));
|
|
64
65
|
process.exit(1);
|
|
65
66
|
}
|
|
67
|
+
// Created from a local bot's conversation: keep that bot in the company.
|
|
68
|
+
const bot = botToAddAfterCreateCompany(process.env, result.result?.companyUid);
|
|
69
|
+
if (bot) {
|
|
70
|
+
const added = await addBotToNewCompany({ ...bot, token: accessToken });
|
|
71
|
+
if (added.ok)
|
|
72
|
+
console.log(chalk.green(`✓ Added your bot (${bot.agentUid}) to ${options.name} as a member`));
|
|
73
|
+
else
|
|
74
|
+
console.log(chalk.yellow(`! Could not add your bot (${bot.agentUid}) to ${options.name}: ${added.reason}. Invite it later with: hq members invite ${bot.agentUid} --company ${options.slug} --role member`));
|
|
75
|
+
}
|
|
66
76
|
}
|
|
67
77
|
catch (err) {
|
|
68
78
|
console.error(chalk.red("\n✗ Error:"), err instanceof Error ? err.message : String(err));
|
package/dist/lib/bot/api.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export interface AgentRecord {
|
|
|
45
45
|
ownerUid?: string | null;
|
|
46
46
|
computeMode?: string;
|
|
47
47
|
botKind?: string;
|
|
48
|
+
/** Company bots: the `cmp_` uids the server made it a member of. */
|
|
49
|
+
companyMemberships?: string[];
|
|
48
50
|
online?: boolean;
|
|
49
51
|
runtime?: {
|
|
50
52
|
status?: string;
|
|
@@ -180,9 +182,18 @@ export declare class BotApi {
|
|
|
180
182
|
promotion: BotPromotionState;
|
|
181
183
|
upload: BotPromotionUpload;
|
|
182
184
|
}>;
|
|
185
|
+
/**
|
|
186
|
+
* POST /v1/agents — a local bot identity. `kind` is the bot kind the cloud
|
|
187
|
+
* stores and enforces ("personal" acts as its owner through mirror-owner;
|
|
188
|
+
* "company" is evaluated as itself through its memberships). Default personal.
|
|
189
|
+
*/
|
|
183
190
|
createLocalBot(input: {
|
|
184
191
|
name: string;
|
|
185
192
|
slug?: string;
|
|
193
|
+
kind?: "personal" | "company";
|
|
194
|
+
/** Company bots: the company it acts for, plus any further companies. The server makes it a member of each. */
|
|
195
|
+
companyUid?: string;
|
|
196
|
+
companyMemberships?: string[];
|
|
186
197
|
}): Promise<{
|
|
187
198
|
agent: AgentRecord;
|
|
188
199
|
identity: {
|
package/dist/lib/bot/api.js
CHANGED
|
@@ -180,6 +180,11 @@ export class BotApi {
|
|
|
180
180
|
async promotionUpload(agentUid, input) {
|
|
181
181
|
return (await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/promote/snapshot-upload`, { method: "POST", body: input })).body;
|
|
182
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* POST /v1/agents — a local bot identity. `kind` is the bot kind the cloud
|
|
185
|
+
* stores and enforces ("personal" acts as its owner through mirror-owner;
|
|
186
|
+
* "company" is evaluated as itself through its memberships). Default personal.
|
|
187
|
+
*/
|
|
183
188
|
async createLocalBot(input) {
|
|
184
189
|
const { body } = await this.call("/v1/agents", {
|
|
185
190
|
method: "POST",
|
|
@@ -187,7 +192,9 @@ export class BotApi {
|
|
|
187
192
|
name: input.name,
|
|
188
193
|
...(input.slug ? { slug: input.slug } : {}),
|
|
189
194
|
computeMode: "local",
|
|
190
|
-
botKind: "personal",
|
|
195
|
+
botKind: input.kind ?? "personal",
|
|
196
|
+
...(input.companyUid ? { companyUid: input.companyUid } : {}),
|
|
197
|
+
...(input.companyMemberships?.length ? { companyMemberships: input.companyMemberships } : {}),
|
|
191
198
|
role: "local-bot",
|
|
192
199
|
},
|
|
193
200
|
});
|
package/dist/lib/bot/config.d.ts
CHANGED
|
@@ -4,6 +4,31 @@
|
|
|
4
4
|
export declare const BOT_RUNTIMES: readonly ["claude", "codex", "grok"];
|
|
5
5
|
export type BotRuntimeId = (typeof BOT_RUNTIMES)[number];
|
|
6
6
|
export declare function isBotRuntimeId(value: unknown): value is BotRuntimeId;
|
|
7
|
+
/**
|
|
8
|
+
* What a bot is (owner decisions, 2026-09-15):
|
|
9
|
+
* - "personal": acts as its owner. Every turn (DMs, kickoff, rooms) runs with
|
|
10
|
+
* the owner's sign-in, so it reaches everything the owner can and can create
|
|
11
|
+
* companies, bots, invites and shares for them. Setup is a personal bot.
|
|
12
|
+
* - "company": acts as itself with its own identity, like a cloud agent. It is
|
|
13
|
+
* a member of one or more companies (`companies`) and reaches only theirs.
|
|
14
|
+
*/
|
|
15
|
+
export declare const BOT_KINDS: readonly ["personal", "company"];
|
|
16
|
+
export type BotKind = (typeof BOT_KINDS)[number];
|
|
17
|
+
export declare function isBotKind(value: unknown): value is BotKind;
|
|
18
|
+
/** The id of HQ's bundled setup worker; a bot running it is always personal. */
|
|
19
|
+
export declare const SETUP_BOT_WORKER_ID = "setup";
|
|
20
|
+
/** Trim, validate and dedupe company slugs; throws an expected error on a bad one. */
|
|
21
|
+
export declare function normalizeBotCompanies(values: readonly string[] | undefined): string[];
|
|
22
|
+
/**
|
|
23
|
+
* The kind a bot runs as. A bot.json without a `kind` was created before
|
|
24
|
+
* kinds existed: every such bot's cloud row says botKind "personal", it acted
|
|
25
|
+
* as its owner, and it was never invited into a company — so it is personal,
|
|
26
|
+
* even one running a company worker (its `companySlug` is still the company
|
|
27
|
+
* bind of its turns, not a membership). Setup is always personal.
|
|
28
|
+
*/
|
|
29
|
+
export declare function effectiveBotKind(config: Pick<BotConfig, "kind" | "workerId">): BotKind;
|
|
30
|
+
/** The companies a bot belongs to (empty for a personal bot: its owner's memberships apply). */
|
|
31
|
+
export declare function effectiveBotCompanies(config: Pick<BotConfig, "kind" | "companies" | "workerId">): string[];
|
|
7
32
|
/** Longest `intro` (first-start hello DM) a bot may store. */
|
|
8
33
|
export declare const BOT_INTRO_MAX_CHARS = 500;
|
|
9
34
|
/** Longest `kickoff` (first-start model turn prompt) a bot may store. */
|
|
@@ -48,6 +73,14 @@ export interface BotConfig {
|
|
|
48
73
|
/** The person (prs_) who created and owns the bot. */
|
|
49
74
|
ownerUid: string;
|
|
50
75
|
runtime: BotRuntimeId;
|
|
76
|
+
/**
|
|
77
|
+
* "personal" (acts as its owner everywhere) or "company" (acts as itself,
|
|
78
|
+
* member of `companies`). Missing on bots created before kinds existed:
|
|
79
|
+
* see effectiveBotKind for the rule readBotConfig applies.
|
|
80
|
+
*/
|
|
81
|
+
kind?: BotKind;
|
|
82
|
+
/** Company bots only: slugs of the companies the bot is a member of. */
|
|
83
|
+
companies?: string[];
|
|
51
84
|
/** Optional model override handed to the runtime CLI (unset = the CLI's own default). */
|
|
52
85
|
model?: string;
|
|
53
86
|
/** Thinking level for the runtime CLI (see BOT_EFFORT_LEVELS; unset = DEFAULT_BOT_EFFORT). */
|
|
@@ -95,6 +128,8 @@ export interface BotConfig {
|
|
|
95
128
|
enabled: boolean;
|
|
96
129
|
/** Set once the bot has introduced itself to the owner (US-008 intro). */
|
|
97
130
|
introSentAt?: string;
|
|
131
|
+
/** The kickoff could not run because the coding tool needed a sign-in; it runs once that works. */
|
|
132
|
+
kickoffPendingSignIn?: boolean;
|
|
98
133
|
}
|
|
99
134
|
export declare function readBotConfig(dir: string): BotConfig | null;
|
|
100
135
|
export declare function writeBotConfig(dir: string, config: BotConfig): void;
|
package/dist/lib/bot/config.js
CHANGED
|
@@ -8,6 +8,62 @@ export const BOT_RUNTIMES = ["claude", "codex", "grok"];
|
|
|
8
8
|
export function isBotRuntimeId(value) {
|
|
9
9
|
return typeof value === "string" && BOT_RUNTIMES.includes(value);
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* What a bot is (owner decisions, 2026-09-15):
|
|
13
|
+
* - "personal": acts as its owner. Every turn (DMs, kickoff, rooms) runs with
|
|
14
|
+
* the owner's sign-in, so it reaches everything the owner can and can create
|
|
15
|
+
* companies, bots, invites and shares for them. Setup is a personal bot.
|
|
16
|
+
* - "company": acts as itself with its own identity, like a cloud agent. It is
|
|
17
|
+
* a member of one or more companies (`companies`) and reaches only theirs.
|
|
18
|
+
*/
|
|
19
|
+
export const BOT_KINDS = ["personal", "company"];
|
|
20
|
+
export function isBotKind(value) {
|
|
21
|
+
return typeof value === "string" && BOT_KINDS.includes(value);
|
|
22
|
+
}
|
|
23
|
+
/** The id of HQ's bundled setup worker; a bot running it is always personal. */
|
|
24
|
+
export const SETUP_BOT_WORKER_ID = "setup";
|
|
25
|
+
/** Company slugs are path segments and cloud lookups: keep them plain. */
|
|
26
|
+
const COMPANY_SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
27
|
+
/** Trim, validate and dedupe company slugs; throws an expected error on a bad one. */
|
|
28
|
+
export function normalizeBotCompanies(values) {
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const raw of values ?? []) {
|
|
31
|
+
const slug = (raw ?? "").trim();
|
|
32
|
+
if (!slug)
|
|
33
|
+
continue;
|
|
34
|
+
if (!COMPANY_SLUG_RE.test(slug) || slug === "personal") {
|
|
35
|
+
throw Object.assign(new Error(`"${slug}" is not a company slug (letters, digits, . _ -).`), { expected: true });
|
|
36
|
+
}
|
|
37
|
+
if (!out.includes(slug))
|
|
38
|
+
out.push(slug);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The kind a bot runs as. A bot.json without a `kind` was created before
|
|
44
|
+
* kinds existed: every such bot's cloud row says botKind "personal", it acted
|
|
45
|
+
* as its owner, and it was never invited into a company — so it is personal,
|
|
46
|
+
* even one running a company worker (its `companySlug` is still the company
|
|
47
|
+
* bind of its turns, not a membership). Setup is always personal.
|
|
48
|
+
*/
|
|
49
|
+
export function effectiveBotKind(config) {
|
|
50
|
+
if (config.workerId === SETUP_BOT_WORKER_ID)
|
|
51
|
+
return "personal";
|
|
52
|
+
return isBotKind(config.kind) ? config.kind : "personal";
|
|
53
|
+
}
|
|
54
|
+
/** The companies a bot belongs to (empty for a personal bot: its owner's memberships apply). */
|
|
55
|
+
export function effectiveBotCompanies(config) {
|
|
56
|
+
if (effectiveBotKind(config) !== "company")
|
|
57
|
+
return [];
|
|
58
|
+
// Tolerant on read: a malformed slug is dropped, never a reason to lose the bot.
|
|
59
|
+
const listed = [];
|
|
60
|
+
for (const raw of config.companies ?? []) {
|
|
61
|
+
const slug = typeof raw === "string" ? raw.trim() : "";
|
|
62
|
+
if (slug && COMPANY_SLUG_RE.test(slug) && !listed.includes(slug))
|
|
63
|
+
listed.push(slug);
|
|
64
|
+
}
|
|
65
|
+
return listed;
|
|
66
|
+
}
|
|
11
67
|
/** Longest `intro` (first-start hello DM) a bot may store. */
|
|
12
68
|
export const BOT_INTRO_MAX_CHARS = 500;
|
|
13
69
|
/** Longest `kickoff` (first-start model turn prompt) a bot may store. */
|
|
@@ -105,10 +161,21 @@ export function readBotConfig(dir) {
|
|
|
105
161
|
}
|
|
106
162
|
if (!isBotRuntimeId(raw.runtime))
|
|
107
163
|
return null;
|
|
108
|
-
const { intro: rawIntro, kickoff: rawKickoff, ...rest } = raw;
|
|
164
|
+
const { intro: rawIntro, kickoff: rawKickoff, kind: _kind, companies: _companies, ...rest } = raw;
|
|
109
165
|
const intro = typeof rawIntro === "string" && rawIntro.trim() ? rawIntro : undefined;
|
|
110
166
|
const kickoff = typeof rawKickoff === "string" && rawKickoff.trim() ? rawKickoff : undefined;
|
|
111
|
-
|
|
167
|
+
// Older bot.json files carry no kind: they read (and, on the first patch,
|
|
168
|
+
// persist) as personal, so every reader sees a kind.
|
|
169
|
+
const kind = effectiveBotKind(raw);
|
|
170
|
+
const companies = effectiveBotCompanies(raw);
|
|
171
|
+
return {
|
|
172
|
+
...rest,
|
|
173
|
+
kind,
|
|
174
|
+
...(kind === "company" ? { companies } : {}),
|
|
175
|
+
...(intro ? { intro } : {}),
|
|
176
|
+
...(kickoff ? { kickoff } : {}),
|
|
177
|
+
enabled: raw.enabled !== false,
|
|
178
|
+
};
|
|
112
179
|
}
|
|
113
180
|
catch {
|
|
114
181
|
return null;
|
package/dist/lib/bot/prompt.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Pure over an injected reader so tests never touch the real HQ.
|
|
18
18
|
*/
|
|
19
|
+
import type { BotKind } from "./config.js";
|
|
19
20
|
export declare const MEMORY_FILE_BUDGET_BYTES: number;
|
|
20
21
|
export declare const MEMORY_TOTAL_BUDGET_BYTES: number;
|
|
21
22
|
export declare const CONTEXT_FILE_BUDGET_BYTES: number;
|
|
@@ -84,9 +85,20 @@ export interface BuildSystemPromptInput {
|
|
|
84
85
|
workerSource?: "scaffold" | "worker";
|
|
85
86
|
workerId?: string;
|
|
86
87
|
companySlug?: string;
|
|
88
|
+
/** "personal" (acts as its owner everywhere) or "company" (acts as itself). Default personal. */
|
|
89
|
+
kind?: BotKind;
|
|
90
|
+
/** Company bots: the slugs of the companies the bot is a member of. */
|
|
91
|
+
companies?: string[];
|
|
87
92
|
/** hqRoot-relative (default `${workerDir}/memory`) or absolute. */
|
|
88
93
|
memoryDir?: string;
|
|
89
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* One paragraph saying which kind of bot this is and what that means for the
|
|
97
|
+
* `hq` commands it runs. The owner's decision (2026-09-15): a personal bot
|
|
98
|
+
* acts as its owner everywhere; a company bot acts as itself and cannot
|
|
99
|
+
* create anything for the owner.
|
|
100
|
+
*/
|
|
101
|
+
export declare function botKindParagraph(input: Pick<BuildSystemPromptInput, "agentUid" | "ownerUid" | "kind" | "companies">): string;
|
|
90
102
|
/**
|
|
91
103
|
* The standing instructions the bot runs under. The DM body is delivered as
|
|
92
104
|
* the user turn; this text is the system prompt (claude/codex) or the prompt
|
|
@@ -94,7 +106,7 @@ export interface BuildSystemPromptInput {
|
|
|
94
106
|
*/
|
|
95
107
|
export declare function buildSystemPrompt(input: BuildSystemPromptInput): string;
|
|
96
108
|
/** Fixed text sent to any non-owner who DMs the bot (never reaches the model). */
|
|
97
|
-
export declare function nonOwnerRefusalText(botName: string): string;
|
|
109
|
+
export declare function nonOwnerRefusalText(botName: string, kind?: BotKind): string;
|
|
98
110
|
/**
|
|
99
111
|
* The one-time introduction the bot DMs its owner when it first comes online.
|
|
100
112
|
* A bot created with `--intro` sends that text verbatim instead.
|
package/dist/lib/bot/prompt.js
CHANGED
|
@@ -229,6 +229,32 @@ export function readPromptSources(hqRoot, workerDir, io = defaultPromptFs, opts
|
|
|
229
229
|
}
|
|
230
230
|
return { workerYaml, persona: read("persona.md"), memory };
|
|
231
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* One paragraph saying which kind of bot this is and what that means for the
|
|
234
|
+
* `hq` commands it runs. The owner's decision (2026-09-15): a personal bot
|
|
235
|
+
* acts as its owner everywhere; a company bot acts as itself and cannot
|
|
236
|
+
* create anything for the owner.
|
|
237
|
+
*/
|
|
238
|
+
export function botKindParagraph(input) {
|
|
239
|
+
const kind = input.kind ?? "personal";
|
|
240
|
+
if (kind === "company") {
|
|
241
|
+
const list = (input.companies ?? []).filter((c) => c.trim());
|
|
242
|
+
const members = list.length > 0 ? list.map((c) => `\`${c}\``).join(", ") : "(none recorded yet)";
|
|
243
|
+
return (`You are a COMPANY bot: you act as yourself, ${input.agentUid}, with your own identity and your own permissions — never as your owner. ` +
|
|
244
|
+
`You are a member of these companies: ${members}. You can only reach those companies' files, memory, and secrets; ` +
|
|
245
|
+
`people in those companies can @mention you in their rooms, and your owner can message you directly, but you act as yourself there too. ` +
|
|
246
|
+
`Every \`hq\` command you run — in direct messages and in rooms — runs with your own sign-in: \`hq whoami\` and membership lookups describe you, the bot. ` +
|
|
247
|
+
`You cannot create a company, a bot, an invite or a share on your owner's behalf, and you cannot reach companies you are not a member of. ` +
|
|
248
|
+
`If asked to, say plainly that you are a company bot and cannot do that, and suggest your owner ask one of their personal bots (or do it themselves in HQ). ` +
|
|
249
|
+
`Nobody's messages carry an "Owner context" block; never state which companies your owner belongs to.`);
|
|
250
|
+
}
|
|
251
|
+
return (`You are a PERSONAL bot: you act as your owner, ${input.ownerUid}. ` +
|
|
252
|
+
`Every \`hq\` command you run — in direct messages, in rooms, and on your first task — runs with your owner's own sign-in, so it reaches everything they can: all of their companies and files. ` +
|
|
253
|
+
`Creating a company, a bot, an invite or a share for them works directly, so do it yourself when they ask — never tell them to open a terminal or run a command. ` +
|
|
254
|
+
`Direct messages from your owner start with an "Owner context" block checked with your owner's own sign-in; that is the only source for your owner's companies. ` +
|
|
255
|
+
`If it says the check failed, say you could not check; never say your owner has no company unless that block says so. ` +
|
|
256
|
+
`Room messages do not carry it, and other people read your answers there: in a room, never state which companies your owner belongs to, and never repeat anything from their files or your DMs.`);
|
|
257
|
+
}
|
|
232
258
|
/**
|
|
233
259
|
* The standing instructions the bot runs under. The DM body is delivered as
|
|
234
260
|
* the user turn; this text is the system prompt (claude/codex) or the prompt
|
|
@@ -240,23 +266,19 @@ export function buildSystemPrompt(input) {
|
|
|
240
266
|
const worker = input.sources.worker;
|
|
241
267
|
const workerId = input.workerId ?? worker?.id ?? path.basename(input.workerDir);
|
|
242
268
|
const company = input.companySlug ?? worker?.company;
|
|
269
|
+
const kind = input.kind ?? "personal";
|
|
243
270
|
const parts = [];
|
|
244
271
|
const opening = workerMode
|
|
245
|
-
? `You are "${input.botName}", a
|
|
272
|
+
? `You are "${input.botName}", a ${kind} HQ bot running the company worker \`${workerId}\`${company ? ` for \`${company}\`` : ""}, locally on your owner's computer.` +
|
|
246
273
|
(worker?.name ? ` The worker is "${worker.name}".` : "") +
|
|
247
274
|
(worker?.description ? ` ${worker.description}` : "") +
|
|
248
275
|
`\nYou work only inside \`${company ?? "this company"}\`'s context: its files, policies, services, and credentials — never another company's.`
|
|
249
|
-
: `You are "${input.botName}", a
|
|
276
|
+
: `You are "${input.botName}", a ${kind} HQ bot that runs locally on your owner's computer.`;
|
|
250
277
|
parts.push(`${opening}\n` +
|
|
251
|
-
`Your HQ identity is ${input.agentUid}. Your owner is ${input.ownerUid}; only your owner can
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
`and a bot usually belongs to no company. Never use them to say who your owner is or which companies they belong to. ` +
|
|
256
|
-
`The exception is \`hq bot\` (create, list, rm, promote): it always acts as your owner, so you can create a bot for them when they ask; its owner is your owner. ` +
|
|
257
|
-
`Direct messages from your owner start with an "Owner context" block checked with your owner's own sign-in; that is the only source for your owner's companies. ` +
|
|
258
|
-
`If it says the check failed, say you could not check; never say your owner has no company unless that block says so. ` +
|
|
259
|
-
`Room messages do not carry it: in a room, never state which companies your owner belongs to.\n` +
|
|
278
|
+
`Your HQ identity is ${input.agentUid}. Your owner is ${input.ownerUid}; only your owner can send you direct messages.\n` +
|
|
279
|
+
`${kind === "personal" ? "You act with your owner's permissions" : "You act with your own permissions"}, and every tool and command is pre-approved for you — do not ask for approval, just do the work. ` +
|
|
280
|
+
`${kind === "personal" ? "Their" : "Your owner's"} HQ folder is ${input.hqRoot}; it is your working directory, so its files, skills, and \`hq\` commands are available directly.\n` +
|
|
281
|
+
`${botKindParagraph(input)}\n` +
|
|
260
282
|
`Each message you receive is a direct message from your owner. Whatever you print as your final answer is sent back as a DM reply, ` +
|
|
261
283
|
`so answer the person directly, in plain language, and keep it as short as the request allows. ` +
|
|
262
284
|
`Do not narrate tool use or repeat the question. If you cannot do something, say so and suggest the nearest thing you can do.\n` +
|
|
@@ -311,7 +333,10 @@ export function buildSystemPrompt(input) {
|
|
|
311
333
|
return `${parts.join("\n\n")}\n`;
|
|
312
334
|
}
|
|
313
335
|
/** Fixed text sent to any non-owner who DMs the bot (never reaches the model). */
|
|
314
|
-
export function nonOwnerRefusalText(botName) {
|
|
336
|
+
export function nonOwnerRefusalText(botName, kind = "personal") {
|
|
337
|
+
if (kind === "company") {
|
|
338
|
+
return `Hi — I'm ${botName}, a company bot, and I only take direct messages from my owner. You can @mention me in one of my companies' rooms.`;
|
|
339
|
+
}
|
|
315
340
|
return `Hi — I'm ${botName}, a personal bot, and I only take messages from my owner. Please reach out to them directly.`;
|
|
316
341
|
}
|
|
317
342
|
/**
|
package/dist/lib/bot/run.d.ts
CHANGED
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
* started (error, crash, owner stop, restart) posts one "did not finish"
|
|
27
27
|
* message and is NOT run again — it may already have done outward things.
|
|
28
28
|
* inflight.json marks the turn in progress so a restart can say so.
|
|
29
|
+
* - a turn that fails because the coding tool's sign-in no longer works
|
|
30
|
+
* (runtime-sign-in.ts) is not a failure of the message: the bot says once
|
|
31
|
+
* per conversation that it needs a sign-in, keeps the message un-acked,
|
|
32
|
+
* holds new turns, and tries again every SIGN_IN_RETRY_MS (and at once
|
|
33
|
+
* after a restart). A turn that works clears it.
|
|
29
34
|
* - failures before the model starts (missing binary) retry with backoff;
|
|
30
35
|
* 5 model failures in 10 minutes → state `failed` and a clean exit (launchd
|
|
31
36
|
* does not respawn a clean exit). Owner stops never count.
|
|
@@ -34,7 +39,7 @@
|
|
|
34
39
|
*/
|
|
35
40
|
import { type OwnerContext } from "./owner-context.js";
|
|
36
41
|
import type { BotApi, InboxItem } from "./api.js";
|
|
37
|
-
import type { BotConfig } from "./config.js";
|
|
42
|
+
import type { BotConfig, BotKind } from "./config.js";
|
|
38
43
|
import { type ProgressPosterOptions } from "./progress.js";
|
|
39
44
|
import { type BotLogger } from "./log.js";
|
|
40
45
|
import { type BotRuntime, type RuntimeTurnInput, type RuntimeTurnResult } from "./runtime/index.js";
|
|
@@ -79,15 +84,37 @@ export interface BotRunDeps {
|
|
|
79
84
|
progress?: Pick<ProgressPosterOptions, "coalesceMs" | "minGapMs" | "workingNoticeMs" | "setTimer" | "clearTimer" | "now">;
|
|
80
85
|
/**
|
|
81
86
|
* The owner's verified company context, prepended to every owner DM turn
|
|
82
|
-
* (never room turns
|
|
83
|
-
* memberships for its owner's.
|
|
87
|
+
* of a PERSONAL bot (never room turns, never a company bot's turns) so the
|
|
88
|
+
* model never mistakes the bot's own (empty) memberships for its owner's.
|
|
84
89
|
* Omitted in tests that do not exercise it.
|
|
85
90
|
*/
|
|
86
91
|
ownerContext?: () => Promise<OwnerContext>;
|
|
87
92
|
/** Turns run at once across conversations (default MAX_PARALLEL_TURNS). */
|
|
88
93
|
maxParallelTurns?: number;
|
|
94
|
+
/** Wait between retries while the coding tool needs a sign-in (default SIGN_IN_RETRY_MS). */
|
|
95
|
+
signInRetryMs?: number;
|
|
89
96
|
}
|
|
90
97
|
export declare const KICKOFF_MESSAGE_ID = "kickoff";
|
|
98
|
+
/**
|
|
99
|
+
* The environment a model turn's `hq` commands run in.
|
|
100
|
+
*
|
|
101
|
+
* The bot process carries its own machine identity in HQ_MACHINE_CREDS_FILE /
|
|
102
|
+
* HQ_MACHINE_TOKEN_STATE_DIR so its inbox, heartbeat and posts are the bot's.
|
|
103
|
+
* What a turn inherits depends on the bot's kind:
|
|
104
|
+
* - A PERSONAL bot acts as its owner on every turn (DMs, the kickoff, rooms):
|
|
105
|
+
* everything the owner asks for — a company, a bot, an invite, a share —
|
|
106
|
+
* belongs to the owner's account, and the cloud rightly refuses a bot that
|
|
107
|
+
* tries to own it ("No person entity found"). Stripping the marker makes
|
|
108
|
+
* every `hq` command in the turn use the owner's sign-in, the same one the
|
|
109
|
+
* desktop app shares, so no command needs a special case. The bot's id
|
|
110
|
+
* (never a credential) rides along in HQ_BOT_AGENT_UID so what the owner
|
|
111
|
+
* creates in the turn — a company — can add the bot as a member afterwards.
|
|
112
|
+
* - A COMPANY bot acts as itself on every turn, like a cloud agent: the
|
|
113
|
+
* machine identity stays, and the cloud evaluates it through its own
|
|
114
|
+
* company memberships. It never acts as the owner, in DMs or in rooms.
|
|
115
|
+
* Pure so the rule is unit-testable.
|
|
116
|
+
*/
|
|
117
|
+
export declare function turnEnv(base: NodeJS.ProcessEnv, kind: BotKind, agentUid?: string): NodeJS.ProcessEnv;
|
|
91
118
|
/** The core worker HQ's setup bot runs. */
|
|
92
119
|
export declare const SETUP_WORKER_ID = "setup";
|
|
93
120
|
/** The one message a person gets when a turn on their message did not finish. */
|