@indigoai-us/hq-cli 5.345.178 → 5.345.180
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 +39 -0
- package/dist/command-catalog.generated.d.ts +197 -1
- package/dist/command-catalog.generated.js +241 -1
- package/dist/commands/bot-jobs.d.ts +11 -9
- package/dist/commands/bot-jobs.js +56 -21
- package/dist/commands/bot.d.ts +37 -1
- package/dist/commands/bot.js +289 -13
- package/dist/commands/channels.d.ts +18 -1
- package/dist/commands/channels.js +185 -1
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +15 -4
- package/dist/commands/integrations-core.d.ts +2 -0
- package/dist/commands/integrations-core.js +1 -0
- package/dist/commands/lanes.d.ts +1 -1
- package/dist/commands/lanes.js +6 -2
- package/dist/commands/mesh.js +19 -0
- package/dist/commands/sources.d.ts +89 -0
- package/dist/commands/sources.js +363 -1
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2285 -1743
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/config.d.ts +8 -0
- package/dist/lib/bot/fleet-local.d.ts +1 -1
- package/dist/lib/bot/fleet-local.js +82 -30
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +109 -0
- package/dist/lib/bot/jobs/hot-signal-sync.js +177 -0
- package/dist/lib/bot/local-jobs.d.ts +40 -4
- package/dist/lib/bot/local-jobs.js +72 -5
- package/dist/lib/bot/run.d.ts +15 -0
- package/dist/lib/bot/run.js +99 -1
- package/dist/lib/bot/runtime-auth.d.ts +119 -0
- package/dist/lib/bot/runtime-auth.js +251 -0
- package/dist/lib/daemon/catalog.d.ts +1 -0
- package/dist/lib/daemon/catalog.js +1 -1
- package/dist/lib/daemon/handoff.d.ts +4 -0
- package/dist/lib/daemon/handoff.js +6 -2
- package/dist/lib/daemon/install.d.ts +70 -2
- package/dist/lib/daemon/install.js +185 -27
- package/dist/lib/daemon/legacy.d.ts +9 -3
- package/dist/lib/daemon/legacy.js +11 -4
- package/dist/lib/daemon/migrate.js +1 -1
- package/dist/lib/daemon/run.d.ts +5 -2
- package/dist/lib/daemon/run.js +74 -22
- package/dist/lib/doctor/checks/anywhere.d.ts +6 -0
- package/dist/lib/doctor/checks/anywhere.js +35 -6
- 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/hot-channels/adapters/__fixtures__/dummy.d.ts +33 -0
- package/dist/lib/hot-channels/adapters/__fixtures__/dummy.js +53 -0
- package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.d.ts +38 -0
- package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.js +64 -0
- package/dist/lib/hot-channels/adapters/attio.d.ts +67 -0
- package/dist/lib/hot-channels/adapters/attio.js +374 -0
- package/dist/lib/hot-channels/adapters/gmail.d.ts +111 -0
- package/dist/lib/hot-channels/adapters/gmail.js +413 -0
- package/dist/lib/hot-channels/adapters/index.d.ts +23 -0
- package/dist/lib/hot-channels/adapters/index.js +39 -0
- package/dist/lib/hot-channels/adapters/linear.d.ts +79 -0
- package/dist/lib/hot-channels/adapters/linear.js +321 -0
- package/dist/lib/hot-channels/adapters/meeting-providers.d.ts +86 -0
- package/dist/lib/hot-channels/adapters/meeting-providers.js +253 -0
- package/dist/lib/hot-channels/adapters/meetings.d.ts +81 -0
- package/dist/lib/hot-channels/adapters/meetings.js +525 -0
- package/dist/lib/hot-channels/adapters/slack.d.ts +70 -0
- package/dist/lib/hot-channels/adapters/slack.js +277 -0
- package/dist/lib/hot-channels/alert.d.ts +78 -0
- package/dist/lib/hot-channels/alert.js +179 -0
- package/dist/lib/hot-channels/audience.d.ts +15 -0
- package/dist/lib/hot-channels/audience.js +25 -0
- package/dist/lib/hot-channels/contract.d.ts +146 -0
- package/dist/lib/hot-channels/contract.js +20 -0
- package/dist/lib/hot-channels/cursors.d.ts +41 -0
- package/dist/lib/hot-channels/cursors.js +84 -0
- package/dist/lib/hot-channels/index.d.ts +10 -0
- package/dist/lib/hot-channels/index.js +10 -0
- package/dist/lib/hot-channels/landing.d.ts +83 -0
- package/dist/lib/hot-channels/landing.js +175 -0
- package/dist/lib/hot-channels/ledger.d.ts +101 -0
- package/dist/lib/hot-channels/ledger.js +203 -0
- package/dist/lib/hot-channels/loop.d.ts +74 -0
- package/dist/lib/hot-channels/loop.js +221 -0
- package/dist/lib/hot-channels/prompts/signals-pass.md +22 -0
- package/dist/lib/hot-channels/registry.d.ts +28 -0
- package/dist/lib/hot-channels/registry.js +121 -0
- package/dist/lib/hot-channels/run.d.ts +137 -0
- package/dist/lib/hot-channels/run.js +324 -0
- package/dist/lib/hot-channels/signals-pass.d.ts +150 -0
- package/dist/lib/hot-channels/signals-pass.js +581 -0
- package/dist/lib/hot-channels/sync-lock.d.ts +51 -0
- package/dist/lib/hot-channels/sync-lock.js +224 -0
- package/dist/lib/install/hook-target.d.ts +6 -2
- package/dist/lib/install/hook-target.js +9 -4
- package/dist/lib/lanes/accounts.d.ts +1 -17
- package/dist/lib/lanes/accounts.js +5 -45
- package/dist/lib/lanes/loop.d.ts +10 -0
- package/dist/lib/lanes/loop.js +18 -1
- package/dist/lib/lanes/provider-home.d.ts +24 -0
- package/dist/lib/lanes/provider-home.js +52 -0
- package/dist/lib/lanes/spawn.d.ts +12 -0
- package/dist/lib/lanes/spawn.js +27 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/doctor.js +13 -0
- package/dist/lib/mesh/live/daemon/owner-inbox-push.d.ts +94 -0
- package/dist/lib/mesh/live/daemon/owner-inbox-push.js +273 -0
- package/dist/lib/mesh/live/daemon/run.js +31 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +14 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.js +210 -3
- package/dist/lib/mesh/live/paths.d.ts +2 -0
- package/dist/lib/mesh/live/paths.js +2 -0
- package/dist/lib/sync-core/flag.d.ts +1 -1
- package/dist/lib/sync-core/flag.js +1 -1
- package/dist/lib/sync-core/reconcile.d.ts +1 -1
- package/dist/lib/sync-core/reconcile.js +9 -7
- package/dist/lib/sync-core/release.d.ts +3 -3
- package/dist/lib/sync-core/release.js +4 -4
- package/dist/lib/sync-core/rollout-state.d.ts +1 -0
- package/dist/lib/sync-core/rollout-state.js +1 -0
- package/package.json +8 -8
|
@@ -5,14 +5,13 @@
|
|
|
5
5
|
* hq bot jobs <botName> --add "<what to do>" --every "<plain english>"
|
|
6
6
|
* hq bot jobs <botName> --add "<what to do>" --cron "0 9 * * 1-5"
|
|
7
7
|
* hq bot jobs <botName> --rm <jobId>
|
|
8
|
+
* hq bot jobs <botName> --reschedule <jobId> --every "<plain english>"
|
|
8
9
|
*
|
|
9
|
-
* The schedule lives
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* a
|
|
13
|
-
*
|
|
14
|
-
* The routes are self-only per agent, so every call uses the BOT's own machine
|
|
15
|
-
* identity — the same credential path its turns use — not the owner's session.
|
|
10
|
+
* The schedule lives with the bot: its jobs sit in the bot's own folder
|
|
11
|
+
* (lib/bot/local-jobs.ts) and the bot's inbox loop fires them while it runs
|
|
12
|
+
* (see lib/bot/job-run.ts). Nothing here writes a crontab, a launchd plist, or
|
|
13
|
+
* a systemd unit — by rule. Schedules are still written in the EventBridge form
|
|
14
|
+
* and held to the same limits, so a job reads the same wherever it runs.
|
|
16
15
|
*/
|
|
17
16
|
import chalk from "chalk";
|
|
18
17
|
import { localJobsClient } from "../lib/bot/local-jobs.js";
|
|
@@ -365,12 +364,56 @@ export async function runBotJobs(nameArg, opts, deps) {
|
|
|
365
364
|
client = deps.apiFor ? deps.apiFor(dir) : localJobsClient(dir);
|
|
366
365
|
return client;
|
|
367
366
|
};
|
|
368
|
-
if (opts.add
|
|
369
|
-
refuse("Add
|
|
367
|
+
if ([opts.add, opts.rm, opts.reschedule].filter((value) => value !== undefined).length > 1) {
|
|
368
|
+
refuse("Add, remove, or reschedule one job at a time, not several in the same command.");
|
|
370
369
|
}
|
|
371
370
|
if (opts.quiet && opts.add === undefined) {
|
|
372
371
|
refuse("Use --quiet with --add to set a scheduled job's delivery option.");
|
|
373
372
|
}
|
|
373
|
+
// Shared by --add and --reschedule: one schedule from --every or --cron, in
|
|
374
|
+
// the stored form, held to the same floor.
|
|
375
|
+
const scheduleFromOptions = () => {
|
|
376
|
+
if (!opts.every && !opts.cron) {
|
|
377
|
+
refuse(`Say how often it should run: --every "every weekday at 9am", or --cron "0 9 * * 1-5".`);
|
|
378
|
+
}
|
|
379
|
+
if (opts.every && opts.cron)
|
|
380
|
+
refuse("Use --every or --cron, not both.");
|
|
381
|
+
const timezone = opts.tz?.trim() || (deps.timezone ?? machineTimezone)();
|
|
382
|
+
// An unknown zone would throw RangeError on every inbox poll; refuse it now.
|
|
383
|
+
try {
|
|
384
|
+
new Intl.DateTimeFormat("en-US", { timeZone: timezone });
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
refuse(`"${timezone}" is not a known IANA time zone (for example America/New_York or Europe/London).`);
|
|
388
|
+
}
|
|
389
|
+
const parse = deps.parseSchedule ?? parseScheduleText;
|
|
390
|
+
return opts.every ? parse(opts.every, { timezone }) : { cron: opts.cron, timezone };
|
|
391
|
+
};
|
|
392
|
+
if (opts.reschedule !== undefined) {
|
|
393
|
+
const jobId = opts.reschedule.trim();
|
|
394
|
+
if (!jobId)
|
|
395
|
+
refuse("Say which job to reschedule: --reschedule <jobId>");
|
|
396
|
+
const schedule = scheduleFromOptions();
|
|
397
|
+
const cron = toEventBridgeCron(schedule.cron);
|
|
398
|
+
assertScheduleWithinFloor(cron);
|
|
399
|
+
const api = await jobsApi();
|
|
400
|
+
if (!api.rescheduleJob)
|
|
401
|
+
refuse(`${name}'s jobs cannot be rescheduled in place. Remove the job and add it again.`);
|
|
402
|
+
let updated;
|
|
403
|
+
try {
|
|
404
|
+
updated = await api.rescheduleJob(config.agentUid, jobId, { cron, timezone: schedule.timezone });
|
|
405
|
+
}
|
|
406
|
+
catch (err) {
|
|
407
|
+
refuse(jobsErrorMessage(err, ctx));
|
|
408
|
+
}
|
|
409
|
+
if (opts.json) {
|
|
410
|
+
out(JSON.stringify({ ok: true, job: updated }, null, 2));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const words = schedule.human || describeSchedule(updated);
|
|
414
|
+
out(chalk.green(`Rescheduled ${updated.jobId}: ${name} now runs it ${words}.`));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
374
417
|
if (opts.rm !== undefined) {
|
|
375
418
|
const jobId = opts.rm.trim();
|
|
376
419
|
if (!jobId)
|
|
@@ -389,16 +432,7 @@ export async function runBotJobs(nameArg, opts, deps) {
|
|
|
389
432
|
if (opts.quiet && !(await quietOptionEnabled())) {
|
|
390
433
|
refuse(`--quiet requires the ${BOT_JOB_QUIET_OPTION_FLAG} flag, which is currently disabled.`);
|
|
391
434
|
}
|
|
392
|
-
|
|
393
|
-
refuse(`Say how often it should run: --every "every weekday at 9am", or --cron "0 9 * * 1-5".`);
|
|
394
|
-
}
|
|
395
|
-
if (opts.every && opts.cron)
|
|
396
|
-
refuse("Use --every or --cron, not both.");
|
|
397
|
-
const timezone = opts.tz?.trim() || (deps.timezone ?? machineTimezone)();
|
|
398
|
-
const parse = deps.parseSchedule ?? parseScheduleText;
|
|
399
|
-
const schedule = opts.every
|
|
400
|
-
? parse(opts.every, { timezone })
|
|
401
|
-
: { cron: opts.cron, timezone };
|
|
435
|
+
const schedule = scheduleFromOptions();
|
|
402
436
|
const body = buildCreateJobBody({ prompt: opts.add, cron: schedule.cron, timezone: schedule.timezone, quiet: opts.quiet });
|
|
403
437
|
const api = await jobsApi();
|
|
404
438
|
let created;
|
|
@@ -412,7 +446,7 @@ export async function runBotJobs(nameArg, opts, deps) {
|
|
|
412
446
|
out(JSON.stringify({ ok: true, job: created }, null, 2));
|
|
413
447
|
return;
|
|
414
448
|
}
|
|
415
|
-
const words =
|
|
449
|
+
const words = schedule.human || describeSchedule(created);
|
|
416
450
|
out(chalk.green(`Scheduled ${created.jobId}: ${name} will ${body.prompt.replace(/\s+/g, " ").slice(0, 120)} — ${words}.`));
|
|
417
451
|
out(chalk.gray(`It runs on this computer while ${name} is running. If a run is due while the computer is asleep or ` +
|
|
418
452
|
`${name} is stopped, it runs once, as a catch-up, the next time ${name} is running.`));
|
|
@@ -432,13 +466,14 @@ export async function runBotJobs(nameArg, opts, deps) {
|
|
|
432
466
|
export function registerBotJobsCommand(bot, deps) {
|
|
433
467
|
bot
|
|
434
468
|
.command("jobs <name>")
|
|
435
|
-
.description("Show, schedule, or remove a bot's recurring jobs (
|
|
469
|
+
.description("Show, schedule, reschedule, or remove a bot's recurring jobs (they run on this computer while the bot runs)")
|
|
436
470
|
.option("--add <what>", "Schedule new work, in plain language")
|
|
437
471
|
.option("--every <when>", 'How often, in plain language (e.g. "every weekday at 9am")')
|
|
438
472
|
.option("--cron <expr>", 'How often, as a cron expression (e.g. "0 9 * * 1-5")')
|
|
439
473
|
.option("--tz <zone>", "IANA timezone for the schedule (default: this computer's)")
|
|
440
474
|
.option("--quiet", "Skip owner DMs after successful runs (requires rollout flag)")
|
|
441
475
|
.option("--rm <jobId>", "Remove a scheduled job")
|
|
476
|
+
.option("--reschedule <jobId>", "Change a job's schedule (with --every or --cron)")
|
|
442
477
|
.option("--json", "Print machine-readable JSON")
|
|
443
478
|
.action(async (name, opts) => {
|
|
444
479
|
try {
|
package/dist/commands/bot.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import { type RuntimeSignInCheck, type RuntimeSignInIssue } from "../lib/bot/runtime-sign-in.js";
|
|
3
3
|
import type { BotStatusFile } from "../lib/bot/status.js";
|
|
4
4
|
import { probeBotRuntime, type RuntimeProbeResult } from "../lib/bot/probe.js";
|
|
5
|
+
import { type SpawnInteractive } from "../lib/bot/runtime-auth.js";
|
|
5
6
|
import { type BotLocalConfig, type MyLocalBot, type BotKind, type BotMemoryMode, type BotNotRunnableReason, type RuntimeCrash, type BotConfig, type BotRuntimeId, type LaunchctlExec, type BotRole } from "../lib/bot/index.js";
|
|
6
7
|
/** Real launchctl; every failure is a non-ok result, never a throw. */
|
|
7
8
|
export declare const defaultLaunchctl: LaunchctlExec;
|
|
@@ -193,12 +194,39 @@ export declare function runBotWorkers(opts: {
|
|
|
193
194
|
* from HQ changes nothing and fails.
|
|
194
195
|
*/
|
|
195
196
|
export interface BotSetOptions {
|
|
197
|
+
runtime?: string;
|
|
198
|
+
/** A capacity account id for the bot's runtime, or "default" for the owner's own sign-in. */
|
|
199
|
+
account?: string;
|
|
196
200
|
model?: string;
|
|
197
201
|
effort?: string;
|
|
198
202
|
displayName?: string;
|
|
199
203
|
json?: boolean;
|
|
200
204
|
}
|
|
201
|
-
export
|
|
205
|
+
export interface BotSetDeps {
|
|
206
|
+
probe?: typeof probeBotRuntime;
|
|
207
|
+
/** Test seam: stop and start the bot process. */
|
|
208
|
+
lifecycle?: {
|
|
209
|
+
isRunning: (dir: string) => boolean;
|
|
210
|
+
stop: (name: string, dir: string, config: BotConfig) => Promise<boolean>;
|
|
211
|
+
start: (name: string, dir: string, config: BotConfig) => Promise<boolean>;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export declare function runBotSet(nameArg: string, opts: BotSetOptions, deps?: BotSetDeps): Promise<void>;
|
|
215
|
+
/** `hq bot login <name>`: sign the bot in with a login of its own. */
|
|
216
|
+
export declare function runBotLogin(nameArg: string, opts: {
|
|
217
|
+
runtime?: string;
|
|
218
|
+
json?: boolean;
|
|
219
|
+
}, deps?: {
|
|
220
|
+
spawn?: SpawnInteractive;
|
|
221
|
+
interactive?: boolean;
|
|
222
|
+
}): Promise<void>;
|
|
223
|
+
/** `hq bot logout <name>`: sign the bot's own login out and delete it. */
|
|
224
|
+
export declare function runBotLogout(nameArg: string, opts: {
|
|
225
|
+
runtime?: string;
|
|
226
|
+
json?: boolean;
|
|
227
|
+
}, deps?: {
|
|
228
|
+
spawn?: SpawnInteractive;
|
|
229
|
+
}): Promise<void>;
|
|
202
230
|
/** `hq bot probe`: exit code 0 when the runtime answered, 1 when it did not. */
|
|
203
231
|
export declare function runBotProbe(opts: {
|
|
204
232
|
runtime?: string;
|
|
@@ -379,4 +407,12 @@ export interface RemoteBotRow {
|
|
|
379
407
|
ownerUid: string | null;
|
|
380
408
|
}
|
|
381
409
|
export declare function registerBotCommand(program: Command): void;
|
|
410
|
+
/**
|
|
411
|
+
* The company a bot's hot-signal-sync job syncs: the bot's own company, else
|
|
412
|
+
* the first company it serves. Null = none, and no job is registered. There is
|
|
413
|
+
* no fallback to the HQ's active company: a personal bot syncs a company only
|
|
414
|
+
* when it was given one explicitly (`--company`), so the job never writes into
|
|
415
|
+
* a vault the bot does not belong to.
|
|
416
|
+
*/
|
|
417
|
+
export declare function hotSignalSyncCompany(config: Pick<BotConfig, "companySlug" | "kind" | "companies" | "workerId">): string | null;
|
|
382
418
|
//# sourceMappingURL=bot.d.ts.map
|
package/dist/commands/bot.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { withLocalJobs } from "../lib/bot/local-jobs.js";
|
|
2
|
+
import { currentHotSignalSyncCompany, ensureHotSignalSyncJob, removeStaleHotSignalSyncJobs, runHotSignalSyncJob } from "../lib/bot/jobs/hot-signal-sync.js";
|
|
3
|
+
import { createClaudeSignalsPass } from "../lib/hot-channels/signals-pass.js";
|
|
4
|
+
import { machineTimeZone } from "../lib/bot/job-run.js";
|
|
5
|
+
import { runCompanyHotChannelSync } from "./sources.js";
|
|
2
6
|
import { OwnerContextCache, lookupOwnerContext } from "../lib/bot/owner-context.js";
|
|
3
7
|
import { hasActivePromotion } from "../lib/bot/promotion-receipt.js";
|
|
4
8
|
import { readInflightTurns } from "../lib/bot/inflight.js";
|
|
@@ -52,6 +56,7 @@ import { isAncestorProcess } from "../lib/bot/self-process.js";
|
|
|
52
56
|
import { runtimeDisplayName, signInCommand } from "../lib/bot/runtime-sign-in.js";
|
|
53
57
|
import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
|
|
54
58
|
import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.js";
|
|
59
|
+
import { BOT_LOGIN_RUNTIMES, botAuthEnv, botRuntimeAuth, capacityAccountHome, describeBotAuth, formatBotAuth, isBotLoginRuntime, restoreBotSessions, setAsideBotSessions, signInBotRuntime, signOutBotRuntime, withRuntimeAuth, } from "../lib/bot/runtime-auth.js";
|
|
55
60
|
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, runtimeCrashSinceStart, 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, buildSystemPrompt, controlFleetLocalBot, ensureFleetPython, fleetLocalGatewayPid, fleetProviderForRuntime, importFleetLocalMachineCredential, fleetProviderNeedsSignIn, fleetDefaultCompanies, fleetOwnerCompanies, fleetRuntimeIsDefault, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
|
|
56
61
|
function collectRepeated(value, previous) {
|
|
57
62
|
return [...previous, value];
|
|
@@ -796,6 +801,11 @@ async function runBotCreateLocked(name, opts) {
|
|
|
796
801
|
// cost an identity).
|
|
797
802
|
const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
|
|
798
803
|
const { kind, companies } = resolveCreateKind(opts, worker);
|
|
804
|
+
if (requestedFleetRuntime && kind === "company" && companies.length !== 1) {
|
|
805
|
+
throw Object.assign(new Error("An HQ Fleet company bot must belong to exactly one company."), {
|
|
806
|
+
expected: true,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
799
809
|
// Companies with the fleet-default flag get their personal bots on
|
|
800
810
|
// the HQ Fleet Runtime; --legacy-runtime keeps the model-CLI runner.
|
|
801
811
|
const fleetCandidate = requestedFleetRuntime ||
|
|
@@ -804,12 +814,11 @@ async function runBotCreateLocked(name, opts) {
|
|
|
804
814
|
// and only where the company turned the flag on.
|
|
805
815
|
const flaggedCompanies = fleetCandidate && kind === "personal" ? await fleetDefaultCompanies(hqRoot) : [];
|
|
806
816
|
const useFleetRuntime = requestedFleetRuntime || (fleetCandidate && fleetRuntimeIsDefault(flaggedCompanies));
|
|
807
|
-
const ownerCompanies = useFleetRuntime
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
817
|
+
const ownerCompanies = useFleetRuntime
|
|
818
|
+
? kind === "personal"
|
|
819
|
+
? await fleetOwnerCompanies()
|
|
820
|
+
: companies
|
|
821
|
+
: [];
|
|
813
822
|
const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
|
|
814
823
|
if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
|
|
815
824
|
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 });
|
|
@@ -1274,6 +1283,27 @@ async function runBotResident(nameArg) {
|
|
|
1274
1283
|
const token = botTokenSupplier(dir);
|
|
1275
1284
|
// The bot's own schedule lives in its folder and fires from this loop.
|
|
1276
1285
|
const api = withLocalJobs(new BotApi({ token }), dir);
|
|
1286
|
+
// The built-in hot-signal-sync job, registered once in the bot's own
|
|
1287
|
+
// jobs.json (06:30 daily in this computer's time zone until the owner
|
|
1288
|
+
// reschedules it with `hq bot jobs`). It runs in-process, with no model turn.
|
|
1289
|
+
const syncCompany = hotSignalSyncCompany(config);
|
|
1290
|
+
try {
|
|
1291
|
+
// A job for a company the bot no longer serves is removed before anything runs.
|
|
1292
|
+
for (const stale of removeStaleHotSignalSyncJobs(dir, syncCompany)) {
|
|
1293
|
+
console.log(`Removed the hot-channel sync job ${stale.jobId}: this bot no longer syncs that company.`);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
catch (err) {
|
|
1297
|
+
console.error(`Could not check the hot-channel sync jobs: ${err instanceof Error ? err.message : String(err)}`);
|
|
1298
|
+
}
|
|
1299
|
+
if (syncCompany) {
|
|
1300
|
+
try {
|
|
1301
|
+
ensureHotSignalSyncJob(dir, { company: syncCompany, timezone: machineTimeZone() });
|
|
1302
|
+
}
|
|
1303
|
+
catch (err) {
|
|
1304
|
+
console.error(`Could not register the hot-channel sync job for ${syncCompany}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1277
1307
|
const owner = new OwnerContextCache(() => lookupOwnerContext(config.ownerUid), Date.now, undefined, config.ownerUid);
|
|
1278
1308
|
const handle = await runBot({
|
|
1279
1309
|
dir,
|
|
@@ -1281,6 +1311,15 @@ async function runBotResident(nameArg) {
|
|
|
1281
1311
|
api,
|
|
1282
1312
|
runtime: runtimeFor(config.runtime),
|
|
1283
1313
|
ownerContext: () => owner.get(),
|
|
1314
|
+
hotSignalSync: ({ company, catchUp, now, sendAlert }) => runHotSignalSyncJob({
|
|
1315
|
+
company,
|
|
1316
|
+
catchUp,
|
|
1317
|
+
now,
|
|
1318
|
+
sendAlert,
|
|
1319
|
+
sync: (input) => runCompanyHotChannelSync({ hqRoot: config.hqRoot, ...input }),
|
|
1320
|
+
// A new pass per run: the login preflight and the 20-document cap are per run.
|
|
1321
|
+
signalsPass: createClaudeSignalsPass({ hqRoot: config.hqRoot, company }),
|
|
1322
|
+
}),
|
|
1284
1323
|
// US-005: decision picks in the owner DM, with the bot's own machine token
|
|
1285
1324
|
// (never the owner's login).
|
|
1286
1325
|
decisions: new DecisionsClient(token),
|
|
@@ -1353,14 +1392,16 @@ async function runBotStatus(nameArg, opts) {
|
|
|
1353
1392
|
const status = readBotStatus(dir);
|
|
1354
1393
|
const creds = readBotCredsIdentity(dir);
|
|
1355
1394
|
if (opts.json) {
|
|
1356
|
-
console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine } : {}), credsPresent: fleet || !!creds, status }, null, 2));
|
|
1395
|
+
console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine } : {}), credsPresent: fleet || !!creds, login: describeBotAuth(dir, config), status }, null, 2));
|
|
1357
1396
|
return;
|
|
1358
1397
|
}
|
|
1359
1398
|
console.log(`${chalk.bold(name)} ${row.agentUid}`);
|
|
1360
1399
|
if (fleet)
|
|
1361
1400
|
console.log(` engine: HQ Fleet Runtime ${config.engine?.version ?? ""} ${chalk.dim(`logs ~/.hq-fleet/profiles/${config.agentUid}/logs`)}`);
|
|
1362
1401
|
console.log(` kind: ${describeKind(row.kind, row.companies)}`);
|
|
1402
|
+
const login = describeBotAuth(dir, config);
|
|
1363
1403
|
console.log(` runtime: ${row.runtime} model ${row.model ?? "default"} thinking ${row.effort}${row.effortIsDefault ? " (default)" : ""}`);
|
|
1404
|
+
console.log(` login: ${login.login === "own" && !login.present ? chalk.red(formatBotAuth(login, name)) : formatBotAuth(login, name)}`);
|
|
1364
1405
|
if (row.workerId)
|
|
1365
1406
|
console.log(` worker: ${row.workerId}${row.companySlug ? ` (company ${row.companySlug})` : ""} ${chalk.dim(row.workerDir)}`);
|
|
1366
1407
|
console.log(` memory: ${row.memory} ${chalk.dim(row.memoryDir)}`);
|
|
@@ -1387,7 +1428,13 @@ async function runBotStatus(nameArg, opts) {
|
|
|
1387
1428
|
if (status.runtimeSignIn) {
|
|
1388
1429
|
const tool = runtimeDisplayName(status.runtimeSignIn.runtime);
|
|
1389
1430
|
const what = status.runtimeSignIn.state === "signed-out" ? `${tool} is not signed in for ${name}` : `${tool} needs to sign in again`;
|
|
1390
|
-
|
|
1431
|
+
const which = login.login === "default" ? "" : ` (${formatBotAuth(login, name)})`;
|
|
1432
|
+
const fix = login.login === "own"
|
|
1433
|
+
? `run \`hq bot login ${name} --runtime ${status.runtimeSignIn.runtime}\`, then \`hq bot restart ${name}\``
|
|
1434
|
+
: login.login === "account"
|
|
1435
|
+
? `run \`hq capacity login ${status.runtimeSignIn.runtime} --account ${login.account}\`, then \`hq bot restart ${name}\``
|
|
1436
|
+
: `sign in from HQ, or run \`${signInCommand(status.runtimeSignIn.runtime)}\`; ${name} checks again on its own`;
|
|
1437
|
+
console.log(` ${chalk.yellow(`${what}${which}`)} — ${fix}`);
|
|
1391
1438
|
}
|
|
1392
1439
|
else if (status.runtimeSignInCheck) {
|
|
1393
1440
|
const c = status.runtimeSignInCheck;
|
|
@@ -1667,9 +1714,9 @@ async function runBotRmLocked(validated, opts) {
|
|
|
1667
1714
|
console.log(chalk.dim(` HQ did not finish: ${incomplete.steps.join(", ")}`));
|
|
1668
1715
|
throw Object.assign(new Error(message), { expected: true });
|
|
1669
1716
|
}
|
|
1670
|
-
export async function runBotSet(nameArg, opts) {
|
|
1717
|
+
export async function runBotSet(nameArg, opts, deps = {}) {
|
|
1671
1718
|
const name = validateBotName(nameArg);
|
|
1672
|
-
await withBotLock(name, () => runBotSetLocked(name, opts), { command: "hq bot set" });
|
|
1719
|
+
await withBotLock(name, () => runBotSetLocked(name, opts, deps), { command: "hq bot set" });
|
|
1673
1720
|
}
|
|
1674
1721
|
/** A sentence for a refused name write: the status only, never the server's text. */
|
|
1675
1722
|
function nameWriteRefused(name, err) {
|
|
@@ -1696,12 +1743,48 @@ function nameWriteRefused(name, err) {
|
|
|
1696
1743
|
expected: true,
|
|
1697
1744
|
});
|
|
1698
1745
|
}
|
|
1699
|
-
|
|
1746
|
+
const defaultSetLifecycle = {
|
|
1747
|
+
isRunning: (dir) => {
|
|
1748
|
+
const s = readBotStatus(dir);
|
|
1749
|
+
return !!s && isPidAlive(s.pid) && s.state === "running";
|
|
1750
|
+
},
|
|
1751
|
+
stop: async (name, dir, config) => {
|
|
1752
|
+
await stopBot(name, dir, config, "silent");
|
|
1753
|
+
return waitFor(() => {
|
|
1754
|
+
const s = readBotStatus(dir);
|
|
1755
|
+
return !s || !isPidAlive(s.pid);
|
|
1756
|
+
}, 15_000);
|
|
1757
|
+
},
|
|
1758
|
+
start: async (name, dir, config) => (await startBot(name, dir, config, "silent")).running,
|
|
1759
|
+
};
|
|
1760
|
+
async function runBotSetLocked(nameArg, opts, deps = {}) {
|
|
1700
1761
|
const { name, dir, config } = requireBot(nameArg);
|
|
1701
1762
|
if (hasPromotionHold(dir))
|
|
1702
1763
|
throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
|
|
1703
|
-
if (opts.model === undefined && opts.effort === undefined && opts.displayName === undefined) {
|
|
1704
|
-
throw Object.assign(new Error("Nothing to change: pass --model <id|default>, --effort <level|default> and/or --display-name <name>."), { expected: true });
|
|
1764
|
+
if (opts.runtime === undefined && opts.account === undefined && opts.model === undefined && opts.effort === undefined && opts.displayName === undefined) {
|
|
1765
|
+
throw Object.assign(new Error("Nothing to change: pass --runtime <runtime>, --account <id|default>, --model <id|default>, --effort <level|default> and/or --display-name <name>."), { expected: true });
|
|
1766
|
+
}
|
|
1767
|
+
if (opts.runtime !== undefined && !isBotRuntimeId(opts.runtime)) {
|
|
1768
|
+
throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
|
|
1769
|
+
}
|
|
1770
|
+
const targetRuntime = opts.runtime ?? config.runtime;
|
|
1771
|
+
const switching = targetRuntime !== config.runtime;
|
|
1772
|
+
if ((switching || opts.account !== undefined) && opts.displayName !== undefined) {
|
|
1773
|
+
throw Object.assign(new Error("Change --display-name on its own, not together with --runtime or --account."), { expected: true });
|
|
1774
|
+
}
|
|
1775
|
+
let runtimeAuth = config.runtimeAuth;
|
|
1776
|
+
if (opts.account !== undefined) {
|
|
1777
|
+
const id = opts.account.trim();
|
|
1778
|
+
let auth = null;
|
|
1779
|
+
if (id && id.toLowerCase() !== "default") {
|
|
1780
|
+
// Checked now so a typo never reaches a turn.
|
|
1781
|
+
capacityAccountHome(config.hqRoot, targetRuntime, id);
|
|
1782
|
+
auth = { mode: "account", account: id };
|
|
1783
|
+
}
|
|
1784
|
+
runtimeAuth = withRuntimeAuth(runtimeAuth, targetRuntime, auth);
|
|
1785
|
+
}
|
|
1786
|
+
if (switching || opts.account !== undefined) {
|
|
1787
|
+
return switchRuntimeOrAccount({ name, dir, config, targetRuntime, switching, runtimeAuth, opts, deps });
|
|
1705
1788
|
}
|
|
1706
1789
|
// Only the keys this command changes are written, onto a fresh read of
|
|
1707
1790
|
// bot.json: the running bot writes it too (introSentAt, kickoff state), and
|
|
@@ -1778,6 +1861,174 @@ async function runBotSetLocked(nameArg, opts) {
|
|
|
1778
1861
|
console.log(chalk.green(`${name} is now called ${displayName}`) + chalk.dim(`${where}${how}`));
|
|
1779
1862
|
}
|
|
1780
1863
|
}
|
|
1864
|
+
/**
|
|
1865
|
+
* `hq bot set --runtime/--account`: probe the new setup first (nothing changes
|
|
1866
|
+
* when it cannot answer), stop a running bot, back up bot.json and set the
|
|
1867
|
+
* old runtime's session files aside, save, and start it again. A bot that does
|
|
1868
|
+
* not come back is rolled back to its old settings and sessions.
|
|
1869
|
+
*/
|
|
1870
|
+
async function switchRuntimeOrAccount(input) {
|
|
1871
|
+
const { name, dir, config, targetRuntime, switching, runtimeAuth, opts, deps } = input;
|
|
1872
|
+
const life = deps.lifecycle ?? defaultSetLifecycle;
|
|
1873
|
+
const patch = { runtime: targetRuntime, runtimeAuth };
|
|
1874
|
+
// Model ids and thinking levels belong to one runtime: a switch resets what does not carry over.
|
|
1875
|
+
patch.model = opts.model !== undefined ? validateBotModel(opts.model) || undefined : switching ? undefined : config.model;
|
|
1876
|
+
patch.effort =
|
|
1877
|
+
opts.effort !== undefined
|
|
1878
|
+
? validateBotEffort(targetRuntime, opts.effort) || undefined
|
|
1879
|
+
: config.effort && BOT_EFFORT_LEVELS[targetRuntime].includes(config.effort)
|
|
1880
|
+
? config.effort
|
|
1881
|
+
: undefined;
|
|
1882
|
+
const candidate = { ...config, ...patch };
|
|
1883
|
+
const probeEnv = botAuthEnv(dir, candidate, process.env);
|
|
1884
|
+
const probe = await (deps.probe ?? probeBotRuntime)({
|
|
1885
|
+
runtime: targetRuntime,
|
|
1886
|
+
...(candidate.model ? { model: candidate.model } : {}),
|
|
1887
|
+
...(candidate.effort ? { effort: candidate.effort } : {}),
|
|
1888
|
+
env: probeEnv,
|
|
1889
|
+
});
|
|
1890
|
+
if (!probe.ok) {
|
|
1891
|
+
throw Object.assign(new Error(`${name} stays on ${config.runtime}: ${probe.detail} Nothing was changed.`), { expected: true });
|
|
1892
|
+
}
|
|
1893
|
+
const status = readBotStatus(dir);
|
|
1894
|
+
if (status && isPidAlive(status.pid) && selfStopProbe(status.pid)) {
|
|
1895
|
+
throw Object.assign(new Error(`${name} cannot change its runtime or login from inside one of its own turns. Run this from outside the bot. Nothing was changed.`), { expected: true });
|
|
1896
|
+
}
|
|
1897
|
+
const wasRunning = life.isRunning(dir);
|
|
1898
|
+
if (wasRunning && !(await life.stop(name, dir, config))) {
|
|
1899
|
+
throw Object.assign(new Error(`${name} did not stop, so nothing was changed. Try again, or run hq bot stop ${name} first.`), { expected: true });
|
|
1900
|
+
}
|
|
1901
|
+
let aside = null;
|
|
1902
|
+
// Only the settings this command changed go back, onto a fresh bot.json.
|
|
1903
|
+
const rollBack = () => {
|
|
1904
|
+
if (aside)
|
|
1905
|
+
restoreBotSessions(dir, aside);
|
|
1906
|
+
return patchBotConfig(dir, { runtime: config.runtime, runtimeAuth: config.runtimeAuth, model: config.model, effort: config.effort });
|
|
1907
|
+
};
|
|
1908
|
+
let next;
|
|
1909
|
+
let running = false;
|
|
1910
|
+
let rolledBack = false;
|
|
1911
|
+
try {
|
|
1912
|
+
if (switching)
|
|
1913
|
+
aside = setAsideBotSessions(dir, config.runtime);
|
|
1914
|
+
next = patchBotConfig(dir, patch);
|
|
1915
|
+
if (wasRunning)
|
|
1916
|
+
running = await life.start(name, dir, next);
|
|
1917
|
+
}
|
|
1918
|
+
catch (err) {
|
|
1919
|
+
// Any failure part-way puts the old settings and sessions back, and the bot back up.
|
|
1920
|
+
try {
|
|
1921
|
+
await life.stop(name, dir, config);
|
|
1922
|
+
}
|
|
1923
|
+
catch { /* best effort */ }
|
|
1924
|
+
const old = rollBack();
|
|
1925
|
+
if (wasRunning)
|
|
1926
|
+
await life.start(name, dir, old);
|
|
1927
|
+
throw err;
|
|
1928
|
+
}
|
|
1929
|
+
const login = describeBotAuth(dir, next);
|
|
1930
|
+
createBotLogger(dir)("info", `settings: runtime=${next.runtime} login=${login.login}${login.account ? ` account=${login.account}` : ""} model=${next.model ?? "default"} effort=${effectiveBotEffort(next)}${aside ? ` (sessions set aside in ${aside.archiveDir})` : ""}`);
|
|
1931
|
+
if (wasRunning && !running) {
|
|
1932
|
+
await life.stop(name, dir, next);
|
|
1933
|
+
const old = rollBack();
|
|
1934
|
+
rolledBack = true;
|
|
1935
|
+
running = await life.start(name, dir, old);
|
|
1936
|
+
createBotLogger(dir)("warn", `settings rolled back: ${name} did not start on ${next.runtime}`);
|
|
1937
|
+
}
|
|
1938
|
+
const result = {
|
|
1939
|
+
ok: !rolledBack,
|
|
1940
|
+
name,
|
|
1941
|
+
runtime: rolledBack ? config.runtime : next.runtime,
|
|
1942
|
+
previousRuntime: config.runtime,
|
|
1943
|
+
login: rolledBack ? describeBotAuth(dir, config) : login,
|
|
1944
|
+
model: rolledBack ? config.model ?? null : next.model ?? null,
|
|
1945
|
+
effort: effectiveBotEffort(rolledBack ? config : next),
|
|
1946
|
+
sessionsSetAside: aside && !rolledBack ? { dir: aside.archiveDir, files: aside.moved.length } : null,
|
|
1947
|
+
backup: aside ? path.join(aside.archiveDir, "bot.json") : null,
|
|
1948
|
+
running,
|
|
1949
|
+
restarted: wasRunning,
|
|
1950
|
+
rolledBack,
|
|
1951
|
+
};
|
|
1952
|
+
if (rolledBack)
|
|
1953
|
+
process.exitCode = 1;
|
|
1954
|
+
if (opts.json) {
|
|
1955
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
if (rolledBack) {
|
|
1959
|
+
console.log(chalk.red(`${name} did not start on ${next.runtime}, so it is back on ${config.runtime} with its old sessions. Check: hq bot logs ${name}`));
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
if (switching) {
|
|
1963
|
+
console.log(chalk.green(`${name} now runs on ${next.runtime} (${formatBotAuth(login, name)}), model ${next.model ?? "default"}, thinking ${result.effort}.`));
|
|
1964
|
+
console.log(chalk.dim(` ${aside.moved.length} ${config.runtime} session file(s) set aside in ${aside.archiveDir}; DM history and memory are unchanged.`));
|
|
1965
|
+
}
|
|
1966
|
+
else {
|
|
1967
|
+
console.log(chalk.green(`${name} now uses ${formatBotAuth(login, name)} for ${next.runtime}.`));
|
|
1968
|
+
}
|
|
1969
|
+
if (wasRunning)
|
|
1970
|
+
console.log(chalk.dim(running ? " restarted" : ` did not report running; check: hq bot logs ${name}`));
|
|
1971
|
+
}
|
|
1972
|
+
/** `hq bot login <name>`: sign the bot in with a login of its own. */
|
|
1973
|
+
export async function runBotLogin(nameArg, opts, deps = {}) {
|
|
1974
|
+
const name = validateBotName(nameArg);
|
|
1975
|
+
await withBotLock(name, async () => {
|
|
1976
|
+
const { dir, config } = requireBot(name);
|
|
1977
|
+
const runtime = opts.runtime ?? config.runtime;
|
|
1978
|
+
if (!isBotLoginRuntime(runtime)) {
|
|
1979
|
+
throw Object.assign(new Error(`hq bot login supports ${BOT_LOGIN_RUNTIMES.join(", ")}${opts.runtime ? "" : `; ${name} runs on ${runtime}, pass --runtime`}.`), { expected: true });
|
|
1980
|
+
}
|
|
1981
|
+
if (!(deps.interactive ?? process.stdin.isTTY === true)) {
|
|
1982
|
+
throw Object.assign(new Error("hq bot login needs an interactive terminal: the sign-in opens in your browser."), { expected: true });
|
|
1983
|
+
}
|
|
1984
|
+
signInBotRuntime(dir, runtime, deps.spawn ? { spawn: deps.spawn } : {});
|
|
1985
|
+
const next = patchBotConfig(dir, { runtimeAuth: withRuntimeAuth(config.runtimeAuth, runtime, { mode: "own" }) });
|
|
1986
|
+
createBotLogger(dir)("info", `login: own ${runtime} login set`);
|
|
1987
|
+
const login = describeBotAuth(dir, { ...next, runtime });
|
|
1988
|
+
if (opts.json)
|
|
1989
|
+
console.log(JSON.stringify({ ok: true, name, runtime, login }, null, 2));
|
|
1990
|
+
else
|
|
1991
|
+
console.log(chalk.green(`${name} now uses its own ${runtime} login${runtime === config.runtime ? " from its next message" : ` when it runs on ${runtime}`}.`));
|
|
1992
|
+
}, { command: "hq bot login" });
|
|
1993
|
+
}
|
|
1994
|
+
/** `hq bot logout <name>`: sign the bot's own login out and delete it. */
|
|
1995
|
+
export async function runBotLogout(nameArg, opts, deps = {}) {
|
|
1996
|
+
const name = validateBotName(nameArg);
|
|
1997
|
+
await withBotLock(name, async () => {
|
|
1998
|
+
const { dir, config } = requireBot(name);
|
|
1999
|
+
if (opts.runtime !== undefined && !isBotLoginRuntime(opts.runtime)) {
|
|
2000
|
+
throw Object.assign(new Error(`--runtime must be one of ${BOT_LOGIN_RUNTIMES.join(", ")}`), { expected: true });
|
|
2001
|
+
}
|
|
2002
|
+
const runtimes = opts.runtime ? [opts.runtime] : [...BOT_LOGIN_RUNTIMES];
|
|
2003
|
+
let runtimeAuth = config.runtimeAuth;
|
|
2004
|
+
const results = [];
|
|
2005
|
+
for (const rt of runtimes) {
|
|
2006
|
+
const r = signOutBotRuntime(dir, rt, deps.spawn ? { spawn: deps.spawn } : {});
|
|
2007
|
+
results.push({ runtime: rt, ...r });
|
|
2008
|
+
// A failed vendor logout keeps the folder and the setting, so the bot never drops to your login.
|
|
2009
|
+
if (r.removed && botRuntimeAuth({ runtime: rt, runtimeAuth }, rt)?.mode === "own")
|
|
2010
|
+
runtimeAuth = withRuntimeAuth(runtimeAuth, rt, null);
|
|
2011
|
+
}
|
|
2012
|
+
patchBotConfig(dir, { runtimeAuth });
|
|
2013
|
+
createBotLogger(dir)("info", `logout: ${results.map((r) => `${r.runtime}=${r.removed ? "removed" : "none"}`).join(" ")}`);
|
|
2014
|
+
if (opts.json) {
|
|
2015
|
+
console.log(JSON.stringify({ ok: true, name, results }, null, 2));
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
const failed = results.filter((r) => r.vendorLogout === "failed");
|
|
2019
|
+
for (const r of results) {
|
|
2020
|
+
if (r.removed)
|
|
2021
|
+
console.log(chalk.green(`${name}'s own ${r.runtime} login is signed out and removed.`));
|
|
2022
|
+
else if (r.vendorLogout === "failed")
|
|
2023
|
+
console.log(chalk.yellow(`${r.runtime} logout did not finish, so ${name}'s own ${r.runtime} login was kept. Make sure ${r.runtime} is installed, then run hq bot logout ${name} --runtime ${r.runtime} again.`));
|
|
2024
|
+
}
|
|
2025
|
+
if (failed.length)
|
|
2026
|
+
process.exitCode = 1;
|
|
2027
|
+
if (!results.some((r) => r.removed || r.vendorLogout === "failed"))
|
|
2028
|
+
console.log(`${name} has no login of its own${opts.runtime ? ` for ${opts.runtime}` : ""}.`);
|
|
2029
|
+
console.log(chalk.dim(` ${name} now uses your default sign-in for ${runtimes.join(" and ")} unless an account is set (hq bot set ${name} --account <id>).`));
|
|
2030
|
+
}, { command: "hq bot logout" });
|
|
2031
|
+
}
|
|
1781
2032
|
/** `hq bot probe`: exit code 0 when the runtime answered, 1 when it did not. */
|
|
1782
2033
|
export async function runBotProbe(opts, deps = {}) {
|
|
1783
2034
|
const saved = opts.bot ? requireBot(opts.bot).config : undefined;
|
|
@@ -1795,6 +2046,7 @@ export async function runBotProbe(opts, deps = {}) {
|
|
|
1795
2046
|
...(model ? { model } : {}),
|
|
1796
2047
|
...(effort ? { effort } : {}),
|
|
1797
2048
|
timeoutMs,
|
|
2049
|
+
...(opts.bot && saved ? { env: botAuthEnv(botDir(validateBotName(opts.bot)), { ...saved, runtime }, process.env) } : {}),
|
|
1798
2050
|
});
|
|
1799
2051
|
if (!result.ok)
|
|
1800
2052
|
process.exitCode = 1;
|
|
@@ -2588,11 +2840,25 @@ export function registerBotCommand(program) {
|
|
|
2588
2840
|
bot
|
|
2589
2841
|
.command("set <name>")
|
|
2590
2842
|
.description("Change the model and thinking level a bot uses (applies from its next message), or the name people see for it (saved in HQ; a running bot restarts to use it)")
|
|
2843
|
+
.option("--runtime <runtime>", `Switch the model CLI: ${BOT_RUNTIMES.join("|")}. Checked with one test turn first; the old runtime's sessions are set aside (backed up), DM history and memory stay`)
|
|
2844
|
+
.option("--account <id>", "Use this hq capacity account's login for the bot's runtime, or default for your own sign-in")
|
|
2591
2845
|
.option("--model <model>", "Model id for the runtime CLI, or default for the CLI's own default")
|
|
2592
2846
|
.option("--display-name <name>", `The name people see for the bot in HQ (up to ${BOT_DISPLAY_NAME_MAX_CHARS} chars). The handle stays <name>`)
|
|
2593
2847
|
.option("--effort <level>", `Thinking level (claude ${BOT_EFFORT_LEVELS.claude.join("|")}; codex ${BOT_EFFORT_LEVELS.codex.join("|")}; grok ${BOT_EFFORT_LEVELS.grok.join("|")}), or default (${DEFAULT_BOT_EFFORT})`)
|
|
2594
2848
|
.option("--json", "Print machine-readable JSON")
|
|
2595
2849
|
.action((name, opts) => wrap(() => runBotSet(name, opts))());
|
|
2850
|
+
bot
|
|
2851
|
+
.command("login <name>")
|
|
2852
|
+
.description("Give the bot a model login of its own, so it does not use yours: runs the runtime's own sign-in into ~/.hq/bots/<name>/auth/<runtime>")
|
|
2853
|
+
.option("--runtime <runtime>", `${BOT_LOGIN_RUNTIMES.join("|")} (default: the bot's runtime)`)
|
|
2854
|
+
.option("--json", "Print machine-readable JSON")
|
|
2855
|
+
.action((name, opts) => wrap(() => runBotLogin(name, opts))());
|
|
2856
|
+
bot
|
|
2857
|
+
.command("logout <name>")
|
|
2858
|
+
.description("Sign the bot's own model login out and delete it; the bot goes back to your default sign-in")
|
|
2859
|
+
.option("--runtime <runtime>", `${BOT_LOGIN_RUNTIMES.join("|")} (default: both)`)
|
|
2860
|
+
.option("--json", "Print machine-readable JSON")
|
|
2861
|
+
.action((name, opts) => wrap(() => runBotLogout(name, opts))());
|
|
2596
2862
|
bot
|
|
2597
2863
|
.command("set-model <name> <model>")
|
|
2598
2864
|
.description("Set the model this bot asks its runtime for (stored in the bot's own settings; the runtime's own config is never changed), or default to clear it")
|
|
@@ -2641,4 +2907,14 @@ export function registerBotCommand(program) {
|
|
|
2641
2907
|
console.log(opts.json ? JSON.stringify({ ok: true, ...r }, null, 2) : r.message);
|
|
2642
2908
|
})());
|
|
2643
2909
|
}
|
|
2910
|
+
/**
|
|
2911
|
+
* The company a bot's hot-signal-sync job syncs: the bot's own company, else
|
|
2912
|
+
* the first company it serves. Null = none, and no job is registered. There is
|
|
2913
|
+
* no fallback to the HQ's active company: a personal bot syncs a company only
|
|
2914
|
+
* when it was given one explicitly (`--company`), so the job never writes into
|
|
2915
|
+
* a vault the bot does not belong to.
|
|
2916
|
+
*/
|
|
2917
|
+
export function hotSignalSyncCompany(config) {
|
|
2918
|
+
return currentHotSignalSyncCompany(config);
|
|
2919
|
+
}
|
|
2644
2920
|
//# sourceMappingURL=bot.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { type ChannelSummary } from "./dm.js";
|
|
2
|
+
import { type ChannelSummary, type DmContact } from "./dm.js";
|
|
3
3
|
/**
|
|
4
4
|
* Human label for a channel row. Named channels (personal/company) show their
|
|
5
5
|
* name + a `hq dm <slug>` hint; unnamed group DMs are identified by their
|
|
@@ -38,6 +38,23 @@ export declare function parseActiveSince(value: string, now?: number): number |
|
|
|
38
38
|
* original order). Pure → unit-testable.
|
|
39
39
|
*/
|
|
40
40
|
export declare function channelsByActivity<T extends ChannelWithActivity>(channels: T[], sinceMs?: number): T[];
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the `<member>` argument of `hq channels add|remove` to a person or
|
|
43
|
+
* agent uid. Accepts a uid (`prs_…`, `agt_…`), an email, or a display name /
|
|
44
|
+
* @handle matched against the caller's DM contacts (which include the bots the
|
|
45
|
+
* caller owns). Pure → unit-testable. Returns an error sentence on no or
|
|
46
|
+
* ambiguous match so the caller never guesses. Feedback e165b286.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveMemberTarget(contacts: DmContact[], raw: string): {
|
|
49
|
+
ok: true;
|
|
50
|
+
personUid: string;
|
|
51
|
+
displayName: string;
|
|
52
|
+
} | {
|
|
53
|
+
ok: false;
|
|
54
|
+
error: string;
|
|
55
|
+
};
|
|
56
|
+
/** The server's refusal, as one plain sentence. */
|
|
57
|
+
export declare function channelMembershipError(res: Response, verb: "add" | "remove", fallback: string): Promise<string>;
|
|
41
58
|
export declare function registerChannelsCommand(program: Command): void;
|
|
42
59
|
export {};
|
|
43
60
|
//# sourceMappingURL=channels.d.ts.map
|