@indigoai-us/hq-cli 5.113.1 → 5.115.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 +57 -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 +155 -7
- 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 +6 -0
- package/dist/lib/bot/api.js +6 -1
- package/dist/lib/bot/config.d.ts +35 -0
- package/dist/lib/bot/config.js +69 -2
- package/dist/lib/bot/inflight.d.ts +18 -7
- package/dist/lib/bot/inflight.js +57 -18
- package/dist/lib/bot/prompt.d.ts +13 -1
- package/dist/lib/bot/prompt.js +37 -12
- package/dist/lib/bot/run.d.ts +48 -5
- package/dist/lib/bot/run.js +231 -47
- 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/dist/utils/feedback-log-bundle.d.ts +61 -11
- package/dist/utils/feedback-log-bundle.js +209 -29
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.115.0] — 2026-09-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Local bots now come in two kinds. A **personal bot** acts as you: everything
|
|
10
|
+
it does — in your direct messages, in rooms, and on its first task — runs
|
|
11
|
+
under your own account, so it reaches every company and file you can and
|
|
12
|
+
can create companies, bots, invites and shares for you. A **company bot**
|
|
13
|
+
acts as itself, like a cloud agent: it is a member of one or more companies
|
|
14
|
+
and reaches only those companies' files, memory and secrets; it never acts
|
|
15
|
+
as you and says so if asked to create something for you, suggesting you ask
|
|
16
|
+
a personal bot instead. `hq bot create <name> --kind personal|company
|
|
17
|
+
--company <slug>` (repeat `--company` for several) picks the kind; a
|
|
18
|
+
company bot is invited into each company as you create it, and the command
|
|
19
|
+
tells you exactly which invite did not go through. `hq bot companies
|
|
20
|
+
<name> --add/--remove <slug>` changes a company bot's memberships later.
|
|
21
|
+
`hq bot list --json` and `hq bot status` show each bot's kind and
|
|
22
|
+
companies, and every bot's instructions say which kind it is. Bots created
|
|
23
|
+
before this release keep working, as personal bots (including one created
|
|
24
|
+
from a company worker, and the setup bot). Removing a company bot with
|
|
25
|
+
`hq bot rm` also takes it out of its companies.
|
|
26
|
+
- A personal bot now runs `hq` commands as you in rooms too, not only in your
|
|
27
|
+
direct messages (before, its room turns used the bot's own identity).
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- When a local bot's coding tool (Claude Code, Codex or Grok) has lost its
|
|
32
|
+
sign-in, the bot no longer answers every message with "Failed to
|
|
33
|
+
authenticate". It says once that the tool needs you to sign in again, keeps
|
|
34
|
+
your message, and answers it on its own as soon as the sign-in works (right
|
|
35
|
+
away after `hq bot restart`). A setup bot's first step waits the same way.
|
|
36
|
+
`hq bot list --json` reports `runtimeSignIn` while this is the case.
|
|
37
|
+
- In your direct messages, a local bot now does what you ask under your own
|
|
38
|
+
account: creating a company, a bot, an invite or a share works directly,
|
|
39
|
+
instead of the bot explaining that the cloud refuses to let it act in your
|
|
40
|
+
name and asking you to run a command in a terminal. In rooms, bots still act
|
|
41
|
+
as themselves. A company you create this way now also adds that bot as a
|
|
42
|
+
member, so it can keep helping inside the new company.
|
|
43
|
+
|
|
44
|
+
## [5.114.0] — 2026-09-15
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- Local bots now work on two conversations at once. A question in one
|
|
49
|
+
channel no longer waits for the bot to finish answering in another; messages
|
|
50
|
+
in the same conversation (your DMs with the bot, or one channel) are still
|
|
51
|
+
answered in order. A restart or stop during two turns reports each one that
|
|
52
|
+
did not finish.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- The log bundle attached to `hq feedback` reports is now a zip instead of a
|
|
57
|
+
gzip. Slack refuses a gzip file from the HQ app at any size, so for several
|
|
58
|
+
weeks every report's logs were uploaded and then silently dropped before
|
|
59
|
+
anyone could read them. Nothing about what the bundle contains or how it is
|
|
60
|
+
redacted has changed.
|
|
61
|
+
|
|
5
62
|
## [5.113.1] — 2026-09-15
|
|
6
63
|
|
|
7
64
|
### Fixed
|
|
@@ -6129,6 +6129,12 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
6129
6129
|
readonly variadic: false;
|
|
6130
6130
|
}];
|
|
6131
6131
|
readonly options: readonly [{
|
|
6132
|
+
readonly flags: "--kind <kind>";
|
|
6133
|
+
readonly description: "personal (acts as you, everywhere you are) or company (acts as itself, as a member of its companies). Default: personal, or company when created from a company worker";
|
|
6134
|
+
}, {
|
|
6135
|
+
readonly flags: "--company <slug>";
|
|
6136
|
+
readonly description: "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation";
|
|
6137
|
+
}, {
|
|
6132
6138
|
readonly flags: "--runtime <runtime>";
|
|
6133
6139
|
readonly description: "Model CLI to run the bot with: claude|codex|grok";
|
|
6134
6140
|
}, {
|
|
@@ -6163,6 +6169,28 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
6163
6169
|
readonly description: "Print machine-readable JSON";
|
|
6164
6170
|
}];
|
|
6165
6171
|
readonly subcommands: readonly [];
|
|
6172
|
+
}, {
|
|
6173
|
+
readonly name: "companies";
|
|
6174
|
+
readonly description: "Show or change which companies a company bot is a member of (you invite or remove it; a personal bot has no list of its own)";
|
|
6175
|
+
readonly aliases: readonly [];
|
|
6176
|
+
readonly hidden: false;
|
|
6177
|
+
readonly usage: "[options] <name>";
|
|
6178
|
+
readonly arguments: readonly [{
|
|
6179
|
+
readonly name: "name";
|
|
6180
|
+
readonly required: true;
|
|
6181
|
+
readonly variadic: false;
|
|
6182
|
+
}];
|
|
6183
|
+
readonly options: readonly [{
|
|
6184
|
+
readonly flags: "--add <slug>";
|
|
6185
|
+
readonly description: "Add the bot to this company (repeatable)";
|
|
6186
|
+
}, {
|
|
6187
|
+
readonly flags: "--remove <slug>";
|
|
6188
|
+
readonly description: "Remove the bot from this company (repeatable)";
|
|
6189
|
+
}, {
|
|
6190
|
+
readonly flags: "--json";
|
|
6191
|
+
readonly description: "Print machine-readable JSON";
|
|
6192
|
+
}];
|
|
6193
|
+
readonly subcommands: readonly [];
|
|
6166
6194
|
}, {
|
|
6167
6195
|
readonly name: "workers";
|
|
6168
6196
|
readonly description: "List the workers (templates) a bot can be created from (hq bot create <name> --worker <id>)";
|
|
@@ -7923,6 +7923,14 @@ export const COMMAND_CATALOG = [
|
|
|
7923
7923
|
}
|
|
7924
7924
|
],
|
|
7925
7925
|
"options": [
|
|
7926
|
+
{
|
|
7927
|
+
"flags": "--kind <kind>",
|
|
7928
|
+
"description": "personal (acts as you, everywhere you are) or company (acts as itself, as a member of its companies). Default: personal, or company when created from a company worker"
|
|
7929
|
+
},
|
|
7930
|
+
{
|
|
7931
|
+
"flags": "--company <slug>",
|
|
7932
|
+
"description": "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation"
|
|
7933
|
+
},
|
|
7926
7934
|
{
|
|
7927
7935
|
"flags": "--runtime <runtime>",
|
|
7928
7936
|
"description": "Model CLI to run the bot with: claude|codex|grok"
|
|
@@ -7970,6 +7978,35 @@ export const COMMAND_CATALOG = [
|
|
|
7970
7978
|
],
|
|
7971
7979
|
"subcommands": []
|
|
7972
7980
|
},
|
|
7981
|
+
{
|
|
7982
|
+
"name": "companies",
|
|
7983
|
+
"description": "Show or change which companies a company bot is a member of (you invite or remove it; a personal bot has no list of its own)",
|
|
7984
|
+
"aliases": [],
|
|
7985
|
+
"hidden": false,
|
|
7986
|
+
"usage": "[options] <name>",
|
|
7987
|
+
"arguments": [
|
|
7988
|
+
{
|
|
7989
|
+
"name": "name",
|
|
7990
|
+
"required": true,
|
|
7991
|
+
"variadic": false
|
|
7992
|
+
}
|
|
7993
|
+
],
|
|
7994
|
+
"options": [
|
|
7995
|
+
{
|
|
7996
|
+
"flags": "--add <slug>",
|
|
7997
|
+
"description": "Add the bot to this company (repeatable)"
|
|
7998
|
+
},
|
|
7999
|
+
{
|
|
8000
|
+
"flags": "--remove <slug>",
|
|
8001
|
+
"description": "Remove the bot from this company (repeatable)"
|
|
8002
|
+
},
|
|
8003
|
+
{
|
|
8004
|
+
"flags": "--json",
|
|
8005
|
+
"description": "Print machine-readable JSON"
|
|
8006
|
+
}
|
|
8007
|
+
],
|
|
8008
|
+
"subcommands": []
|
|
8009
|
+
},
|
|
7973
8010
|
{
|
|
7974
8011
|
"name": "workers",
|
|
7975
8012
|
"description": "List the workers (templates) a bot can be created from (hq bot create <name> --worker <id>)",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A company bot's memberships (BOT-KINDS, 2026-09-15). A company bot acts as
|
|
3
|
+
* itself, so it must be a member of every company it works for; the OWNER
|
|
4
|
+
* (person token) invites it, the same `agt_` member/admin route fleet agents
|
|
5
|
+
* use. Slugs resolve to company uids with the owner's sign-in.
|
|
6
|
+
*
|
|
7
|
+
* Both helpers keep going after a failure and report every slug's outcome,
|
|
8
|
+
* so `hq bot create` can say exactly which invite did not go through.
|
|
9
|
+
*/
|
|
10
|
+
import { inviteMember, revokeInvite } from "./members.js";
|
|
11
|
+
export interface BotCompanyDeps {
|
|
12
|
+
companyUid: (token: string, slug: string) => Promise<string>;
|
|
13
|
+
callerUid: (token: string) => Promise<string>;
|
|
14
|
+
invite: typeof inviteMember;
|
|
15
|
+
revoke: typeof revokeInvite;
|
|
16
|
+
}
|
|
17
|
+
export interface BotCompanyOutcome {
|
|
18
|
+
/** Slugs the change went through for. */
|
|
19
|
+
done: string[];
|
|
20
|
+
/** Slugs it did not, with the reason. */
|
|
21
|
+
failed: Array<{
|
|
22
|
+
slug: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
/** Invite the bot as a member of each company, with the person's token. */
|
|
27
|
+
export declare function addBotToCompanies(input: {
|
|
28
|
+
agentUid: string;
|
|
29
|
+
companies: readonly string[];
|
|
30
|
+
token: string;
|
|
31
|
+
}, deps?: BotCompanyDeps): Promise<BotCompanyOutcome>;
|
|
32
|
+
/** Remove the bot's membership in each company (the `hq members revoke` route). */
|
|
33
|
+
export declare function removeBotFromCompanies(input: {
|
|
34
|
+
agentUid: string;
|
|
35
|
+
companies: readonly string[];
|
|
36
|
+
token: string;
|
|
37
|
+
}, deps?: BotCompanyDeps): Promise<BotCompanyOutcome>;
|
|
38
|
+
/** One line per failure, for an error message or a log. */
|
|
39
|
+
export declare function describeCompanyFailures(failed: BotCompanyOutcome["failed"]): string;
|
|
40
|
+
//# sourceMappingURL=bot-companies.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A company bot's memberships (BOT-KINDS, 2026-09-15). A company bot acts as
|
|
3
|
+
* itself, so it must be a member of every company it works for; the OWNER
|
|
4
|
+
* (person token) invites it, the same `agt_` member/admin route fleet agents
|
|
5
|
+
* use. Slugs resolve to company uids with the owner's sign-in.
|
|
6
|
+
*
|
|
7
|
+
* Both helpers keep going after a failure and report every slug's outcome,
|
|
8
|
+
* so `hq bot create` can say exactly which invite did not go through.
|
|
9
|
+
*/
|
|
10
|
+
import { getCallerPersonUid, inviteMember, resolveRevokeTargetToMembershipKey, revokeInvite } from "./members.js";
|
|
11
|
+
import { getCompanyUid } from "../utils/vault-api.js";
|
|
12
|
+
const defaultDeps = {
|
|
13
|
+
companyUid: getCompanyUid,
|
|
14
|
+
callerUid: getCallerPersonUid,
|
|
15
|
+
invite: inviteMember,
|
|
16
|
+
revoke: revokeInvite,
|
|
17
|
+
};
|
|
18
|
+
function reasonOf(err) {
|
|
19
|
+
return err instanceof Error ? err.message : String(err);
|
|
20
|
+
}
|
|
21
|
+
/** Invite the bot as a member of each company, with the person's token. */
|
|
22
|
+
export async function addBotToCompanies(input, deps = defaultDeps) {
|
|
23
|
+
const out = { done: [], failed: [] };
|
|
24
|
+
if (input.companies.length === 0)
|
|
25
|
+
return out;
|
|
26
|
+
let callerUid;
|
|
27
|
+
try {
|
|
28
|
+
callerUid = await deps.callerUid(input.token);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return { done: [], failed: input.companies.map((slug) => ({ slug, reason: reasonOf(err) })) };
|
|
32
|
+
}
|
|
33
|
+
for (const slug of input.companies) {
|
|
34
|
+
try {
|
|
35
|
+
const companyUid = await deps.companyUid(input.token, slug);
|
|
36
|
+
await deps.invite({ target: input.agentUid, role: "member", sendEmail: false, companyUid, callerUid, token: input.token });
|
|
37
|
+
out.done.push(slug);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
out.failed.push({ slug, reason: reasonOf(err) });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
/** Remove the bot's membership in each company (the `hq members revoke` route). */
|
|
46
|
+
export async function removeBotFromCompanies(input, deps = defaultDeps) {
|
|
47
|
+
const out = { done: [], failed: [] };
|
|
48
|
+
for (const slug of input.companies) {
|
|
49
|
+
try {
|
|
50
|
+
const companyUid = await deps.companyUid(input.token, slug);
|
|
51
|
+
await deps.revoke(input.token, resolveRevokeTargetToMembershipKey(input.agentUid, companyUid), companyUid);
|
|
52
|
+
out.done.push(slug);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
out.failed.push({ slug, reason: reasonOf(err) });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
/** One line per failure, for an error message or a log. */
|
|
61
|
+
export function describeCompanyFailures(failed) {
|
|
62
|
+
return failed.map((f) => `${f.slug} (${f.reason})`).join("; ");
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=bot-companies.js.map
|
package/dist/commands/bot.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { type
|
|
2
|
+
import { type RuntimeSignInIssue } from "../lib/bot/runtime-sign-in.js";
|
|
3
|
+
import { type BotKind, type BotMemoryMode, type BotRuntimeId, type LaunchctlExec } from "../lib/bot/index.js";
|
|
3
4
|
/** Real launchctl; every failure is a non-ok result, never a throw. */
|
|
4
5
|
export declare const defaultLaunchctl: LaunchctlExec;
|
|
5
6
|
/** The desktop app creates HQ's setup bot under this name, always from the `setup` worker. */
|
|
@@ -23,10 +24,16 @@ export interface BotRow {
|
|
|
23
24
|
} | null;
|
|
24
25
|
online: boolean | null;
|
|
25
26
|
lastHeartbeatAt: string | null;
|
|
27
|
+
/** Present while the coding tool needs a new sign-in before the bot can work. */
|
|
28
|
+
runtimeSignIn: RuntimeSignInIssue | null;
|
|
26
29
|
daemonInstalled: boolean;
|
|
27
30
|
daemonLoaded: boolean;
|
|
28
31
|
dir: string;
|
|
29
32
|
introSentAt?: string;
|
|
33
|
+
/** "personal" acts as its owner everywhere; "company" acts as itself. */
|
|
34
|
+
kind: BotKind;
|
|
35
|
+
/** Company bots: the companies the bot is a member of (empty for a personal bot). */
|
|
36
|
+
companies: string[];
|
|
30
37
|
workerSource: "scaffold" | "worker";
|
|
31
38
|
workerDir: string;
|
|
32
39
|
workerId?: string;
|
|
@@ -43,6 +50,24 @@ export declare function listBotNames(root: string): string[];
|
|
|
43
50
|
export declare function botTokenSupplier(dir: string): () => Promise<string>;
|
|
44
51
|
/** Spawn `hq bot run <name>` detached (no launchd). */
|
|
45
52
|
export declare function spawnDetachedRun(name: string, dir: string): number;
|
|
53
|
+
/**
|
|
54
|
+
* The kind a new bot gets, before anything is provisioned. Personal unless
|
|
55
|
+
* `--kind company` is given (the desktop app always passes --kind). A company
|
|
56
|
+
* bot needs at least one company: `--company`, or the company worker's own
|
|
57
|
+
* company when created from one. A personal bot takes no `--company`, and
|
|
58
|
+
* HQ's setup bot is always personal.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveCreateKind(opts: {
|
|
61
|
+
kind?: string;
|
|
62
|
+
company?: string[];
|
|
63
|
+
worker?: string;
|
|
64
|
+
}, worker: {
|
|
65
|
+
id: string;
|
|
66
|
+
company?: string | null;
|
|
67
|
+
} | null): {
|
|
68
|
+
kind: BotKind;
|
|
69
|
+
companies: string[];
|
|
70
|
+
};
|
|
46
71
|
export declare function runBotCreate(nameArg: string, opts: {
|
|
47
72
|
runtime?: string;
|
|
48
73
|
model?: string;
|
|
@@ -55,6 +80,13 @@ export declare function runBotCreate(nameArg: string, opts: {
|
|
|
55
80
|
intro?: string;
|
|
56
81
|
kickoff?: string;
|
|
57
82
|
memory?: string;
|
|
83
|
+
kind?: string;
|
|
84
|
+
company?: string[];
|
|
85
|
+
}): Promise<void>;
|
|
86
|
+
export declare function runBotCompanies(nameArg: string, opts: {
|
|
87
|
+
add?: string[];
|
|
88
|
+
remove?: string[];
|
|
89
|
+
json?: boolean;
|
|
58
90
|
}): Promise<void>;
|
|
59
91
|
export declare function runBotWorkers(opts: {
|
|
60
92
|
json?: boolean;
|
package/dist/commands/bot.js
CHANGED
|
@@ -4,7 +4,8 @@ import { hasPromotionHold, promotionHoldSummary } from "../lib/bot/promotion-hol
|
|
|
4
4
|
/**
|
|
5
5
|
* `hq bot` — personal local bots (local-bots US-003/US-004/US-005).
|
|
6
6
|
*
|
|
7
|
-
* hq bot create <name> [--runtime claude|codex|grok] [--model m] [--effort level] [--worker id] [--intro text] [--kickoff prompt] [--memory synced|local] [--no-daemon]
|
|
7
|
+
* hq bot create <name> [--kind personal|company] [--company slug]... [--runtime claude|codex|grok] [--model m] [--effort level] [--worker id] [--intro text] [--kickoff prompt] [--memory synced|local] [--no-daemon]
|
|
8
|
+
* hq bot companies <name> [--add slug]... [--remove slug]... (a company bot's memberships)
|
|
8
9
|
* hq bot workers [--json] (company + core workers a bot can be created from)
|
|
9
10
|
* hq bot list [--json]
|
|
10
11
|
* hq bot start|stop|restart <name>
|
|
@@ -20,6 +21,7 @@ import { hasPromotionHold, promotionHoldSummary } from "../lib/bot/promotion-hol
|
|
|
20
21
|
* list (online flag), and rm. Secrets are never printed.
|
|
21
22
|
*/
|
|
22
23
|
import { promoteLocalBot } from "../lib/bot/promote.js";
|
|
24
|
+
import { addBotToCompanies, describeCompanyFailures, removeBotFromCompanies } from "./bot-companies.js";
|
|
23
25
|
import { registerBotContinuityCommand } from "./bot-continuity.js";
|
|
24
26
|
import chalk from "chalk";
|
|
25
27
|
import { execFileSync, spawn } from "node:child_process";
|
|
@@ -30,7 +32,11 @@ import { DEFAULT_COGNITO, ensureCognitoToken, resolveDefaultHqRoot, } from "../u
|
|
|
30
32
|
import { peekHqApiKey } from "../utils/resolve-vault-credential.js";
|
|
31
33
|
import { resolveCallerPersonUid } from "../utils/vault-api.js";
|
|
32
34
|
import { resolveHqBinary } from "../lib/mesh/live/daemon/install.js";
|
|
33
|
-
import {
|
|
35
|
+
import { runtimeDisplayName } from "../lib/bot/runtime-sign-in.js";
|
|
36
|
+
import { BotApi, BotApiError, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, } from "../lib/bot/index.js";
|
|
37
|
+
function collectRepeated(value, previous) {
|
|
38
|
+
return [...previous, value];
|
|
39
|
+
}
|
|
34
40
|
function fail(message) {
|
|
35
41
|
console.error(chalk.red(message));
|
|
36
42
|
process.exit(1);
|
|
@@ -134,10 +140,13 @@ function rowFor(name, opts = {}) {
|
|
|
134
140
|
promotionHold: promotionHoldSummary(dir, config.agentUid, config.ownerUid),
|
|
135
141
|
online: null,
|
|
136
142
|
lastHeartbeatAt: status?.lastHeartbeatAt ?? null,
|
|
143
|
+
runtimeSignIn: status?.runtimeSignIn ?? null,
|
|
137
144
|
daemonInstalled,
|
|
138
145
|
daemonLoaded,
|
|
139
146
|
dir,
|
|
140
147
|
introSentAt: config.introSentAt,
|
|
148
|
+
kind: effectiveBotKind(config),
|
|
149
|
+
companies: effectiveBotCompanies(config),
|
|
141
150
|
workerSource: config.workerSource ?? "scaffold",
|
|
142
151
|
workerDir: config.workerDir,
|
|
143
152
|
...(config.workerId ? { workerId: config.workerId } : {}),
|
|
@@ -290,6 +299,42 @@ async function stopBot(name, dir, config, output) {
|
|
|
290
299
|
console.log(signalled || gone ? chalk.green(`${name} stopped`) : chalk.dim(`${name} was not running`));
|
|
291
300
|
}
|
|
292
301
|
// ── create ───────────────────────────────────────────────────────────────────
|
|
302
|
+
/** "personal (acts as you)" / "company (acts as itself; member of a, b)". */
|
|
303
|
+
function describeKind(kind, companies) {
|
|
304
|
+
return kind === "company"
|
|
305
|
+
? `company (acts as itself; member of ${companies.length ? companies.join(", ") : "no company"})`
|
|
306
|
+
: "personal (acts as you)";
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* The kind a new bot gets, before anything is provisioned. Personal unless
|
|
310
|
+
* `--kind company` is given (the desktop app always passes --kind). A company
|
|
311
|
+
* bot needs at least one company: `--company`, or the company worker's own
|
|
312
|
+
* company when created from one. A personal bot takes no `--company`, and
|
|
313
|
+
* HQ's setup bot is always personal.
|
|
314
|
+
*/
|
|
315
|
+
export function resolveCreateKind(opts, worker) {
|
|
316
|
+
const given = opts.kind?.trim().toLowerCase();
|
|
317
|
+
if (given !== undefined && given !== "" && !isBotKind(given)) {
|
|
318
|
+
throw Object.assign(new Error(`--kind must be one of ${BOT_KINDS.join(", ")}`), { expected: true });
|
|
319
|
+
}
|
|
320
|
+
const requested = normalizeBotCompanies(opts.company);
|
|
321
|
+
const isSetup = worker?.id === SETUP_BOT_WORKER_ID;
|
|
322
|
+
const kind = isBotKind(given) ? given : "personal";
|
|
323
|
+
if (kind === "personal") {
|
|
324
|
+
if (requested.length > 0) {
|
|
325
|
+
throw Object.assign(new Error("--company only applies to a company bot (a personal bot acts as you, in every company you belong to). Add --kind company, or drop --company."), { expected: true });
|
|
326
|
+
}
|
|
327
|
+
return { kind, companies: [] };
|
|
328
|
+
}
|
|
329
|
+
if (isSetup) {
|
|
330
|
+
throw Object.assign(new Error("HQ's setup bot is always a personal bot; drop --kind company."), { expected: true });
|
|
331
|
+
}
|
|
332
|
+
const companies = requested.length > 0 ? requested : normalizeBotCompanies(worker?.company ? [worker.company] : []);
|
|
333
|
+
if (companies.length === 0) {
|
|
334
|
+
throw Object.assign(new Error("A company bot needs at least one company: add --company <slug> (repeat it for several), or create the bot from a company worker."), { expected: true });
|
|
335
|
+
}
|
|
336
|
+
return { kind, companies };
|
|
337
|
+
}
|
|
293
338
|
export async function runBotCreate(nameArg, opts) {
|
|
294
339
|
const name = validateBotName(nameArg);
|
|
295
340
|
const runtime = opts.runtime ?? "claude";
|
|
@@ -317,6 +362,7 @@ export async function runBotCreate(nameArg, opts) {
|
|
|
317
362
|
// Resolve the company worker before provisioning anything (a typo must not
|
|
318
363
|
// cost an identity).
|
|
319
364
|
const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
|
|
365
|
+
const { kind, companies } = resolveCreateKind(opts, worker);
|
|
320
366
|
const token = await ownerToken();
|
|
321
367
|
const owner = await resolveCallerPersonUid(token);
|
|
322
368
|
if (!owner)
|
|
@@ -324,7 +370,7 @@ export async function runBotCreate(nameArg, opts) {
|
|
|
324
370
|
const api = new BotApi({ token: async () => token });
|
|
325
371
|
let created;
|
|
326
372
|
try {
|
|
327
|
-
created = await api.createLocalBot({ name });
|
|
373
|
+
created = await api.createLocalBot({ name, kind });
|
|
328
374
|
}
|
|
329
375
|
catch (err) {
|
|
330
376
|
if (err instanceof BotApiError && err.code === "LOCAL_BOT_CAP_REACHED") {
|
|
@@ -350,6 +396,9 @@ export async function runBotCreate(nameArg, opts) {
|
|
|
350
396
|
agentUid,
|
|
351
397
|
ownerUid: created.agent.ownerUid ?? owner,
|
|
352
398
|
runtime,
|
|
399
|
+
kind,
|
|
400
|
+
// A company bot's list is filled in below, with the invites that went through.
|
|
401
|
+
...(kind === "company" ? { companies: [] } : {}),
|
|
353
402
|
...(model ? { model } : {}),
|
|
354
403
|
...(effort ? { effort } : {}),
|
|
355
404
|
...(opts.autoApprove === false ? { autoApprove: false } : {}),
|
|
@@ -377,15 +426,32 @@ export async function runBotCreate(nameArg, opts) {
|
|
|
377
426
|
enabled: true,
|
|
378
427
|
};
|
|
379
428
|
writeBotConfig(dir, config);
|
|
429
|
+
// A company bot acts as itself, so it must be a member of each of its
|
|
430
|
+
// companies: the owner invites it now (person token). Only the companies
|
|
431
|
+
// it actually joined are recorded, so after a failed invite the identity
|
|
432
|
+
// and bot.json stay on disk and `hq bot companies <name> --add <slug>`
|
|
433
|
+
// retries exactly the missing ones (or `hq bot rm <name>` undoes it all).
|
|
434
|
+
if (kind === "company") {
|
|
435
|
+
const outcome = await addBotToCompanies({ agentUid, companies, token });
|
|
436
|
+
config.companies = outcome.done;
|
|
437
|
+
writeBotConfig(dir, config);
|
|
438
|
+
if (outcome.failed.length > 0) {
|
|
439
|
+
createBotLogger(dir)("error", `created ${name} (${agentUid}) but could not add it to: ${describeCompanyFailures(outcome.failed)}`);
|
|
440
|
+
throw Object.assign(new Error(`Created the bot identity ${agentUid} but could not add it to ${outcome.failed.length === 1 ? "a company" : `${outcome.failed.length} companies`}: ${describeCompanyFailures(outcome.failed)}. ` +
|
|
441
|
+
`Fix the cause and run \`hq bot companies ${name} --add ${outcome.failed.map((f) => f.slug).join(" --add ")}\`, or remove the bot with \`hq bot rm ${name}\`.`), { expected: true });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
380
444
|
// A worker-sourced bot never writes into the company tree: only its own
|
|
381
445
|
// memory folder is scaffolded.
|
|
382
446
|
const scaffoldOpts = { memoryDir: config.memoryDir };
|
|
383
447
|
const scaffold = worker
|
|
384
448
|
? scaffoldBotMemory(hqRoot, name, undefined, scaffoldOpts)
|
|
385
449
|
: scaffoldBotWorker(hqRoot, name, agentUid, runtime, undefined, scaffoldOpts);
|
|
386
|
-
createBotLogger(dir)("info", `created ${name} (${agentUid}) runtime=${runtime}${worker ? ` worker=${worker.id} company=${worker.company ?? "-"}` : ""} memory=${memoryMode}`);
|
|
450
|
+
createBotLogger(dir)("info", `created ${name} (${agentUid}) kind=${kind}${companies.length ? ` companies=${companies.join(",")}` : ""} runtime=${runtime}${worker ? ` worker=${worker.id} company=${worker.company ?? "-"}` : ""} memory=${memoryMode}`);
|
|
387
451
|
const created_ = {
|
|
388
452
|
agentUid,
|
|
453
|
+
kind,
|
|
454
|
+
companies,
|
|
389
455
|
runtime,
|
|
390
456
|
dir,
|
|
391
457
|
worker: worker ? path.join(hqRoot, worker.path) : scaffold.dir,
|
|
@@ -405,6 +471,7 @@ export async function runBotCreate(nameArg, opts) {
|
|
|
405
471
|
}
|
|
406
472
|
if (!opts.json) {
|
|
407
473
|
console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${agentUid}, ${runtime})`));
|
|
474
|
+
console.log(chalk.dim(` kind: ${describeKind(kind, companies)}`));
|
|
408
475
|
if (worker)
|
|
409
476
|
console.log(chalk.dim(` worker: ${worker.id}${worker.company ? ` (${worker.company})` : ""} — ${path.join(hqRoot, worker.path)}`));
|
|
410
477
|
if (!worker)
|
|
@@ -434,6 +501,66 @@ export async function runBotCreate(nameArg, opts) {
|
|
|
434
501
|
console.log(JSON.stringify({ ok: true, name, ...created_, daemon: daemonMessage, running: false }, null, 2));
|
|
435
502
|
}
|
|
436
503
|
}
|
|
504
|
+
// ── companies ────────────────────────────────────────────────────────────────
|
|
505
|
+
export async function runBotCompanies(nameArg, opts) {
|
|
506
|
+
const { name, dir, config } = requireBot(nameArg);
|
|
507
|
+
const kind = effectiveBotKind(config);
|
|
508
|
+
const add = normalizeBotCompanies(opts.add);
|
|
509
|
+
const remove = normalizeBotCompanies(opts.remove);
|
|
510
|
+
if (kind !== "company") {
|
|
511
|
+
throw Object.assign(new Error(`${name} is a personal bot: it acts as you, in every company you belong to, so it has no company list of its own. Create a company bot with: hq bot create <name> --kind company --company <slug>`), { expected: true });
|
|
512
|
+
}
|
|
513
|
+
const current = effectiveBotCompanies(config);
|
|
514
|
+
const both = add.filter((c) => remove.includes(c));
|
|
515
|
+
if (both.length > 0) {
|
|
516
|
+
throw Object.assign(new Error(`--add and --remove name the same company: ${both.join(", ")}`), { expected: true });
|
|
517
|
+
}
|
|
518
|
+
const toAdd = add.filter((c) => !current.includes(c));
|
|
519
|
+
const toRemove = remove.filter((c) => current.includes(c));
|
|
520
|
+
const unknown = remove.filter((c) => !current.includes(c));
|
|
521
|
+
if (unknown.length > 0) {
|
|
522
|
+
throw Object.assign(new Error(`${name} is not in ${unknown.join(", ")} (its companies: ${current.join(", ") || "none"}).`), { expected: true });
|
|
523
|
+
}
|
|
524
|
+
const next = [...current.filter((c) => !toRemove.includes(c)), ...toAdd];
|
|
525
|
+
if (next.length === 0) {
|
|
526
|
+
throw Object.assign(new Error(`A company bot needs at least one company; ${name} would be left with none. Remove the bot instead with: hq bot rm ${name}`), { expected: true });
|
|
527
|
+
}
|
|
528
|
+
const failed = [];
|
|
529
|
+
let added = [];
|
|
530
|
+
let removed = [];
|
|
531
|
+
if (toAdd.length > 0 || toRemove.length > 0) {
|
|
532
|
+
const token = await ownerToken();
|
|
533
|
+
if (toAdd.length > 0) {
|
|
534
|
+
const r = await addBotToCompanies({ agentUid: config.agentUid, companies: toAdd, token });
|
|
535
|
+
added = r.done;
|
|
536
|
+
failed.push(...r.failed);
|
|
537
|
+
}
|
|
538
|
+
if (toRemove.length > 0) {
|
|
539
|
+
const r = await removeBotFromCompanies({ agentUid: config.agentUid, companies: toRemove, token });
|
|
540
|
+
removed = r.done;
|
|
541
|
+
failed.push(...r.failed);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const companies = [...current.filter((c) => !removed.includes(c)), ...added];
|
|
545
|
+
if (added.length > 0 || removed.length > 0)
|
|
546
|
+
patchBotConfig(dir, { kind: "company", companies });
|
|
547
|
+
if (opts.json) {
|
|
548
|
+
console.log(JSON.stringify({ ok: failed.length === 0, name, kind, companies, added, removed, failed }, null, 2));
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
console.log(`${chalk.bold(name)} company bot, member of: ${companies.join(", ")}`);
|
|
552
|
+
if (added.length > 0)
|
|
553
|
+
console.log(chalk.green(` added: ${added.join(", ")}`));
|
|
554
|
+
if (removed.length > 0)
|
|
555
|
+
console.log(chalk.green(` removed: ${removed.join(", ")}`));
|
|
556
|
+
if (companies.length > 0 && (added.length > 0 || removed.length > 0)) {
|
|
557
|
+
console.log(chalk.dim(` restart the bot so its instructions pick this up: hq bot restart ${name}`));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (failed.length > 0) {
|
|
561
|
+
throw Object.assign(new Error(`Could not change ${name}'s membership in: ${describeCompanyFailures(failed)}`), { expected: true });
|
|
562
|
+
}
|
|
563
|
+
}
|
|
437
564
|
// ── workers ──────────────────────────────────────────────────────────────────
|
|
438
565
|
export async function runBotWorkers(opts) {
|
|
439
566
|
const hqRoot = resolveDefaultHqRoot({ onMissing: "throw" });
|
|
@@ -473,7 +600,7 @@ async function runBotList(opts) {
|
|
|
473
600
|
return;
|
|
474
601
|
}
|
|
475
602
|
for (const r of rows) {
|
|
476
|
-
console.log(`${chalk.bold(r.name.padEnd(16))} ${r.agentUid} ${r.runtime.padEnd(6)} ${humanOnline(r).padEnd(8)} ${r.processAlive ? chalk.green(`pid ${r.pid}`) : chalk.gray(r.state)}${r.daemonInstalled ? chalk.dim(" launchd") : ""}${r.workerId ? chalk.dim(` worker ${r.workerId}${r.companySlug ? ` (${r.companySlug})` : ""}`) : ""}`);
|
|
603
|
+
console.log(`${chalk.bold(r.name.padEnd(16))} ${r.agentUid} ${r.runtime.padEnd(6)} ${humanOnline(r).padEnd(8)} ${r.processAlive ? chalk.green(`pid ${r.pid}`) : chalk.gray(r.state)}${chalk.dim(` ${r.kind === "company" ? `company bot: ${r.companies.join(", ") || "no company"}` : "personal"}`)}${r.daemonInstalled ? chalk.dim(" launchd") : ""}${r.workerId ? chalk.dim(` worker ${r.workerId}${r.companySlug ? ` (${r.companySlug})` : ""}`) : ""}`);
|
|
477
604
|
}
|
|
478
605
|
}
|
|
479
606
|
async function runBotStatus(nameArg, opts) {
|
|
@@ -487,6 +614,7 @@ async function runBotStatus(nameArg, opts) {
|
|
|
487
614
|
return;
|
|
488
615
|
}
|
|
489
616
|
console.log(`${chalk.bold(name)} ${row.agentUid}`);
|
|
617
|
+
console.log(` kind: ${describeKind(row.kind, row.companies)}`);
|
|
490
618
|
console.log(` runtime: ${row.runtime} model ${row.model ?? "default"} thinking ${row.effort}${row.effortIsDefault ? " (default)" : ""}`);
|
|
491
619
|
if (row.workerId)
|
|
492
620
|
console.log(` worker: ${row.workerId}${row.companySlug ? ` (company ${row.companySlug})` : ""} ${chalk.dim(row.workerDir)}`);
|
|
@@ -497,6 +625,10 @@ async function runBotStatus(nameArg, opts) {
|
|
|
497
625
|
console.log(` creds: ${creds ? `present for ${creds.entityUid}` : chalk.red("missing")}`);
|
|
498
626
|
if (status) {
|
|
499
627
|
console.log(` started: ${status.startedAt} restarts: ${status.restarts} replies: ${status.repliesSent}`);
|
|
628
|
+
if (status.runtimeSignIn?.state === "expired") {
|
|
629
|
+
const tool = runtimeDisplayName(status.runtimeSignIn.runtime);
|
|
630
|
+
console.log(` ${chalk.yellow(`${tool} needs to sign in again`)} — sign in from HQ, or run \`${status.runtimeSignIn.runtime} ${status.runtimeSignIn.runtime === "claude" ? "auth login" : "login"}\`, then \`hq bot restart ${name}\``);
|
|
631
|
+
}
|
|
500
632
|
if (status.lastError)
|
|
501
633
|
console.log(` last error: ${chalk.yellow(status.lastError)}`);
|
|
502
634
|
}
|
|
@@ -568,15 +700,22 @@ async function runBotRm(nameArg, opts) {
|
|
|
568
700
|
throw err;
|
|
569
701
|
}
|
|
570
702
|
const terminal = "alreadyGone" in identity ? true : identity.terminal;
|
|
703
|
+
// A company bot is a member of its companies: leave them too. Best effort —
|
|
704
|
+
// a stale membership row must not keep the bot's local state around.
|
|
705
|
+
const memberships = effectiveBotCompanies(config);
|
|
706
|
+
const left = memberships.length > 0 ? await removeBotFromCompanies({ agentUid: config.agentUid, companies: memberships, token }) : { done: [], failed: [] };
|
|
571
707
|
deleteBotCreds(dir);
|
|
572
708
|
if (terminal)
|
|
573
709
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
574
710
|
if (opts.json) {
|
|
575
|
-
console.log(JSON.stringify({ ok: terminal, name, agentUid: config.agentUid, daemon: daemon.message, identity, localStateRemoved: terminal, workerKept: path.join(config.hqRoot, config.workerDir) }, null, 2));
|
|
711
|
+
console.log(JSON.stringify({ ok: terminal, name, agentUid: config.agentUid, daemon: daemon.message, identity, companiesLeft: left.done, companiesNotLeft: left.failed, localStateRemoved: terminal, workerKept: path.join(config.hqRoot, config.workerDir) }, null, 2));
|
|
576
712
|
return;
|
|
577
713
|
}
|
|
714
|
+
for (const f of left.failed) {
|
|
715
|
+
console.log(chalk.yellow(` could not remove ${name} from ${f.slug}: ${f.reason}`) + chalk.dim(` — remove it by hand: hq members --company ${f.slug} revoke ${config.agentUid}`));
|
|
716
|
+
}
|
|
578
717
|
if (terminal) {
|
|
579
|
-
console.log(chalk.green(`Removed bot ${name}`) + chalk.dim(` (${config.agentUid}); worker folder kept at ${path.join(config.hqRoot, config.workerDir)}`));
|
|
718
|
+
console.log(chalk.green(`Removed bot ${name}`) + chalk.dim(` (${config.agentUid}${left.done.length ? `; left ${left.done.join(", ")}` : ""}); worker folder kept at ${path.join(config.hqRoot, config.workerDir)}`));
|
|
580
719
|
}
|
|
581
720
|
else {
|
|
582
721
|
console.log(chalk.yellow(`Bot ${name} is stopped locally but its cloud identity is not fully removed yet.`));
|
|
@@ -668,6 +807,8 @@ export function registerBotCommand(program) {
|
|
|
668
807
|
bot
|
|
669
808
|
.command("create <name>")
|
|
670
809
|
.description("Provision a local bot identity, scaffold its worker folder, install its launchd agent, and start it")
|
|
810
|
+
.option("--kind <kind>", `personal (acts as you, everywhere you are) or company (acts as itself, as a member of its companies). Default: personal, or company when created from a company worker`)
|
|
811
|
+
.option("--company <slug>", "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation", collectRepeated, [])
|
|
671
812
|
.option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`, "claude")
|
|
672
813
|
.option("--model <model>", "Model override passed to the runtime CLI")
|
|
673
814
|
.option("--effort <level>", `Thinking level: claude ${BOT_EFFORT_LEVELS.claude.join("|")}; codex ${BOT_EFFORT_LEVELS.codex.join("|")}; grok ${BOT_EFFORT_LEVELS.grok.join("|")} (default ${DEFAULT_BOT_EFFORT})`)
|
|
@@ -680,6 +821,13 @@ export function registerBotCommand(program) {
|
|
|
680
821
|
.option("--no-start", "Create without starting")
|
|
681
822
|
.option("--json", "Print machine-readable JSON")
|
|
682
823
|
.action((name, opts) => wrap(() => runBotCreate(name, opts))());
|
|
824
|
+
bot
|
|
825
|
+
.command("companies <name>")
|
|
826
|
+
.description("Show or change which companies a company bot is a member of (you invite or remove it; a personal bot has no list of its own)")
|
|
827
|
+
.option("--add <slug>", "Add the bot to this company (repeatable)", collectRepeated, [])
|
|
828
|
+
.option("--remove <slug>", "Remove the bot from this company (repeatable)", collectRepeated, [])
|
|
829
|
+
.option("--json", "Print machine-readable JSON")
|
|
830
|
+
.action((name, opts) => wrap(() => runBotCompanies(name, opts))());
|
|
683
831
|
bot
|
|
684
832
|
.command("workers")
|
|
685
833
|
.description("List the workers (templates) a bot can be created from (hq bot create <name> --worker <id>)")
|
|
@@ -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
|