@indigoai-us/hq-cli 5.345.170 → 5.345.171
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/dist/command-catalog.generated.d.ts +132 -5
- package/dist/command-catalog.generated.js +164 -6
- package/dist/commands/access.d.ts +32 -1
- package/dist/commands/access.js +112 -3
- package/dist/commands/agent-kit.d.ts +1 -1
- package/dist/commands/agent-kit.js +3 -1
- package/dist/commands/agents.js +2 -1
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +137 -14
- package/dist/commands/cloud.d.ts +58 -1
- package/dist/commands/cloud.js +219 -5
- package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
- package/dist/commands/core-company-index-scope-flag.js +9 -2
- package/dist/commands/core-delegate-w1b.js +5 -2
- package/dist/commands/core-dm-bind-native.js +19 -4
- package/dist/commands/core-rebuild-index-fast.js +24 -6
- package/dist/commands/core.js +18 -6
- package/dist/commands/dm.d.ts +17 -3
- package/dist/commands/dm.js +121 -26
- package/dist/commands/integrations-api.d.ts +4 -0
- package/dist/commands/integrations-api.js +18 -2
- package/dist/commands/integrations-connect.d.ts +10 -3
- package/dist/commands/integrations-connect.js +70 -6
- package/dist/commands/integrations-core.d.ts +23 -0
- package/dist/commands/integrations-core.js +100 -0
- package/dist/commands/integrations-manage.js +8 -1
- package/dist/commands/integrations.js +49 -5
- package/dist/commands/lanes-loop.d.ts +20 -0
- package/dist/commands/lanes-loop.js +69 -11
- package/dist/commands/lanes.js +9 -1
- package/dist/commands/rescue.d.ts +9 -0
- package/dist/commands/rescue.js +31 -0
- package/dist/commands/run.js +115 -40
- package/dist/commands/scan.d.ts +5 -1
- package/dist/commands/scan.js +16 -4
- package/dist/commands/secrets.js +50 -9
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2367 -2109
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
- package/dist/lib/agent-kit/inbox-state.js +41 -20
- package/dist/lib/agent-kit/kit-config.d.ts +2 -0
- package/dist/lib/agent-kit/kit-config.js +6 -0
- package/dist/lib/agent-kit/mcp/tools.js +12 -5
- package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
- package/dist/lib/agent-kit/run/heartbeat.js +6 -3
- package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
- package/dist/lib/agent-kit/run/inbox.js +42 -10
- package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
- package/dist/lib/agent-kit/run/request-timeout.js +51 -0
- package/dist/lib/bot/api.d.ts +1 -1
- package/dist/lib/bot/config.d.ts +10 -0
- package/dist/lib/bot/config.js +17 -1
- package/dist/lib/bot/fleet-local.d.ts +127 -0
- package/dist/lib/bot/fleet-local.js +638 -0
- package/dist/lib/bot/index.d.ts +1 -0
- package/dist/lib/bot/index.js +1 -0
- package/dist/lib/bot/local-config.d.ts +3 -2
- package/dist/lib/bot/local-config.js +5 -2
- package/dist/lib/bot/promote.d.ts +10 -0
- package/dist/lib/bot/promote.js +3 -2
- package/dist/lib/bot/runtime/index.js +8 -4
- package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
- package/dist/lib/bot/runtime/scratch-dir.js +41 -0
- package/dist/lib/caller-principal.d.ts +4 -0
- package/dist/lib/caller-principal.js +40 -0
- package/dist/lib/capacity/collectors/grok.js +8 -2
- package/dist/lib/cli-flag-keys.d.ts +2 -0
- package/dist/lib/cli-flag-keys.js +2 -0
- package/dist/lib/daemon/catalog.js +4 -1
- package/dist/lib/daemon/sync-core-canary.d.ts +3 -0
- package/dist/lib/daemon/sync-core-canary.js +8 -3
- package/dist/lib/doctor/checks/release-links.d.ts +62 -1
- package/dist/lib/doctor/checks/release-links.js +188 -2
- package/dist/lib/doctor/fix/apply.js +54 -0
- package/dist/lib/doctor/fix/remediation.d.ts +8 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/index-render/index.d.ts +9 -0
- package/dist/lib/index-render/index.js +65 -5
- package/dist/lib/index-render/session-scope.d.ts +69 -0
- package/dist/lib/index-render/session-scope.js +279 -0
- package/dist/lib/lanes/admission.js +7 -20
- package/dist/lib/lanes/reconcile.js +13 -15
- package/dist/lib/lanes/stale-lock.d.ts +50 -0
- package/dist/lib/lanes/stale-lock.js +105 -0
- package/dist/lib/lanes/store.js +11 -76
- package/dist/lib/notify-recipient.d.ts +10 -4
- package/dist/lib/notify-recipient.js +16 -4
- package/dist/lib/secret-input-link.d.ts +42 -0
- package/dist/lib/secret-input-link.js +93 -0
- package/dist/lib/secret-scan/range.d.ts +57 -0
- package/dist/lib/secret-scan/range.js +266 -0
- package/dist/lib/secret-scan/repo-config.d.ts +18 -3
- package/dist/lib/secret-scan/repo-config.js +135 -2
- package/dist/run/script-bound-schema.d.ts +27 -0
- package/dist/run/script-bound-schema.js +97 -0
- package/dist/secrets-env-action.d.ts +2 -0
- package/dist/secrets-env-action.js +7 -2
- package/dist/utils/cognito-session.d.ts +3 -30
- package/dist/utils/cognito-session.js +2 -100
- package/dist/utils/machine-principal-state.d.ts +38 -0
- package/dist/utils/machine-principal-state.js +110 -0
- package/dist/utils/safe-link.js +9 -0
- package/dist/utils/script-bound-exec.d.ts +158 -0
- package/dist/utils/script-bound-exec.js +568 -0
- package/dist/utils/sync-watchdog.d.ts +92 -0
- package/dist/utils/sync-watchdog.js +250 -0
- package/package.json +8 -8
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { Command } from "commander";
|
|
17
17
|
import { type CompanyBrowseClientFactory, type FilesBrowseVaultClient, type RunGetResult } from "./files-browse.js";
|
|
18
18
|
import { type AclLock } from "./acl-lock.js";
|
|
19
|
-
export type AccessOutcome = "never-existed" | "local" | "not-synced" | "no-access" | "ambiguous" | "pending-confirmation";
|
|
19
|
+
export type AccessOutcome = "never-existed" | "local" | "not-synced" | "no-access" | "no-write-access" | "ambiguous" | "pending-confirmation";
|
|
20
20
|
export interface AccessAclTree {
|
|
21
21
|
prefix: string;
|
|
22
22
|
direct: Array<{
|
|
@@ -151,6 +151,15 @@ export interface RunAccessResult {
|
|
|
151
151
|
* grants (including a root `*` company-wide grant) do not apply.
|
|
152
152
|
*/
|
|
153
153
|
lockedBy?: AclLock;
|
|
154
|
+
/**
|
|
155
|
+
* Set with outcome "no-write-access": the ACL row that blocks writes to a
|
|
156
|
+
* path that is on disk but not in the vault, so a push cannot upload it.
|
|
157
|
+
* Anchored as companies/<slug>/<prefix>; absent when only the effective
|
|
158
|
+
* permission is known.
|
|
159
|
+
*/
|
|
160
|
+
writeBlockedBy?: string;
|
|
161
|
+
/** Effective permission on the path when outcome is "no-write-access". */
|
|
162
|
+
permission?: string | null;
|
|
154
163
|
}
|
|
155
164
|
export interface AccessAsResult {
|
|
156
165
|
personUid: string;
|
|
@@ -164,6 +173,28 @@ export interface AccessAsResult {
|
|
|
164
173
|
export declare function outcomeExitCode(outcome: AccessOutcome): 0 | 2 | 3 | 4;
|
|
165
174
|
export declare function formatAccessOutcome(result: RunAccessResult): string;
|
|
166
175
|
export declare function resolveGrantor(tree: AccessAclTree | null, members: AccessMember[]): AccessGrantor | null;
|
|
176
|
+
/**
|
|
177
|
+
* ACL evidence for one key. `tree` is the full acl/tree response (which keeps
|
|
178
|
+
* the deny / no-record distinction and the ACL creator for grantor lookup).
|
|
179
|
+
* `resolved` is a non-null permission from the batch acl/resolve route.
|
|
180
|
+
*/
|
|
181
|
+
export type AclVerdict = {
|
|
182
|
+
kind: "tree";
|
|
183
|
+
tree: AccessAclTree | null;
|
|
184
|
+
} | {
|
|
185
|
+
kind: "resolved";
|
|
186
|
+
permission: string;
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Why the caller cannot write `relative`, or null when it can or the ACL says
|
|
190
|
+
* nothing (no record: membership-distributed access decides, which this
|
|
191
|
+
* command cannot see). The tree is fetched when the fast resolve path gave
|
|
192
|
+
* only a permission, so the blocking row can be named.
|
|
193
|
+
*/
|
|
194
|
+
export declare function writeBlocker(verdict: AclVerdict, relative: string, deps: Pick<AccessDeps, "fetchAclTree" | "companySlug">): Promise<{
|
|
195
|
+
permission: string | null;
|
|
196
|
+
blockedBy?: string;
|
|
197
|
+
} | null>;
|
|
167
198
|
export declare function runAccess(input: RunAccessInput): Promise<RunAccessResult>;
|
|
168
199
|
export interface RunAccessBatchInput {
|
|
169
200
|
targets: string[];
|
package/dist/commands/access.js
CHANGED
|
@@ -26,6 +26,7 @@ import { listActiveMembers } from "./members.js";
|
|
|
26
26
|
import { aclLockedBy } from "./acl-lock.js";
|
|
27
27
|
import { buildDmBody } from "./dm.js";
|
|
28
28
|
import { defaultAudienceFor, recipientKindOf } from "../lib/notify-recipient.js";
|
|
29
|
+
import { callerIsMachinePrincipal } from "../lib/caller-principal.js";
|
|
29
30
|
import { peekIdToken } from "../utils/id-token.js";
|
|
30
31
|
import { findRecentAccessRequest, formatTimeAgo, recordAccessRequest, } from "../utils/access-requests.js";
|
|
31
32
|
import { recordAccessOutcome } from "../utils/access-outcomes.js";
|
|
@@ -34,8 +35,11 @@ export function outcomeExitCode(outcome) {
|
|
|
34
35
|
return 0;
|
|
35
36
|
if (outcome === "never-existed")
|
|
36
37
|
return 2;
|
|
37
|
-
if (outcome === "no-access" ||
|
|
38
|
+
if (outcome === "no-access" ||
|
|
39
|
+
outcome === "no-write-access" ||
|
|
40
|
+
outcome === "pending-confirmation") {
|
|
38
41
|
return 3;
|
|
42
|
+
}
|
|
39
43
|
return 4;
|
|
40
44
|
}
|
|
41
45
|
export function formatAccessOutcome(result) {
|
|
@@ -57,6 +61,16 @@ export function formatAccessOutcome(result) {
|
|
|
57
61
|
lines.push(`You do not have read access to this path.`);
|
|
58
62
|
lines.push(result.path);
|
|
59
63
|
break;
|
|
64
|
+
case "no-write-access":
|
|
65
|
+
lines.push(`The file is on this computer but not in the ${result.company} vault, and you cannot write to it, so a push cannot upload it.`);
|
|
66
|
+
lines.push(result.path);
|
|
67
|
+
if (result.writeBlockedBy) {
|
|
68
|
+
lines.push(`Write is blocked by the ACL on ${result.writeBlockedBy} (your permission: ${result.permission ?? "none"}). Ask its creator or a company admin for write access.`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
lines.push(`Your permission on this path is ${result.permission ?? "none"}. Ask a company admin for write access.`);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
60
74
|
case "pending-confirmation":
|
|
61
75
|
lines.push(`Ask the grantor for read access to this path.`);
|
|
62
76
|
lines.push(result.path);
|
|
@@ -243,6 +257,69 @@ function startEarly(promise) {
|
|
|
243
257
|
promise.catch(() => undefined);
|
|
244
258
|
return promise;
|
|
245
259
|
}
|
|
260
|
+
const WRITE_PERMISSIONS = new Set(["write", "admin", "owner"]);
|
|
261
|
+
/**
|
|
262
|
+
* Why the caller cannot write `relative`, or null when it can or the ACL says
|
|
263
|
+
* nothing (no record: membership-distributed access decides, which this
|
|
264
|
+
* command cannot see). The tree is fetched when the fast resolve path gave
|
|
265
|
+
* only a permission, so the blocking row can be named.
|
|
266
|
+
*/
|
|
267
|
+
export async function writeBlocker(verdict, relative, deps) {
|
|
268
|
+
const resolvedPermission = verdict.kind === "resolved" ? verdict.permission : undefined;
|
|
269
|
+
if (resolvedPermission && WRITE_PERMISSIONS.has(resolvedPermission))
|
|
270
|
+
return null;
|
|
271
|
+
let tree = verdict.kind === "tree" ? verdict.tree : null;
|
|
272
|
+
if (verdict.kind === "resolved") {
|
|
273
|
+
try {
|
|
274
|
+
tree = await deps.fetchAclTree(relative);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
tree = null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const permission = resolvedPermission ?? tree?.effectivePermission ?? null;
|
|
281
|
+
if (permission && WRITE_PERMISSIONS.has(permission))
|
|
282
|
+
return null;
|
|
283
|
+
const covering = tree?.coveringAclPath;
|
|
284
|
+
if (typeof covering === "string" && covering !== "") {
|
|
285
|
+
return {
|
|
286
|
+
permission,
|
|
287
|
+
blockedBy: toBucketRelative(covering, deps.companySlug),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
// A row on the path itself with a known non-write permission blocks it.
|
|
291
|
+
// With no permission reported, nothing proves the path is blocked.
|
|
292
|
+
if (permission && tree?.directRow) {
|
|
293
|
+
return { permission, blockedBy: toBucketRelative(tree.prefix, deps.companySlug) };
|
|
294
|
+
}
|
|
295
|
+
// No ACL row covers the path. A known non-write permission still blocks.
|
|
296
|
+
if (resolvedPermission)
|
|
297
|
+
return { permission };
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Owners and admins write past ACL rows on the server (role bypass), which
|
|
302
|
+
* the ACL tree does not always show. Treat a lookup failure as no bypass only
|
|
303
|
+
* when the member list loads; if it cannot be read, assume a bypass so the
|
|
304
|
+
* command keeps its old "never-existed" answer instead of a wrong blocker.
|
|
305
|
+
*/
|
|
306
|
+
async function callerHasRoleBypass(deps) {
|
|
307
|
+
try {
|
|
308
|
+
const [members, requester] = await Promise.all([
|
|
309
|
+
deps.listMembers(),
|
|
310
|
+
resolveRequester(deps.requester),
|
|
311
|
+
]);
|
|
312
|
+
const email = (requester.email ?? "").trim().toLowerCase();
|
|
313
|
+
// An unknown caller cannot be matched; keep the old answer.
|
|
314
|
+
if (email === "")
|
|
315
|
+
return true;
|
|
316
|
+
return members.some((m) => (m.personEmail ?? "").trim().toLowerCase() === email &&
|
|
317
|
+
(m.role === "owner" || m.role === "admin"));
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
246
323
|
function verdictDenied(verdict) {
|
|
247
324
|
return verdict.kind === "tree" ? aclDenied(verdict.tree) : false;
|
|
248
325
|
}
|
|
@@ -388,6 +465,31 @@ async function locateTarget(target, deps, ctx) {
|
|
|
388
465
|
},
|
|
389
466
|
};
|
|
390
467
|
}
|
|
468
|
+
// feedback_5baccb9e: a file that is on disk but absent from the vault
|
|
469
|
+
// is usually one a push could not upload. When the ACL blocks writes,
|
|
470
|
+
// say so instead of "never existed".
|
|
471
|
+
if (relative !== "" &&
|
|
472
|
+
fs.existsSync(path.join(deps.hqRoot, "companies", slug, relative))) {
|
|
473
|
+
steps.push("disk: present");
|
|
474
|
+
const blocker = await writeBlocker(verdict, relative, deps);
|
|
475
|
+
if (blocker && !(await callerHasRoleBypass(deps))) {
|
|
476
|
+
steps.push(blocker.blockedBy
|
|
477
|
+
? `acl: write blocked by ${anchored(blocker.blockedBy)} (${blocker.permission ?? "none"})`
|
|
478
|
+
: `acl: no write (${blocker.permission ?? "none"})`);
|
|
479
|
+
return {
|
|
480
|
+
kind: "terminal",
|
|
481
|
+
outcome: "no-write-access",
|
|
482
|
+
fields: {
|
|
483
|
+
path: anchored(relative),
|
|
484
|
+
company: slug,
|
|
485
|
+
exists: false,
|
|
486
|
+
steps,
|
|
487
|
+
permission: blocker.permission,
|
|
488
|
+
...(blocker.blockedBy ? { writeBlockedBy: anchored(blocker.blockedBy) } : {}),
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
391
493
|
return {
|
|
392
494
|
kind: "terminal",
|
|
393
495
|
outcome: "never-existed",
|
|
@@ -857,6 +959,11 @@ function jsonPayload(result) {
|
|
|
857
959
|
payload.alreadyAskedAt = result.alreadyAskedAt;
|
|
858
960
|
if (result.lockedBy)
|
|
859
961
|
payload.lockedBy = result.lockedBy;
|
|
962
|
+
if (result.outcome === "no-write-access") {
|
|
963
|
+
payload.permission = result.permission ?? null;
|
|
964
|
+
if (result.writeBlockedBy)
|
|
965
|
+
payload.writeBlockedBy = result.writeBlockedBy;
|
|
966
|
+
}
|
|
860
967
|
return payload;
|
|
861
968
|
}
|
|
862
969
|
function emit(input, result, log) {
|
|
@@ -933,7 +1040,9 @@ export function registerAccessCommand(program) {
|
|
|
933
1040
|
Exit codes:
|
|
934
1041
|
0 local or not-synced (file exists and you can read it)
|
|
935
1042
|
2 never-existed (the path was never created)
|
|
936
|
-
3 no-access (the file exists but you cannot read it)
|
|
1043
|
+
3 no-access (the file exists but you cannot read it), or
|
|
1044
|
+
no-write-access (the file is on disk, not in the vault, and you cannot
|
|
1045
|
+
write to it, so a push cannot upload it)
|
|
937
1046
|
4 ambiguous (several keys matched; pick one)
|
|
938
1047
|
|
|
939
1048
|
With several paths, each gets its own result block and the exit code is the
|
|
@@ -1161,7 +1270,7 @@ async function buildLiveAccessDeps(slug, hqRoot) {
|
|
|
1161
1270
|
prompt: msg.prompt,
|
|
1162
1271
|
details: msg.details,
|
|
1163
1272
|
now: Date.now(),
|
|
1164
|
-
audience: defaultAudienceFor(recipientKindOf(msg.recipient)),
|
|
1273
|
+
audience: defaultAudienceFor(recipientKindOf(msg.recipient), callerIsMachinePrincipal()),
|
|
1165
1274
|
});
|
|
1166
1275
|
const res = await vaultApiFetch({
|
|
1167
1276
|
token,
|
|
@@ -75,7 +75,7 @@ export interface KitRuntime {
|
|
|
75
75
|
paths: AgentKitPaths;
|
|
76
76
|
creds: ExternalMachineCreds;
|
|
77
77
|
config: KitConfig;
|
|
78
|
-
getToken: () => Promise<string>;
|
|
78
|
+
getToken: (signal?: AbortSignal) => Promise<string>;
|
|
79
79
|
log: KitLogger;
|
|
80
80
|
credsSource?: HeartbeatCredsSource;
|
|
81
81
|
}
|
|
@@ -153,7 +153,7 @@ export function buildKitRuntime(service) {
|
|
|
153
153
|
process.env.HQ_REQUIRE_MACHINE_IDENTITY = "1";
|
|
154
154
|
process.env.HQ_VAULT_API_URL = creds.apiBaseUrl;
|
|
155
155
|
const log = createKitLogger(paths, service, { echo: process.stdout.isTTY === true });
|
|
156
|
-
const getToken = () => ensureCognitoToken({ tokenSource: "machine", interactive: false });
|
|
156
|
+
const getToken = (signal) => ensureCognitoToken({ tokenSource: "machine", interactive: false, ...(signal ? { signal } : {}) });
|
|
157
157
|
return { paths, creds, config, getToken, log, credsSource };
|
|
158
158
|
}
|
|
159
159
|
export async function runKitService(service, rt) {
|
|
@@ -177,6 +177,7 @@ export async function runKitService(service, rt) {
|
|
|
177
177
|
},
|
|
178
178
|
apiBaseUrl: rt.creds.apiBaseUrl,
|
|
179
179
|
intervalMs: rt.config.heartbeatIntervalMs,
|
|
180
|
+
requestTimeoutMs: rt.config.requestTimeoutMs,
|
|
180
181
|
getToken: rt.getToken,
|
|
181
182
|
log: rt.log,
|
|
182
183
|
components: enabledKitComponents(rt.config),
|
|
@@ -192,6 +193,7 @@ export async function runKitService(service, rt) {
|
|
|
192
193
|
agentUid: rt.creds.entityUid,
|
|
193
194
|
apiBaseUrl: rt.creds.apiBaseUrl,
|
|
194
195
|
pollMs: rt.config.inboxPollMs,
|
|
196
|
+
requestTimeoutMs: rt.config.requestTimeoutMs,
|
|
195
197
|
ack: rt.config.inboxAck,
|
|
196
198
|
getToken: rt.getToken,
|
|
197
199
|
log: rt.log,
|
package/dist/commands/agents.js
CHANGED
|
@@ -43,6 +43,7 @@ import { launchSessionManagerPlugin, SessionManagerPluginLaunchError, TerminalSe
|
|
|
43
43
|
import { peekIdToken } from "../utils/id-token.js";
|
|
44
44
|
import { canOfferTeamUpgrade, formatUpgradeRefusal, isPlanGateError, offerUpgradeLink, } from "../utils/plan-gate-error.js";
|
|
45
45
|
import { AGENT_UID_PATTERN, defaultAudienceFor, isAgentUid } from "../lib/notify-recipient.js";
|
|
46
|
+
import { callerIsMachinePrincipal } from "../lib/caller-principal.js";
|
|
46
47
|
import { AGENT_MODEL_PROVIDERS, modelSelectionErrorMessage, readAgentModelCatalog, validateAgentModelSelection, } from "../lib/agents/model-catalog.js";
|
|
47
48
|
import { fetchCompanyPricingPlan, isUsageOnly, usageOnlyBoxSentence, usageOnlyCardRequiredLines, } from "../lib/billing/usage-only.js";
|
|
48
49
|
import { BUNDLED_USAGE_CARD, fetchUsageCard, } from "../lib/billing/usage-card.js";
|
|
@@ -1139,7 +1140,7 @@ export async function sendAgentDm(token, agentUid, message) {
|
|
|
1139
1140
|
token,
|
|
1140
1141
|
path: "/v1/notify/dm",
|
|
1141
1142
|
method: "POST",
|
|
1142
|
-
body: { toPersonUid: agentUid, body: message, audience: defaultAudienceFor("agent") },
|
|
1143
|
+
body: { toPersonUid: agentUid, body: message, audience: defaultAudienceFor("agent", callerIsMachinePrincipal()) },
|
|
1143
1144
|
});
|
|
1144
1145
|
}
|
|
1145
1146
|
/** Read the two-way DM conversation with an agent (most recent `limit`). */
|
package/dist/commands/bot.d.ts
CHANGED
|
@@ -109,6 +109,7 @@ export declare function resolveCreateKind(opts: {
|
|
|
109
109
|
};
|
|
110
110
|
export interface BotCreateOptions {
|
|
111
111
|
runtime?: string;
|
|
112
|
+
hqFleetRuntime?: boolean;
|
|
112
113
|
model?: string;
|
|
113
114
|
effort?: string;
|
|
114
115
|
autoApprove?: boolean;
|
|
@@ -343,6 +344,8 @@ export interface RemoteBotRow {
|
|
|
343
344
|
displayName: string | null;
|
|
344
345
|
/** "assistant" for HQ's setup assistant, else null. */
|
|
345
346
|
role: BotRole;
|
|
347
|
+
/** The owner's person uid exactly as GET /v1/agents/mine sends it; null when the server omits it. */
|
|
348
|
+
ownerUid: string | null;
|
|
346
349
|
}
|
|
347
350
|
export declare function registerBotCommand(program: Command): void;
|
|
348
351
|
//# sourceMappingURL=bot.d.ts.map
|
package/dist/commands/bot.js
CHANGED
|
@@ -52,7 +52,7 @@ import { isAncestorProcess } from "../lib/bot/self-process.js";
|
|
|
52
52
|
import { runtimeDisplayName } from "../lib/bot/runtime-sign-in.js";
|
|
53
53
|
import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
|
|
54
54
|
import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.js";
|
|
55
|
-
import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, } from "../lib/bot/index.js";
|
|
55
|
+
import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, HQ_FLEET_RUNTIME_ENGINE_ID, authenticateFleetLocalBot, bootstrapFleetLocalBot, controlFleetLocalBot, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, removeFleetLocalBot, } from "../lib/bot/index.js";
|
|
56
56
|
function collectRepeated(value, previous) {
|
|
57
57
|
return [...previous, value];
|
|
58
58
|
}
|
|
@@ -114,6 +114,18 @@ function requireBot(name) {
|
|
|
114
114
|
}
|
|
115
115
|
return { name: valid, dir, config };
|
|
116
116
|
}
|
|
117
|
+
function isFleetRuntimeBot(config) {
|
|
118
|
+
return config.engine?.id === HQ_FLEET_RUNTIME_ENGINE_ID;
|
|
119
|
+
}
|
|
120
|
+
function runFleetRuntimeControl(name, config, action, json) {
|
|
121
|
+
const result = controlFleetLocalBot(config.agentUid, action);
|
|
122
|
+
if (json) {
|
|
123
|
+
console.log(JSON.stringify({ name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const summary = typeof result.summary === "string" ? result.summary : `${action} completed`;
|
|
127
|
+
console.log(chalk.green(`${name}: ${summary}`));
|
|
128
|
+
}
|
|
117
129
|
/** Bot-identity token supplier: per-bot creds + per-bot token cache dir. */
|
|
118
130
|
export function botTokenSupplier(dir) {
|
|
119
131
|
process.env.HQ_MACHINE_CREDS_FILE = botCredsPath(dir);
|
|
@@ -605,6 +617,10 @@ async function finishExistingSetupBot(name, dir, config, displayName, opts) {
|
|
|
605
617
|
console.log(chalk.yellow(`${name} did not report running within 15s`) + chalk.dim(` logs: hq bot logs ${name}`));
|
|
606
618
|
}
|
|
607
619
|
async function runBotCreateLocked(name, opts) {
|
|
620
|
+
const useFleetRuntime = opts.hqFleetRuntime === true;
|
|
621
|
+
if (useFleetRuntime && (opts.runtime || opts.model || opts.effort || opts.lean)) {
|
|
622
|
+
throw Object.assign(new Error("--hq-fleet-runtime selects the local HQ runtime. Do not combine it with --runtime, --model, --effort, or --lean."), { expected: true });
|
|
623
|
+
}
|
|
608
624
|
const runtime = opts.runtime ?? "claude";
|
|
609
625
|
if (!isBotRuntimeId(runtime)) {
|
|
610
626
|
throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
|
|
@@ -640,6 +656,11 @@ async function runBotCreateLocked(name, opts) {
|
|
|
640
656
|
// cost an identity).
|
|
641
657
|
const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
|
|
642
658
|
const { kind, companies } = resolveCreateKind(opts, worker);
|
|
659
|
+
if (useFleetRuntime && kind !== "personal") {
|
|
660
|
+
throw Object.assign(new Error("--hq-fleet-runtime creates a personal bot. Drop --kind company and --company."), {
|
|
661
|
+
expected: true,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
643
664
|
const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
|
|
644
665
|
if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
|
|
645
666
|
throw Object.assign(new Error(`The name ${RESERVED_SETUP_BOT_NAME} is reserved for HQ's assistant; create it with --worker ${RESERVED_SETUP_BOT_NAME}.`), { expected: true });
|
|
@@ -654,6 +675,9 @@ async function runBotCreateLocked(name, opts) {
|
|
|
654
675
|
expected: true,
|
|
655
676
|
});
|
|
656
677
|
}
|
|
678
|
+
const fleetInstallation = useFleetRuntime
|
|
679
|
+
? await installPinnedFleetLocalDistribution()
|
|
680
|
+
: null;
|
|
657
681
|
const token = await ownerToken();
|
|
658
682
|
const owner = await resolveCallerPersonUid(token, undefined, { command: "hq bot create" });
|
|
659
683
|
if (!owner)
|
|
@@ -763,6 +787,7 @@ async function runBotCreateLocked(name, opts) {
|
|
|
763
787
|
// instead of creating a new one. The machine secret never travels.
|
|
764
788
|
const localConfig = buildBotLocalConfig({
|
|
765
789
|
runtime,
|
|
790
|
+
...(fleetInstallation ? { engine: fleetInstallation.engine } : {}),
|
|
766
791
|
kind,
|
|
767
792
|
...(kind === "company" ? { companies } : {}),
|
|
768
793
|
...(model ? { model } : {}),
|
|
@@ -829,6 +854,7 @@ async function runBotCreateLocked(name, opts) {
|
|
|
829
854
|
agentUid,
|
|
830
855
|
ownerUid: created.agent.ownerUid ?? owner,
|
|
831
856
|
runtime,
|
|
857
|
+
...(fleetInstallation ? { engine: fleetInstallation.engine } : {}),
|
|
832
858
|
kind,
|
|
833
859
|
// A company bot's list is filled in below, with the invites that went through.
|
|
834
860
|
...(kind === "company" ? { companies: [] } : {}),
|
|
@@ -885,8 +911,20 @@ async function runBotCreateLocked(name, opts) {
|
|
|
885
911
|
const scaffoldOpts = { memoryDir: config.memoryDir };
|
|
886
912
|
const scaffold = worker
|
|
887
913
|
? scaffoldBotMemory(hqRoot, name, undefined, scaffoldOpts)
|
|
888
|
-
: scaffoldBotWorker(hqRoot, name, agentUid, runtime, undefined, scaffoldOpts);
|
|
889
|
-
|
|
914
|
+
: scaffoldBotWorker(hqRoot, name, agentUid, fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime, undefined, scaffoldOpts);
|
|
915
|
+
const fleetBootstrap = fleetInstallation
|
|
916
|
+
? bootstrapFleetLocalBot({
|
|
917
|
+
agentUid,
|
|
918
|
+
ownerUid: config.ownerUid,
|
|
919
|
+
hqRoot,
|
|
920
|
+
botDir: dir,
|
|
921
|
+
installation: fleetInstallation,
|
|
922
|
+
machineCredentials: botCredsPath(dir),
|
|
923
|
+
}, { start: opts.start !== false })
|
|
924
|
+
: null;
|
|
925
|
+
const fleetProfile = fleetBootstrap?.profile ?? null;
|
|
926
|
+
const runtimeLabel = fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime;
|
|
927
|
+
createBotLogger(dir)("info", `created ${name} (${agentUid}) kind=${kind}${companies.length ? ` companies=${companies.join(",")}` : ""} runtime=${runtimeLabel}${worker ? ` worker=${worker.id} company=${worker.company ?? "-"}` : ""} memory=${memoryMode}`);
|
|
890
928
|
const created_ = {
|
|
891
929
|
outcome: "created",
|
|
892
930
|
role: localBotRole(config),
|
|
@@ -895,7 +933,9 @@ async function runBotCreateLocked(name, opts) {
|
|
|
895
933
|
agentUid,
|
|
896
934
|
kind,
|
|
897
935
|
companies,
|
|
898
|
-
runtime,
|
|
936
|
+
runtime: runtimeLabel,
|
|
937
|
+
engine: fleetInstallation?.engine ?? null,
|
|
938
|
+
profile: fleetProfile,
|
|
899
939
|
dir,
|
|
900
940
|
worker: worker ? path.join(hqRoot, worker.path) : scaffold.dir,
|
|
901
941
|
workerId: worker?.id ?? null,
|
|
@@ -906,15 +946,15 @@ async function runBotCreateLocked(name, opts) {
|
|
|
906
946
|
intro: intro ?? null,
|
|
907
947
|
kickoff: kickoff ?? null,
|
|
908
948
|
model: model ?? null,
|
|
909
|
-
effort: effort ?? DEFAULT_BOT_EFFORT,
|
|
949
|
+
effort: fleetInstallation ? null : effort ?? DEFAULT_BOT_EFFORT,
|
|
910
950
|
};
|
|
911
951
|
let daemonMessage = null;
|
|
912
|
-
if (opts.daemon !== false && process.platform === "darwin") {
|
|
952
|
+
if (!fleetInstallation && opts.daemon !== false && process.platform === "darwin") {
|
|
913
953
|
const r = installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot }));
|
|
914
954
|
daemonMessage = r.message;
|
|
915
955
|
}
|
|
916
956
|
if (!opts.json) {
|
|
917
|
-
console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${agentUid}, ${
|
|
957
|
+
console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${agentUid}, ${runtimeLabel})`));
|
|
918
958
|
console.log(chalk.dim(` kind: ${describeKind(kind, companies)}`));
|
|
919
959
|
if (worker)
|
|
920
960
|
console.log(chalk.dim(` worker: ${worker.id}${worker.company ? ` (${worker.company})` : ""} — ${path.join(hqRoot, worker.path)}`));
|
|
@@ -922,9 +962,17 @@ async function runBotCreateLocked(name, opts) {
|
|
|
922
962
|
console.log(chalk.dim(` worker: ${scaffold.dir}`));
|
|
923
963
|
console.log(chalk.dim(` memory: ${scaffold.memoryDir} (${memoryMode})`));
|
|
924
964
|
console.log(chalk.dim(` state: ${dir}`));
|
|
965
|
+
if (fleetProfile)
|
|
966
|
+
console.log(chalk.dim(` profile: ${fleetProfile}`));
|
|
925
967
|
if (daemonMessage)
|
|
926
968
|
console.log(chalk.dim(` ${daemonMessage}`));
|
|
927
969
|
}
|
|
970
|
+
if (fleetInstallation) {
|
|
971
|
+
if (opts.json) {
|
|
972
|
+
console.log(JSON.stringify({ ok: true, name, ...created_, daemon: null, running: fleetBootstrap?.running ?? false }, null, 2));
|
|
973
|
+
}
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
928
976
|
if (opts.start !== false) {
|
|
929
977
|
if (daemonMessage && /loaded\)/.test(daemonMessage)) {
|
|
930
978
|
// RunAtLoad already started it.
|
|
@@ -1127,7 +1175,11 @@ async function runBotList(opts) {
|
|
|
1127
1175
|
}
|
|
1128
1176
|
}
|
|
1129
1177
|
async function runBotStatus(nameArg, opts) {
|
|
1130
|
-
const { name, dir } = requireBot(nameArg);
|
|
1178
|
+
const { name, dir, config } = requireBot(nameArg);
|
|
1179
|
+
if (isFleetRuntimeBot(config)) {
|
|
1180
|
+
runFleetRuntimeControl(name, config, "status", opts.json === true);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1131
1183
|
const row = rowFor(name);
|
|
1132
1184
|
await decorateOnline([row]);
|
|
1133
1185
|
const status = readBotStatus(dir);
|
|
@@ -1326,6 +1378,15 @@ async function runBotRmOrphan(name, opts) {
|
|
|
1326
1378
|
else
|
|
1327
1379
|
console.log(chalk.dim(` HQ may still have a bot named "${name}". Remove that too with: hq bot rm ${name} --remote`));
|
|
1328
1380
|
}
|
|
1381
|
+
function stopFleetRuntimeBotQuietly(agentUid) {
|
|
1382
|
+
try {
|
|
1383
|
+
controlFleetLocalBot(agentUid, "stop");
|
|
1384
|
+
return true;
|
|
1385
|
+
}
|
|
1386
|
+
catch {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1329
1390
|
async function runBotRm(nameArg, opts) {
|
|
1330
1391
|
const validated = validateBotName(nameArg);
|
|
1331
1392
|
// Under the bot's lock: an rm racing a create, adopt or restore of the same
|
|
@@ -1348,9 +1409,13 @@ async function runBotRmLocked(validated, opts) {
|
|
|
1348
1409
|
return;
|
|
1349
1410
|
}
|
|
1350
1411
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1412
|
+
if (isFleetRuntimeBot(config))
|
|
1413
|
+
stopFleetRuntimeBotQuietly(config.agentUid);
|
|
1414
|
+
else
|
|
1415
|
+
await stopBot(name, dir, config, "silent").catch(() => { });
|
|
1416
|
+
const daemon = isFleetRuntimeBot(config)
|
|
1417
|
+
? { message: "managed by HQ Fleet Runtime" }
|
|
1418
|
+
: uninstallBotDaemon(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }), daemonDeps());
|
|
1354
1419
|
const token = await ownerToken();
|
|
1355
1420
|
const api = new BotApi({ token: async () => token });
|
|
1356
1421
|
let identity;
|
|
@@ -1380,8 +1445,11 @@ async function runBotRmLocked(validated, opts) {
|
|
|
1380
1445
|
// re-created under the same name. Everything else keeps the folder, so the
|
|
1381
1446
|
// same command can pick the job back up.
|
|
1382
1447
|
const localStateRemoved = terminal || incomplete.retryable === false;
|
|
1383
|
-
if (localStateRemoved)
|
|
1448
|
+
if (localStateRemoved) {
|
|
1449
|
+
if (isFleetRuntimeBot(config))
|
|
1450
|
+
removeFleetLocalBot(config.agentUid);
|
|
1384
1451
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
1452
|
+
}
|
|
1385
1453
|
const message = incomplete
|
|
1386
1454
|
? `HQ removed what it could of "${name}", but ${incomplete.detail}.` +
|
|
1387
1455
|
(incomplete.retryable
|
|
@@ -1572,6 +1640,10 @@ export function botExists(name) {
|
|
|
1572
1640
|
/** `hq bot restart <name>`: stop, then start (launchd when installed). */
|
|
1573
1641
|
export async function runBotRestart(nameArg, opts = {}) {
|
|
1574
1642
|
const b = requireBot(nameArg);
|
|
1643
|
+
if (isFleetRuntimeBot(b.config)) {
|
|
1644
|
+
runFleetRuntimeControl(b.name, b.config, "restart", opts.json === true || opts.output === "json");
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1575
1647
|
const running = readBotStatus(b.dir);
|
|
1576
1648
|
if (running && isPidAlive(running.pid) && selfStopProbe(running.pid)) {
|
|
1577
1649
|
// The stop half would end this turn (and this command) before the
|
|
@@ -1581,6 +1653,30 @@ export async function runBotRestart(nameArg, opts = {}) {
|
|
|
1581
1653
|
await stopBot(b.name, b.dir, b.config, "silent");
|
|
1582
1654
|
await startBot(b.name, b.dir, b.config, opts.output ?? (opts.json ? "json" : "text"));
|
|
1583
1655
|
}
|
|
1656
|
+
async function runBotAuth(nameArg, opts) {
|
|
1657
|
+
const b = requireBot(nameArg);
|
|
1658
|
+
if (!isFleetRuntimeBot(b.config)) {
|
|
1659
|
+
throw Object.assign(new Error(`hq bot auth is for bots created with --hq-fleet-runtime. ${b.name} uses ${b.config.runtime}.`), { expected: true });
|
|
1660
|
+
}
|
|
1661
|
+
const apiKey = opts.apiKey ? fs.readFileSync(0, "utf8").trim() : undefined;
|
|
1662
|
+
if (opts.apiKey && !apiKey) {
|
|
1663
|
+
throw Object.assign(new Error("No API key was received on stdin."), { expected: true });
|
|
1664
|
+
}
|
|
1665
|
+
const result = authenticateFleetLocalBot({
|
|
1666
|
+
agentUid: b.config.agentUid,
|
|
1667
|
+
provider: opts.provider,
|
|
1668
|
+
...(opts.model ? { model: opts.model } : {}),
|
|
1669
|
+
...(apiKey ? { apiKey } : {}),
|
|
1670
|
+
...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
|
|
1671
|
+
...(opts.transport ? { transport: opts.transport } : {}),
|
|
1672
|
+
openBrowser: opts.browser !== false,
|
|
1673
|
+
});
|
|
1674
|
+
if (opts.json) {
|
|
1675
|
+
console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
console.log(chalk.green(`${b.name} is signed in to ${result.provider} and running ${result.model}.`));
|
|
1679
|
+
}
|
|
1584
1680
|
// ── adopt / restore (bring a bot you own back to this computer) ──────────────
|
|
1585
1681
|
/**
|
|
1586
1682
|
* Which computer a re-issue is for. Never a secret and never an identifier the
|
|
@@ -2090,6 +2186,7 @@ async function runBotListRemote(opts) {
|
|
|
2090
2186
|
reason: runnable.reason,
|
|
2091
2187
|
displayName: remoteBotDisplayName(bot, name),
|
|
2092
2188
|
role: remoteBotRole(bot),
|
|
2189
|
+
ownerUid: bot.ownerUid ?? null,
|
|
2093
2190
|
};
|
|
2094
2191
|
});
|
|
2095
2192
|
if (opts.json) {
|
|
@@ -2124,7 +2221,13 @@ export function registerBotCommand(program) {
|
|
|
2124
2221
|
const { dir, config } = requireBot(name);
|
|
2125
2222
|
const token = await ownerToken(false);
|
|
2126
2223
|
const api = new BotApi({ token: async () => token });
|
|
2127
|
-
const result = await promoteLocalBot({
|
|
2224
|
+
const result = await promoteLocalBot({
|
|
2225
|
+
dir,
|
|
2226
|
+
config,
|
|
2227
|
+
companyUid: opts.company,
|
|
2228
|
+
api,
|
|
2229
|
+
...(isFleetRuntimeBot(config) ? { runtime: fleetLocalPromotionRuntime(config.agentUid) } : {}),
|
|
2230
|
+
});
|
|
2128
2231
|
const details = result.pending ? await api.promotionDetails(config.agentUid, opts.company) : null;
|
|
2129
2232
|
if (opts.json)
|
|
2130
2233
|
console.log(JSON.stringify({ ...result, pairing: details?.pairing ?? null, ...(details?.failure ? { failure: details.failure } : {}) }));
|
|
@@ -2140,7 +2243,8 @@ export function registerBotCommand(program) {
|
|
|
2140
2243
|
.description("Provision a local bot identity, scaffold its worker folder, install its launchd agent, and start it")
|
|
2141
2244
|
.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`)
|
|
2142
2245
|
.option("--company <slug>", "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation", collectRepeated, [])
|
|
2143
|
-
.option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}
|
|
2246
|
+
.option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`)
|
|
2247
|
+
.option("--hq-fleet-runtime", "Create this personal bot with the local HQ Fleet Runtime")
|
|
2144
2248
|
.option("--model <model>", "Model override passed to the runtime CLI")
|
|
2145
2249
|
.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})`)
|
|
2146
2250
|
.option("--no-auto-approve", "Keep the runtime's permission gates (default: everything pre-approved; a headless bot cannot answer prompts)")
|
|
@@ -2196,6 +2300,10 @@ export function registerBotCommand(program) {
|
|
|
2196
2300
|
.option("--json", "Print machine-readable JSON")
|
|
2197
2301
|
.action((name, opts) => wrap(async () => {
|
|
2198
2302
|
const b = requireBot(name);
|
|
2303
|
+
if (isFleetRuntimeBot(b.config)) {
|
|
2304
|
+
runFleetRuntimeControl(b.name, b.config, "start", opts.json === true);
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2199
2307
|
await startBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
|
|
2200
2308
|
})());
|
|
2201
2309
|
bot
|
|
@@ -2204,6 +2312,10 @@ export function registerBotCommand(program) {
|
|
|
2204
2312
|
.option("--json", "Print machine-readable JSON")
|
|
2205
2313
|
.action((name, opts) => wrap(async () => {
|
|
2206
2314
|
const b = requireBot(name);
|
|
2315
|
+
if (isFleetRuntimeBot(b.config)) {
|
|
2316
|
+
runFleetRuntimeControl(b.name, b.config, "stop", opts.json === true);
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2207
2319
|
await stopBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
|
|
2208
2320
|
})());
|
|
2209
2321
|
bot
|
|
@@ -2216,6 +2328,17 @@ export function registerBotCommand(program) {
|
|
|
2216
2328
|
.description("Show the bot's process, heartbeat, launchd, and credential state")
|
|
2217
2329
|
.option("--json", "Print machine-readable JSON")
|
|
2218
2330
|
.action((name, opts) => wrap(() => runBotStatus(name, opts))());
|
|
2331
|
+
bot
|
|
2332
|
+
.command("auth <name>")
|
|
2333
|
+
.description("Sign a runtime-backed personal bot in to ChatGPT, Grok, Claude, or another supported provider")
|
|
2334
|
+
.requiredOption("--provider <provider>", "Provider: chatgpt, grok, claude, or a runtime provider id")
|
|
2335
|
+
.option("--model <model>", "Model id; required for providers without a built-in personal-bot default")
|
|
2336
|
+
.option("--api-key", "Read an API key from stdin instead of starting device authorization")
|
|
2337
|
+
.option("--base-url <url>", "Provider API base URL; required for custom runtime providers")
|
|
2338
|
+
.option("--transport <transport>", "Provider transport: chat_completions, anthropic_messages, or codex_responses")
|
|
2339
|
+
.option("--no-browser", "Do not open a browser during device authorization")
|
|
2340
|
+
.option("--json", "Print machine-readable JSON")
|
|
2341
|
+
.action((name, opts) => wrap(() => runBotAuth(name, opts))());
|
|
2219
2342
|
bot
|
|
2220
2343
|
.command("logs <name>")
|
|
2221
2344
|
.description("Show the bot's log")
|