@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.
Files changed (117) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/command-catalog.generated.d.ts +197 -1
  3. package/dist/command-catalog.generated.js +241 -1
  4. package/dist/commands/bot-jobs.d.ts +11 -9
  5. package/dist/commands/bot-jobs.js +56 -21
  6. package/dist/commands/bot.d.ts +37 -1
  7. package/dist/commands/bot.js +289 -13
  8. package/dist/commands/channels.d.ts +18 -1
  9. package/dist/commands/channels.js +185 -1
  10. package/dist/commands/daemon.d.ts +2 -0
  11. package/dist/commands/daemon.js +15 -4
  12. package/dist/commands/integrations-core.d.ts +2 -0
  13. package/dist/commands/integrations-core.js +1 -0
  14. package/dist/commands/lanes.d.ts +1 -1
  15. package/dist/commands/lanes.js +6 -2
  16. package/dist/commands/mesh.js +19 -0
  17. package/dist/commands/sources.d.ts +89 -0
  18. package/dist/commands/sources.js +363 -1
  19. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2285 -1743
  20. package/dist/hq-cloud-sync-runner/package.json +1 -1
  21. package/dist/lib/bot/config.d.ts +8 -0
  22. package/dist/lib/bot/fleet-local.d.ts +1 -1
  23. package/dist/lib/bot/fleet-local.js +82 -30
  24. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +109 -0
  25. package/dist/lib/bot/jobs/hot-signal-sync.js +177 -0
  26. package/dist/lib/bot/local-jobs.d.ts +40 -4
  27. package/dist/lib/bot/local-jobs.js +72 -5
  28. package/dist/lib/bot/run.d.ts +15 -0
  29. package/dist/lib/bot/run.js +99 -1
  30. package/dist/lib/bot/runtime-auth.d.ts +119 -0
  31. package/dist/lib/bot/runtime-auth.js +251 -0
  32. package/dist/lib/daemon/catalog.d.ts +1 -0
  33. package/dist/lib/daemon/catalog.js +1 -1
  34. package/dist/lib/daemon/handoff.d.ts +4 -0
  35. package/dist/lib/daemon/handoff.js +6 -2
  36. package/dist/lib/daemon/install.d.ts +70 -2
  37. package/dist/lib/daemon/install.js +185 -27
  38. package/dist/lib/daemon/legacy.d.ts +9 -3
  39. package/dist/lib/daemon/legacy.js +11 -4
  40. package/dist/lib/daemon/migrate.js +1 -1
  41. package/dist/lib/daemon/run.d.ts +5 -2
  42. package/dist/lib/daemon/run.js +74 -22
  43. package/dist/lib/doctor/checks/anywhere.d.ts +6 -0
  44. package/dist/lib/doctor/checks/anywhere.js +35 -6
  45. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  46. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  47. package/dist/lib/hot-channels/adapters/__fixtures__/dummy.d.ts +33 -0
  48. package/dist/lib/hot-channels/adapters/__fixtures__/dummy.js +53 -0
  49. package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.d.ts +38 -0
  50. package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.js +64 -0
  51. package/dist/lib/hot-channels/adapters/attio.d.ts +67 -0
  52. package/dist/lib/hot-channels/adapters/attio.js +374 -0
  53. package/dist/lib/hot-channels/adapters/gmail.d.ts +111 -0
  54. package/dist/lib/hot-channels/adapters/gmail.js +413 -0
  55. package/dist/lib/hot-channels/adapters/index.d.ts +23 -0
  56. package/dist/lib/hot-channels/adapters/index.js +39 -0
  57. package/dist/lib/hot-channels/adapters/linear.d.ts +79 -0
  58. package/dist/lib/hot-channels/adapters/linear.js +321 -0
  59. package/dist/lib/hot-channels/adapters/meeting-providers.d.ts +86 -0
  60. package/dist/lib/hot-channels/adapters/meeting-providers.js +253 -0
  61. package/dist/lib/hot-channels/adapters/meetings.d.ts +81 -0
  62. package/dist/lib/hot-channels/adapters/meetings.js +525 -0
  63. package/dist/lib/hot-channels/adapters/slack.d.ts +70 -0
  64. package/dist/lib/hot-channels/adapters/slack.js +277 -0
  65. package/dist/lib/hot-channels/alert.d.ts +78 -0
  66. package/dist/lib/hot-channels/alert.js +179 -0
  67. package/dist/lib/hot-channels/audience.d.ts +15 -0
  68. package/dist/lib/hot-channels/audience.js +25 -0
  69. package/dist/lib/hot-channels/contract.d.ts +146 -0
  70. package/dist/lib/hot-channels/contract.js +20 -0
  71. package/dist/lib/hot-channels/cursors.d.ts +41 -0
  72. package/dist/lib/hot-channels/cursors.js +84 -0
  73. package/dist/lib/hot-channels/index.d.ts +10 -0
  74. package/dist/lib/hot-channels/index.js +10 -0
  75. package/dist/lib/hot-channels/landing.d.ts +83 -0
  76. package/dist/lib/hot-channels/landing.js +175 -0
  77. package/dist/lib/hot-channels/ledger.d.ts +101 -0
  78. package/dist/lib/hot-channels/ledger.js +203 -0
  79. package/dist/lib/hot-channels/loop.d.ts +74 -0
  80. package/dist/lib/hot-channels/loop.js +221 -0
  81. package/dist/lib/hot-channels/prompts/signals-pass.md +22 -0
  82. package/dist/lib/hot-channels/registry.d.ts +28 -0
  83. package/dist/lib/hot-channels/registry.js +121 -0
  84. package/dist/lib/hot-channels/run.d.ts +137 -0
  85. package/dist/lib/hot-channels/run.js +324 -0
  86. package/dist/lib/hot-channels/signals-pass.d.ts +150 -0
  87. package/dist/lib/hot-channels/signals-pass.js +581 -0
  88. package/dist/lib/hot-channels/sync-lock.d.ts +51 -0
  89. package/dist/lib/hot-channels/sync-lock.js +224 -0
  90. package/dist/lib/install/hook-target.d.ts +6 -2
  91. package/dist/lib/install/hook-target.js +9 -4
  92. package/dist/lib/lanes/accounts.d.ts +1 -17
  93. package/dist/lib/lanes/accounts.js +5 -45
  94. package/dist/lib/lanes/loop.d.ts +10 -0
  95. package/dist/lib/lanes/loop.js +18 -1
  96. package/dist/lib/lanes/provider-home.d.ts +24 -0
  97. package/dist/lib/lanes/provider-home.js +52 -0
  98. package/dist/lib/lanes/spawn.d.ts +12 -0
  99. package/dist/lib/lanes/spawn.js +27 -0
  100. package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
  101. package/dist/lib/mesh/live/daemon/doctor.js +13 -0
  102. package/dist/lib/mesh/live/daemon/owner-inbox-push.d.ts +94 -0
  103. package/dist/lib/mesh/live/daemon/owner-inbox-push.js +273 -0
  104. package/dist/lib/mesh/live/daemon/run.js +31 -0
  105. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +14 -0
  106. package/dist/lib/mesh/live/daemon/work-subscribe.js +210 -3
  107. package/dist/lib/mesh/live/paths.d.ts +2 -0
  108. package/dist/lib/mesh/live/paths.js +2 -0
  109. package/dist/lib/sync-core/flag.d.ts +1 -1
  110. package/dist/lib/sync-core/flag.js +1 -1
  111. package/dist/lib/sync-core/reconcile.d.ts +1 -1
  112. package/dist/lib/sync-core/reconcile.js +9 -7
  113. package/dist/lib/sync-core/release.d.ts +3 -3
  114. package/dist/lib/sync-core/release.js +4 -4
  115. package/dist/lib/sync-core/rollout-state.d.ts +1 -0
  116. package/dist/lib/sync-core/rollout-state.js +1 -0
  117. 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 in the cloud, never on this computer: hq-pro-agents holds
10
- * the job and fires it on an EventBridge schedule, and the bot's inbox loop
11
- * runs the fired run (see lib/bot/job-run.ts). Nothing here writes a crontab,
12
- * a launchd plist, or a systemd unit — by rule.
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 !== undefined && opts.rm !== undefined) {
369
- refuse("Add a job or remove one, not both in the same command.");
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
- if (!opts.every && !opts.cron) {
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 = ("human" in schedule && schedule.human) || describeSchedule(created);
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 (the schedule runs in the cloud, not on this computer)")
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 {
@@ -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 declare function runBotSet(nameArg: string, opts: BotSetOptions): Promise<void>;
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
@@ -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 && kind === "personal" ? await fleetOwnerCompanies() : [];
808
- if (useFleetRuntime && kind !== "personal") {
809
- throw Object.assign(new Error("--hq-fleet-runtime creates a personal bot. Drop --kind company and --company."), {
810
- expected: true,
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
- console.log(` ${chalk.yellow(what)} — sign in from HQ, or run \`${signInCommand(status.runtimeSignIn.runtime)}\`; ${name} checks again on its own`);
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
- async function runBotSetLocked(nameArg, opts) {
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