@indigoai-us/hq-cli 5.345.177 → 5.345.179

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 (100) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/command-catalog.generated.d.ts +79 -1
  3. package/dist/command-catalog.generated.js +96 -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 +13 -0
  7. package/dist/commands/bot.js +230 -21
  8. package/dist/commands/cloud.d.ts +49 -0
  9. package/dist/commands/cloud.js +155 -15
  10. package/dist/commands/daemon.d.ts +2 -0
  11. package/dist/commands/daemon.js +15 -4
  12. package/dist/commands/dm-attach.js +15 -3
  13. package/dist/commands/integrations-core.d.ts +2 -0
  14. package/dist/commands/integrations-core.js +1 -0
  15. package/dist/commands/library-add.d.ts +9 -0
  16. package/dist/commands/library-add.js +19 -2
  17. package/dist/commands/run.js +9 -1
  18. package/dist/commands/sources.d.ts +89 -0
  19. package/dist/commands/sources.js +363 -1
  20. package/dist/lib/bot/config.d.ts +2 -0
  21. package/dist/lib/bot/fleet-default-flag.d.ts +31 -0
  22. package/dist/lib/bot/fleet-default-flag.js +127 -0
  23. package/dist/lib/bot/fleet-local.d.ts +30 -1
  24. package/dist/lib/bot/fleet-local.js +86 -11
  25. package/dist/lib/bot/index.d.ts +1 -0
  26. package/dist/lib/bot/index.js +1 -0
  27. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +109 -0
  28. package/dist/lib/bot/jobs/hot-signal-sync.js +177 -0
  29. package/dist/lib/bot/local-config.js +1 -1
  30. package/dist/lib/bot/local-jobs.d.ts +40 -4
  31. package/dist/lib/bot/local-jobs.js +72 -5
  32. package/dist/lib/bot/run.d.ts +15 -0
  33. package/dist/lib/bot/run.js +61 -0
  34. package/dist/lib/daemon/catalog.d.ts +2 -0
  35. package/dist/lib/daemon/catalog.js +7 -2
  36. package/dist/lib/daemon/config.d.ts +2 -1
  37. package/dist/lib/daemon/config.js +4 -3
  38. package/dist/lib/daemon/handoff.d.ts +4 -0
  39. package/dist/lib/daemon/handoff.js +6 -2
  40. package/dist/lib/daemon/install.d.ts +70 -2
  41. package/dist/lib/daemon/install.js +185 -27
  42. package/dist/lib/daemon/legacy.d.ts +9 -3
  43. package/dist/lib/daemon/legacy.js +11 -4
  44. package/dist/lib/daemon/migrate.js +1 -1
  45. package/dist/lib/daemon/run.d.ts +3 -2
  46. package/dist/lib/daemon/run.js +10 -6
  47. package/dist/lib/doctor/checks/anywhere.d.ts +6 -0
  48. package/dist/lib/doctor/checks/anywhere.js +35 -6
  49. package/dist/lib/hot-channels/adapters/__fixtures__/dummy.d.ts +33 -0
  50. package/dist/lib/hot-channels/adapters/__fixtures__/dummy.js +53 -0
  51. package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.d.ts +38 -0
  52. package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.js +64 -0
  53. package/dist/lib/hot-channels/adapters/attio.d.ts +67 -0
  54. package/dist/lib/hot-channels/adapters/attio.js +374 -0
  55. package/dist/lib/hot-channels/adapters/gmail.d.ts +111 -0
  56. package/dist/lib/hot-channels/adapters/gmail.js +413 -0
  57. package/dist/lib/hot-channels/adapters/index.d.ts +23 -0
  58. package/dist/lib/hot-channels/adapters/index.js +39 -0
  59. package/dist/lib/hot-channels/adapters/linear.d.ts +79 -0
  60. package/dist/lib/hot-channels/adapters/linear.js +321 -0
  61. package/dist/lib/hot-channels/adapters/meeting-providers.d.ts +86 -0
  62. package/dist/lib/hot-channels/adapters/meeting-providers.js +253 -0
  63. package/dist/lib/hot-channels/adapters/meetings.d.ts +81 -0
  64. package/dist/lib/hot-channels/adapters/meetings.js +523 -0
  65. package/dist/lib/hot-channels/adapters/slack.d.ts +70 -0
  66. package/dist/lib/hot-channels/adapters/slack.js +277 -0
  67. package/dist/lib/hot-channels/alert.d.ts +78 -0
  68. package/dist/lib/hot-channels/alert.js +179 -0
  69. package/dist/lib/hot-channels/audience.d.ts +15 -0
  70. package/dist/lib/hot-channels/audience.js +25 -0
  71. package/dist/lib/hot-channels/contract.d.ts +140 -0
  72. package/dist/lib/hot-channels/contract.js +20 -0
  73. package/dist/lib/hot-channels/cursors.d.ts +41 -0
  74. package/dist/lib/hot-channels/cursors.js +84 -0
  75. package/dist/lib/hot-channels/index.d.ts +10 -0
  76. package/dist/lib/hot-channels/index.js +10 -0
  77. package/dist/lib/hot-channels/landing.d.ts +83 -0
  78. package/dist/lib/hot-channels/landing.js +175 -0
  79. package/dist/lib/hot-channels/ledger.d.ts +101 -0
  80. package/dist/lib/hot-channels/ledger.js +186 -0
  81. package/dist/lib/hot-channels/loop.d.ts +74 -0
  82. package/dist/lib/hot-channels/loop.js +219 -0
  83. package/dist/lib/hot-channels/prompts/signals-pass.md +22 -0
  84. package/dist/lib/hot-channels/registry.d.ts +28 -0
  85. package/dist/lib/hot-channels/registry.js +121 -0
  86. package/dist/lib/hot-channels/run.d.ts +137 -0
  87. package/dist/lib/hot-channels/run.js +324 -0
  88. package/dist/lib/hot-channels/signals-pass.d.ts +150 -0
  89. package/dist/lib/hot-channels/signals-pass.js +581 -0
  90. package/dist/lib/hot-channels/sync-lock.d.ts +51 -0
  91. package/dist/lib/hot-channels/sync-lock.js +224 -0
  92. package/dist/lib/install/hook-target.d.ts +6 -2
  93. package/dist/lib/install/hook-target.js +9 -4
  94. package/dist/lib/lanes/loop.d.ts +10 -0
  95. package/dist/lib/lanes/loop.js +18 -1
  96. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +1 -0
  97. package/dist/lib/mesh/live/daemon/work-subscribe.js +26 -6
  98. package/dist/run/hq-plugin.d.ts +3 -0
  99. package/dist/run/hq-plugin.js +18 -0
  100. package/package.json +7 -7
@@ -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,7 +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";
55
- import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, 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, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
59
+ 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
60
  function collectRepeated(value, previous) {
57
61
  return [...previous, value];
58
62
  }
@@ -144,11 +148,25 @@ function fleetBotSystemPrompt(name, config, overrides = {}) {
144
148
  }),
145
149
  });
146
150
  }
151
+ function fleetAuthCommand(name, config) {
152
+ return `hq bot auth ${name} --provider ${config.runtime === "grok" ? "grok" : config.runtime === "codex" ? "chatgpt" : "claude"}`;
153
+ }
147
154
  function runFleetRuntimeControl(name, config, action, json) {
155
+ if ((action === "start" || action === "restart") && config.fleetAuthPending === true) {
156
+ const authCommand = fleetAuthCommand(name, config);
157
+ if (json)
158
+ console.log(JSON.stringify({ ok: false, name, running: false, state: "stopped", needsSignIn: true, authCommand }, null, 2));
159
+ else
160
+ throw Object.assign(new Error(`Sign this bot in first: ${authCommand}`), { expected: true });
161
+ return;
162
+ }
148
163
  if (action === "start" || action === "restart") {
149
164
  refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
150
165
  }
151
- const result = controlFleetLocalBot(config.agentUid, action);
166
+ const installService = config.fleetServiceInstallPending === true && (action === "start" || action === "restart");
167
+ const result = controlFleetLocalBot(config.agentUid, installService ? "install" : action);
168
+ if (installService && result.ok)
169
+ patchBotConfig(botDir(name), { fleetServiceInstallPending: false, enabled: true });
152
170
  if (json) {
153
171
  console.log(JSON.stringify({ name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
154
172
  return;
@@ -192,7 +210,7 @@ function rowFor(name, opts = {}) {
192
210
  const dir = botDir(name);
193
211
  const config = readBotConfig(dir);
194
212
  const fleet = isFleetRuntimeBot(config);
195
- const status = fleet ? null : readBotStatus(dir);
213
+ const status = readBotStatus(dir);
196
214
  const pid = fleet ? fleetLocalGatewayPid(config.agentUid) : (status?.pid ?? null);
197
215
  const processAlive = pid !== null && isPidAlive(pid) && (fleet || status?.state === "running");
198
216
  // Turns this bot has taken and not finished: what "thinking" means.
@@ -220,7 +238,7 @@ function rowFor(name, opts = {}) {
220
238
  model: config.model,
221
239
  effort: effectiveBotEffort(config),
222
240
  effortIsDefault: !config.effort,
223
- state: processAlive ? "running" : status?.state === "failed" ? "failed" : "stopped",
241
+ state: processAlive ? "running" : !fleet && status?.state === "failed" ? "failed" : "stopped",
224
242
  pid: processAlive ? pid : null,
225
243
  processAlive,
226
244
  hosting: hasActivePromotion(dir, config.agentUid, config.ownerUid) ? "cloud" : "local",
@@ -234,8 +252,10 @@ function rowFor(name, opts = {}) {
234
252
  busySince: processAlive ? (inflightTurns[0]?.startedAt ?? null) : null,
235
253
  runtimeSignIn: status?.runtimeSignIn ?? null,
236
254
  runtimeSignInCheck: status?.runtimeSignInCheck ?? null,
237
- runtimeSignedIn: runtimeSignedIn(status, processAlive),
238
- signInCommand: signInCommand(config.runtime),
255
+ runtimeSignedIn: config.fleetAuthPending === true ? false : runtimeSignedIn(status, processAlive),
256
+ ...(fleet && config.fleetAuthPending === true ? { needsSignIn: true, authCommand: fleetAuthCommand(name, config) } : {}),
257
+ ...(fleet && config.fleetServiceInstallPending === true ? { needsSignIn: false, serviceInstallPending: true } : {}),
258
+ signInCommand: fleet ? fleetAuthCommand(name, config) : signInCommand(config.runtime),
239
259
  runtimeCrash: processAlive ? runtimeCrashSinceStart(status) : null,
240
260
  daemonInstalled,
241
261
  daemonLoaded,
@@ -340,6 +360,8 @@ function stopProcess(dir) {
340
360
  async function startBot(name, dir, config, output, extra = {}) {
341
361
  if (hasPromotionHold(dir))
342
362
  throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
363
+ if (isFleetRuntimeBot(config))
364
+ return startFleetRuntimeBot(name, dir, config, output, extra);
343
365
  if (config.enabled === false)
344
366
  patchBotConfig(dir, { enabled: true });
345
367
  const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
@@ -401,6 +423,41 @@ async function startBot(name, dir, config, output, extra = {}) {
401
423
  }
402
424
  return { running, how };
403
425
  }
426
+ /** Start (or restart) an HQ Fleet Runtime bot under its own service, with fresh instructions. */
427
+ async function startFleetRuntimeBot(name, dir, config, output, extra) {
428
+ if (config.fleetAuthPending === true) {
429
+ const authCommand = fleetAuthCommand(name, config);
430
+ if (output === "json")
431
+ console.log(JSON.stringify({ ok: false, name, running: false, needsSignIn: true, authCommand }, null, 2));
432
+ else if (output !== "silent")
433
+ throw Object.assign(new Error(`Sign this bot in first: ${authCommand}`), { expected: true });
434
+ return { running: false, how: "waiting for sign-in" };
435
+ }
436
+ refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
437
+ const before = readBotStatus(dir);
438
+ const alreadyRunning = !!before && before.state === "running" && isPidAlive(before.pid);
439
+ const installService = config.fleetServiceInstallPending === true;
440
+ const result = controlFleetLocalBot(config.agentUid, installService ? "install" : alreadyRunning ? "restart" : "start");
441
+ if (installService && result.ok)
442
+ patchBotConfig(dir, { fleetServiceInstallPending: false, enabled: true });
443
+ const how = alreadyRunning ? "restarted by the HQ Fleet Runtime" : "started by the HQ Fleet Runtime";
444
+ const running = result.ok === true && (await waitFor(() => {
445
+ const s = readBotStatus(dir);
446
+ return !!s && s.state === "running" && isPidAlive(s.pid);
447
+ }, 45_000));
448
+ const status = readBotStatus(dir);
449
+ if (output === "silent")
450
+ return { running, how };
451
+ if (output === "json") {
452
+ console.log(JSON.stringify({ ok: running, name, ...extra, running, how, state: status?.state ?? "unknown", pid: status?.pid ?? null, lastError: null }, null, 2));
453
+ return { running, how };
454
+ }
455
+ if (running)
456
+ console.log(chalk.green(`${name} is running`) + chalk.dim(` (${how})`));
457
+ else
458
+ console.log(chalk.yellow(`${name} did not report running within 45s`) + chalk.dim(` logs: ~/.hq-fleet/profiles/${config.agentUid}/logs`));
459
+ return { running, how };
460
+ }
404
461
  /**
405
462
  * `output`: "json" | "text" print a result; "silent" prints nothing — used
406
463
  * when stop is a step inside rm/restart, whose own JSON must be the only
@@ -436,6 +493,14 @@ function requestSelfStopDrain(botPid) {
436
493
  async function stopBot(name, dir, config, output) {
437
494
  if (hasPromotionHold(dir))
438
495
  throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
496
+ if (isFleetRuntimeBot(config)) {
497
+ const result = controlFleetLocalBot(config.agentUid, "stop");
498
+ if (output === "json")
499
+ console.log(JSON.stringify({ ok: result.ok === true, name, stopped: result.ok === true, how: "stopped by the HQ Fleet Runtime" }, null, 2));
500
+ else if (output === "text")
501
+ console.log(chalk.green(`${name} stopped`));
502
+ return;
503
+ }
439
504
  const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
440
505
  const hqDaemon = daemonPaths();
441
506
  const before = readBotStatus(dir);
@@ -546,6 +611,28 @@ export function resolveCreateKind(opts, worker) {
546
611
  }
547
612
  return { kind, companies };
548
613
  }
614
+ async function promptFleetProvider() {
615
+ if (process.stdin.isTTY !== true || process.stderr.isTTY !== true) {
616
+ throw Object.assign(new Error("--provider is required with --hq-fleet-runtime in a non-interactive command (claude, codex, or grok)."), { expected: true });
617
+ }
618
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
619
+ try {
620
+ const answer = await new Promise((resolve) => {
621
+ rl.question("Provider: 1) Codex 2) Claude 3) Grok [1]: ", resolve);
622
+ });
623
+ const selected = answer.trim().toLowerCase();
624
+ if (selected === "" || selected === "1" || selected === "codex")
625
+ return "codex";
626
+ if (selected === "2" || selected === "claude")
627
+ return "claude";
628
+ if (selected === "3" || selected === "grok")
629
+ return "grok";
630
+ throw Object.assign(new Error("Provider must be claude, codex, or grok."), { expected: true });
631
+ }
632
+ finally {
633
+ rl.close();
634
+ }
635
+ }
549
636
  /**
550
637
  * `hq bot create`. Runs under the bot's lock, so a create racing an adopt,
551
638
  * restore, rm or set of the same bot waits for it instead of tripping over
@@ -664,11 +751,21 @@ async function finishExistingSetupBot(name, dir, config, displayName, opts) {
664
751
  console.log(chalk.yellow(`${name} did not report running within 15s`) + chalk.dim(` logs: hq bot logs ${name}`));
665
752
  }
666
753
  async function runBotCreateLocked(name, opts) {
667
- const useFleetRuntime = opts.hqFleetRuntime === true;
668
- if (useFleetRuntime && (opts.runtime || opts.model || opts.effort || opts.lean)) {
669
- throw Object.assign(new Error("--hq-fleet-runtime selects the local HQ runtime. Do not combine it with --runtime, --model, --effort, or --lean."), { expected: true });
754
+ const requestedFleetRuntime = opts.hqFleetRuntime === true;
755
+ if (requestedFleetRuntime && opts.legacyRuntime) {
756
+ throw Object.assign(new Error("Pick one of --hq-fleet-runtime and --legacy-runtime."), { expected: true });
670
757
  }
671
- const runtime = opts.runtime ?? "claude";
758
+ if (requestedFleetRuntime && (opts.effort || opts.lean)) {
759
+ throw Object.assign(new Error("--hq-fleet-runtime sets its own thinking level. Do not combine it with --effort or --lean."), { expected: true });
760
+ }
761
+ if (opts.provider && !isBotRuntimeId(opts.provider)) {
762
+ throw Object.assign(new Error(`--provider must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
763
+ }
764
+ if (opts.provider && opts.runtime && opts.provider !== opts.runtime) {
765
+ throw Object.assign(new Error("--provider and --runtime must name the same provider when both are used."), { expected: true });
766
+ }
767
+ const selectedProvider = opts.provider ?? opts.runtime ?? (requestedFleetRuntime ? await promptFleetProvider() : undefined);
768
+ const runtime = selectedProvider ?? "claude";
672
769
  if (!isBotRuntimeId(runtime)) {
673
770
  throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
674
771
  }
@@ -703,6 +800,15 @@ async function runBotCreateLocked(name, opts) {
703
800
  // cost an identity).
704
801
  const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
705
802
  const { kind, companies } = resolveCreateKind(opts, worker);
803
+ // Companies with the fleet-default flag get their personal bots on
804
+ // the HQ Fleet Runtime; --legacy-runtime keeps the model-CLI runner.
805
+ const fleetCandidate = requestedFleetRuntime ||
806
+ (!opts.legacyRuntime && kind === "personal" && !opts.effort && !opts.lean);
807
+ // Owner turns on the bot may use only these companies: the owner's own,
808
+ // and only where the company turned the flag on.
809
+ const flaggedCompanies = fleetCandidate && kind === "personal" ? await fleetDefaultCompanies(hqRoot) : [];
810
+ const useFleetRuntime = requestedFleetRuntime || (fleetCandidate && fleetRuntimeIsDefault(flaggedCompanies));
811
+ const ownerCompanies = useFleetRuntime && kind === "personal" ? await fleetOwnerCompanies() : [];
706
812
  if (useFleetRuntime && kind !== "personal") {
707
813
  throw Object.assign(new Error("--hq-fleet-runtime creates a personal bot. Drop --kind company and --company."), {
708
814
  expected: true,
@@ -726,6 +832,8 @@ async function runBotCreateLocked(name, opts) {
726
832
  ? await installPinnedFleetLocalDistribution()
727
833
  : null;
728
834
  const fleetPython = fleetInstallation ? await ensureFleetPython() : null;
835
+ const fleetProvider = fleetInstallation ? fleetProviderForRuntime(runtime, model) : null;
836
+ const fleetAuthPending = fleetProvider !== null && fleetProviderNeedsSignIn(fleetProvider);
729
837
  const token = await ownerToken();
730
838
  const owner = await resolveCallerPersonUid(token, undefined, { command: "hq bot create" });
731
839
  if (!owner)
@@ -932,7 +1040,8 @@ async function runBotCreateLocked(name, opts) {
932
1040
  ...(kickoff ? { kickoff } : {}),
933
1041
  ...(opts.lean && runtime === "claude" ? { lean: true } : {}),
934
1042
  createdAt: new Date().toISOString(),
935
- enabled: true,
1043
+ enabled: !fleetAuthPending,
1044
+ ...(fleetAuthPending ? { fleetAuthPending: true } : {}),
936
1045
  };
937
1046
  writeBotConfig(dir, config);
938
1047
  // A company bot acts as itself, so it must be a member of each of its
@@ -971,7 +1080,13 @@ async function runBotCreateLocked(name, opts) {
971
1080
  installation: fleetInstallation,
972
1081
  machineCredentials: botCredsPath(dir),
973
1082
  systemPrompt: fleetBotSystemPrompt(name, config, { kind, companies }),
974
- }, { start: opts.start !== false, python: fleetPython })
1083
+ provider: fleetProvider,
1084
+ ownerCompanies,
1085
+ firstStart: {
1086
+ intro: introDmText(name, runtime, intro, { kind, companies }),
1087
+ ...(kickoff ? { kickoff } : {}),
1088
+ },
1089
+ }, { start: opts.start !== false && !fleetAuthPending, python: fleetPython })
975
1090
  : null;
976
1091
  const fleetProfile = fleetBootstrap?.profile ?? null;
977
1092
  const runtimeLabel = fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime;
@@ -998,6 +1113,7 @@ async function runBotCreateLocked(name, opts) {
998
1113
  kickoff: kickoff ?? null,
999
1114
  model: model ?? null,
1000
1115
  effort: fleetInstallation ? null : effort ?? DEFAULT_BOT_EFFORT,
1116
+ ...(fleetProvider ? { provider: fleetProvider.id, needsSignIn: fleetAuthPending, ...(fleetAuthPending ? { authCommand: fleetAuthCommand(name, config) } : {}) } : {}),
1001
1117
  };
1002
1118
  let daemonMessage = null;
1003
1119
  if (!fleetInstallation && opts.daemon !== false && process.platform === "darwin") {
@@ -1015,6 +1131,9 @@ async function runBotCreateLocked(name, opts) {
1015
1131
  console.log(chalk.dim(` state: ${dir}`));
1016
1132
  if (fleetProfile)
1017
1133
  console.log(chalk.dim(` profile: ${fleetProfile}`));
1134
+ if (fleetProvider && fleetProviderNeedsSignIn(fleetProvider)) {
1135
+ console.log(chalk.yellow(` sign it in before it can answer: hq bot auth ${name} --provider ${runtime === "grok" ? "grok" : "chatgpt"}`));
1136
+ }
1018
1137
  if (daemonMessage)
1019
1138
  console.log(chalk.dim(` ${daemonMessage}`));
1020
1139
  }
@@ -1159,6 +1278,27 @@ async function runBotResident(nameArg) {
1159
1278
  const token = botTokenSupplier(dir);
1160
1279
  // The bot's own schedule lives in its folder and fires from this loop.
1161
1280
  const api = withLocalJobs(new BotApi({ token }), dir);
1281
+ // The built-in hot-signal-sync job, registered once in the bot's own
1282
+ // jobs.json (06:30 daily in this computer's time zone until the owner
1283
+ // reschedules it with `hq bot jobs`). It runs in-process, with no model turn.
1284
+ const syncCompany = hotSignalSyncCompany(config);
1285
+ try {
1286
+ // A job for a company the bot no longer serves is removed before anything runs.
1287
+ for (const stale of removeStaleHotSignalSyncJobs(dir, syncCompany)) {
1288
+ console.log(`Removed the hot-channel sync job ${stale.jobId}: this bot no longer syncs that company.`);
1289
+ }
1290
+ }
1291
+ catch (err) {
1292
+ console.error(`Could not check the hot-channel sync jobs: ${err instanceof Error ? err.message : String(err)}`);
1293
+ }
1294
+ if (syncCompany) {
1295
+ try {
1296
+ ensureHotSignalSyncJob(dir, { company: syncCompany, timezone: machineTimeZone() });
1297
+ }
1298
+ catch (err) {
1299
+ console.error(`Could not register the hot-channel sync job for ${syncCompany}: ${err instanceof Error ? err.message : String(err)}`);
1300
+ }
1301
+ }
1162
1302
  const owner = new OwnerContextCache(() => lookupOwnerContext(config.ownerUid), Date.now, undefined, config.ownerUid);
1163
1303
  const handle = await runBot({
1164
1304
  dir,
@@ -1166,6 +1306,15 @@ async function runBotResident(nameArg) {
1166
1306
  api,
1167
1307
  runtime: runtimeFor(config.runtime),
1168
1308
  ownerContext: () => owner.get(),
1309
+ hotSignalSync: ({ company, catchUp, now, sendAlert }) => runHotSignalSyncJob({
1310
+ company,
1311
+ catchUp,
1312
+ now,
1313
+ sendAlert,
1314
+ sync: (input) => runCompanyHotChannelSync({ hqRoot: config.hqRoot, ...input }),
1315
+ // A new pass per run: the login preflight and the 20-document cap are per run.
1316
+ signalsPass: createClaudeSignalsPass({ hqRoot: config.hqRoot, company }),
1317
+ }),
1169
1318
  // US-005: decision picks in the owner DM, with the bot's own machine token
1170
1319
  // (never the owner's login).
1171
1320
  decisions: new DecisionsClient(token),
@@ -1232,25 +1381,26 @@ async function runBotList(opts) {
1232
1381
  }
1233
1382
  async function runBotStatus(nameArg, opts) {
1234
1383
  const { name, dir, config } = requireBot(nameArg);
1235
- if (isFleetRuntimeBot(config)) {
1236
- runFleetRuntimeControl(name, config, "status", opts.json === true);
1237
- return;
1238
- }
1384
+ const fleet = isFleetRuntimeBot(config);
1239
1385
  const row = rowFor(name);
1240
1386
  await decorateOnline([row]);
1241
1387
  const status = readBotStatus(dir);
1242
1388
  const creds = readBotCredsIdentity(dir);
1243
1389
  if (opts.json) {
1244
- console.log(JSON.stringify({ ...row, credsPresent: !!creds, status }, null, 2));
1390
+ console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine } : {}), credsPresent: fleet || !!creds, status }, null, 2));
1245
1391
  return;
1246
1392
  }
1247
1393
  console.log(`${chalk.bold(name)} ${row.agentUid}`);
1394
+ if (fleet)
1395
+ console.log(` engine: HQ Fleet Runtime ${config.engine?.version ?? ""} ${chalk.dim(`logs ~/.hq-fleet/profiles/${config.agentUid}/logs`)}`);
1248
1396
  console.log(` kind: ${describeKind(row.kind, row.companies)}`);
1249
1397
  console.log(` runtime: ${row.runtime} model ${row.model ?? "default"} thinking ${row.effort}${row.effortIsDefault ? " (default)" : ""}`);
1250
1398
  if (row.workerId)
1251
1399
  console.log(` worker: ${row.workerId}${row.companySlug ? ` (company ${row.companySlug})` : ""} ${chalk.dim(row.workerDir)}`);
1252
1400
  console.log(` memory: ${row.memory} ${chalk.dim(row.memoryDir)}`);
1253
1401
  console.log(` process: ${row.processAlive ? chalk.green(`running (pid ${row.pid})`) : chalk.gray(row.state)}`);
1402
+ if (row.needsSignIn)
1403
+ console.log(` sign-in: required (${row.authCommand})`);
1254
1404
  console.log(` HQ online: ${humanOnline(row)}${row.lastHeartbeatAt ? chalk.dim(` last heartbeat ${row.lastHeartbeatAt}`) : ""}${row.lastHeartbeatOk === false && status?.lastHeartbeatError ? chalk.dim(` (${status.lastHeartbeatError})`) : ""}`);
1255
1405
  if (status?.lastInboxPollOk === false) {
1256
1406
  console.log(` inbox: ${chalk.red("cannot receive")}${status.lastInboxError ? chalk.dim(` ${status.lastInboxError}`) : ""}`);
@@ -1263,7 +1413,7 @@ async function runBotStatus(nameArg, opts) {
1263
1413
  if (row.daemonNodeMissing) {
1264
1414
  console.log(` ${chalk.red(`node missing: ${row.daemonNodeMissing}`)} — launchd cannot start ${name}; fix with: hq bot start ${name}`);
1265
1415
  }
1266
- console.log(` creds: ${creds ? `present for ${creds.entityUid}` : chalk.red("missing")}`);
1416
+ console.log(` creds: ${fleet ? "in the system keychain" : creds ? `present for ${creds.entityUid}` : chalk.red("missing")}`);
1267
1417
  if (status) {
1268
1418
  console.log(` started: ${status.startedAt} restarts: ${status.restarts} replies: ${status.repliesSent}`);
1269
1419
  if (status.cliVersion)
@@ -1729,17 +1879,30 @@ async function runBotAuth(nameArg, opts) {
1729
1879
  const result = authenticateFleetLocalBot({
1730
1880
  agentUid: b.config.agentUid,
1731
1881
  provider: opts.provider,
1882
+ ...(b.config.fleetAuthPending === true ? { installService: true } : {}),
1732
1883
  ...(opts.model ? { model: opts.model } : {}),
1733
1884
  ...(apiKey ? { apiKey } : {}),
1734
1885
  ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
1735
1886
  ...(opts.transport ? { transport: opts.transport } : {}),
1736
1887
  openBrowser: opts.browser !== false,
1737
1888
  });
1889
+ const signedInRuntime = result.runtimeProvider === "anthropic" ? "claude" : result.runtimeProvider === "xai-oauth" ? "grok" : result.runtimeProvider === "openai-codex" ? "codex" : null;
1890
+ if (result.authenticated !== true)
1891
+ throw Object.assign(new Error("The provider has not completed sign-in."), { expected: true });
1892
+ patchBotConfig(b.dir, {
1893
+ ...(signedInRuntime ? { runtime: signedInRuntime } : {}),
1894
+ model: result.model,
1895
+ ...(b.config.fleetAuthPending === true ? { fleetAuthPending: false, fleetServiceInstallPending: !result.restarted, enabled: result.restarted } : {}),
1896
+ });
1897
+ const pid = fleetLocalGatewayPid(b.config.agentUid);
1898
+ const running = pid !== null && isPidAlive(pid);
1738
1899
  if (opts.json) {
1739
- console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
1900
+ console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result, needsSignIn: false, running, serviceInstallPending: b.config.fleetAuthPending === true && !result.restarted }, null, 2));
1740
1901
  return;
1741
1902
  }
1742
- console.log(chalk.green(`${b.name} is signed in to ${result.provider} and running ${result.model}.`));
1903
+ console.log(running
1904
+ ? chalk.green(`${b.name} is signed in to ${result.provider} and running ${result.model}.`)
1905
+ : chalk.yellow(`${b.name} is signed in to ${result.provider}, but did not start. Run hq bot start ${b.name}.`));
1743
1906
  }
1744
1907
  // ── adopt / restore (bring a bot you own back to this computer) ──────────────
1745
1908
  /**
@@ -1993,6 +2156,9 @@ async function adoptRemoteBotLocked(input) {
1993
2156
  region: DEFAULT_COGNITO.region,
1994
2157
  });
1995
2158
  const local = parseBotLocalConfig(reissued.localConfig ?? bot.localConfig);
2159
+ const fleetEngine = local?.engine?.id === HQ_FLEET_RUNTIME_ENGINE_ID;
2160
+ if (fleetEngine && local && !local.runtime)
2161
+ local.runtime = "codex";
1996
2162
  const { config, notes } = configFromLocalConfig({
1997
2163
  name,
1998
2164
  agentUid: bot.agentUid,
@@ -2003,14 +2169,40 @@ async function adoptRemoteBotLocked(input) {
2003
2169
  ...(input.overrides ? { overrides: input.overrides } : {}),
2004
2170
  ...(input.preserveFirstStart ? { preserveFirstStart: true } : {}),
2005
2171
  });
2172
+ // A bot made on the HQ Fleet Runtime comes back on it, on the release this CLI pins.
2173
+ const fleetInstallation = fleetEngine ? await installPinnedFleetLocalDistribution() : null;
2174
+ const fleetPython = fleetInstallation ? await ensureFleetPython() : null;
2175
+ if (fleetInstallation)
2176
+ config.engine = fleetInstallation.engine;
2006
2177
  writeBotConfig(dir, config);
2007
2178
  const scaffoldOpts = { memoryDir: config.memoryDir };
2008
2179
  const scaffold = config.workerSource === "worker"
2009
2180
  ? scaffoldBotMemory(input.hqRoot, name, undefined, scaffoldOpts)
2010
2181
  : scaffoldBotWorker(input.hqRoot, name, bot.agentUid, config.runtime, undefined, scaffoldOpts);
2182
+ if (fleetInstallation && fleetPython) {
2183
+ await bootstrapFleetLocalBot({
2184
+ agentUid: bot.agentUid,
2185
+ ownerUid: config.ownerUid,
2186
+ hqRoot: input.hqRoot,
2187
+ botDir: dir,
2188
+ installation: fleetInstallation,
2189
+ machineCredentials: botCredsPath(dir),
2190
+ systemPrompt: fleetBotSystemPrompt(name, config),
2191
+ provider: fleetProviderForRuntime(config.runtime, config.model),
2192
+ ownerCompanies: await fleetOwnerCompanies(),
2193
+ ...(input.preserveFirstStart
2194
+ ? {
2195
+ firstStart: {
2196
+ intro: introDmText(name, config.runtime, config.intro, { kind: effectiveBotKind(config), companies: effectiveBotCompanies(config) }),
2197
+ ...(config.kickoff ? { kickoff: config.kickoff } : {}),
2198
+ },
2199
+ }
2200
+ : {}),
2201
+ }, { python: fleetPython, start: false });
2202
+ }
2011
2203
  createBotLogger(dir)("info", `adopted ${name} (${bot.agentUid}) on ${localMachineId()} kind=${config.kind}${local ? "" : " (no saved settings; defaults)"} runtime=${config.runtime}`);
2012
2204
  let daemonMessage = null;
2013
- if (input.daemon && process.platform === "darwin") {
2205
+ if (!fleetInstallation && input.daemon && process.platform === "darwin") {
2014
2206
  daemonMessage = installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot: input.hqRoot })).message;
2015
2207
  }
2016
2208
  return {
@@ -2118,6 +2310,11 @@ async function repairLocalBot(name, api, bot, daemon) {
2118
2310
  region: DEFAULT_COGNITO.region,
2119
2311
  });
2120
2312
  createBotLogger(dir)("info", `re-issued machine credentials for ${name} (${bot.agentUid}) on ${localMachineId()}`);
2313
+ if (isFleetRuntimeBot(config)) {
2314
+ importFleetLocalMachineCredential(bot.agentUid, botCredsPath(dir));
2315
+ const restarted = controlFleetLocalBot(bot.agentUid, "restart").ok === true;
2316
+ return restarted ? "new credentials, HQ Fleet Runtime restarted" : "new credentials; the HQ Fleet Runtime did not restart (hq bot start " + name + ")";
2317
+ }
2121
2318
  if (daemon && process.platform === "darwin") {
2122
2319
  installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }));
2123
2320
  }
@@ -2308,7 +2505,9 @@ export function registerBotCommand(program) {
2308
2505
  .option("--kind <kind>", `personal (acts as you, everywhere you are) or company (acts as itself, as a member of its companies). Default: personal, or company when created from a company worker`)
2309
2506
  .option("--company <slug>", "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation", collectRepeated, [])
2310
2507
  .option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`)
2508
+ .option("--provider <provider>", `HQ Fleet provider: ${BOT_RUNTIMES.join("|")} (interactive Fleet creation prompts when omitted)`)
2311
2509
  .option("--hq-fleet-runtime", "Create this personal bot with the local HQ Fleet Runtime")
2510
+ .option("--legacy-runtime", "Run this bot with the model CLI even where the HQ Fleet Runtime is the default")
2312
2511
  .option("--model <model>", "Model override passed to the runtime CLI")
2313
2512
  .option("--effort <level>", `Thinking level: claude ${BOT_EFFORT_LEVELS.claude.join("|")}; codex ${BOT_EFFORT_LEVELS.codex.join("|")}; grok ${BOT_EFFORT_LEVELS.grok.join("|")} (default ${DEFAULT_BOT_EFFORT})`)
2314
2513
  .option("--no-auto-approve", "Keep the runtime's permission gates (default: everything pre-approved; a headless bot cannot answer prompts)")
@@ -2476,4 +2675,14 @@ export function registerBotCommand(program) {
2476
2675
  console.log(opts.json ? JSON.stringify({ ok: true, ...r }, null, 2) : r.message);
2477
2676
  })());
2478
2677
  }
2678
+ /**
2679
+ * The company a bot's hot-signal-sync job syncs: the bot's own company, else
2680
+ * the first company it serves. Null = none, and no job is registered. There is
2681
+ * no fallback to the HQ's active company: a personal bot syncs a company only
2682
+ * when it was given one explicitly (`--company`), so the job never writes into
2683
+ * a vault the bot does not belong to.
2684
+ */
2685
+ export function hotSignalSyncCompany(config) {
2686
+ return currentHotSignalSyncCompany(config);
2687
+ }
2479
2688
  //# sourceMappingURL=bot.js.map
@@ -41,6 +41,50 @@ export declare class SyncExitError extends Error {
41
41
  health?: "failed" | "succeeded";
42
42
  });
43
43
  }
44
+ /** Shown when the session expires during a sync and cannot be renewed. */
45
+ export declare const SYNC_SESSION_EXPIRED_MESSAGE = "Your HQ session expired during sync and could not be refreshed. Run `hq login`, then rerun the sync.";
46
+ /** Shown when the refresh failed for a network or server reason. */
47
+ export declare const SYNC_REFRESH_UNAVAILABLE_MESSAGE = "Could not refresh your HQ session during sync because the sign-in service did not respond. Check your connection, then rerun the sync.";
48
+ /** Shown when the start token carries no principal to bind the run to. */
49
+ export declare const SYNC_NO_IDENTITY_MESSAGE = "The HQ session token does not identify an account, so the sync did not start. Run `hq login`, then rerun the sync.";
50
+ /** Shown when the token would change to a different account mid-run. */
51
+ export declare const SYNC_IDENTITY_CHANGED_MESSAGE = "The signed-in HQ account changed while this sync was running, so the sync stopped instead of continuing as a different account. Rerun the sync as the account you want.";
52
+ /**
53
+ * The session token expired during a sync run and the non-interactive
54
+ * refresh failed. `kind` separates a rejected session (sign in again) from a
55
+ * network or server failure (retry later).
56
+ */
57
+ export declare class SyncSessionExpiredError extends Error {
58
+ readonly kind: "reauth" | "unavailable";
59
+ readonly reason?: string | undefined;
60
+ constructor(kind: "reauth" | "unavailable", reason?: string | undefined);
61
+ }
62
+ export interface SyncVaultConfigDeps {
63
+ /** Non-interactive token resolver: returns the cached token or refreshes it. */
64
+ ensureToken?: () => Promise<string>;
65
+ }
66
+ /** The last token failure recorded by a {@link buildSyncVaultConfig} config. */
67
+ export declare function syncAuthFailureFor(config: VaultServiceConfig | undefined): Error | null;
68
+ /**
69
+ * Vault config for a sync run whose token stays valid for the whole run.
70
+ *
71
+ * `initialToken` is resolved (and, on a TTY, logged in) before the run starts.
72
+ * The config carries a getter instead of that string, so every vault request
73
+ * re-reads the cached session and refreshes it when it is close to expiry.
74
+ * When a request still gets a 401, hq-cloud's VaultClient calls the getter
75
+ * once more and retries once if the token changed (no loop). A multi-company
76
+ * `hq sync pull --all` can outlast the one-hour Cognito access token; a
77
+ * captured string made every later target fail with 401 (feedback_3d3fab0b).
78
+ *
79
+ * The getter is bound to the principal of `initialToken`. If a later token
80
+ * belongs to a different principal (a mid-run `hq login` as another account,
81
+ * or the machine-identity fallback after a rejected person refresh), it throws
82
+ * an {@link IdentitySwapRefusal} and the run stops. It never continues as a
83
+ * different principal.
84
+ *
85
+ * Mid-run refreshes never open a browser.
86
+ */
87
+ export declare function buildSyncVaultConfig(initialToken: string, deps?: SyncVaultConfigDeps): VaultServiceConfig;
44
88
  /**
45
89
  * Preserve the vault's typed write-scope refusal at the CLI boundary. This is
46
90
  * deliberately duck-typed so hq-cli remains compatible while hq-cloud and the
@@ -50,6 +94,7 @@ export declare function formatSyncFailure(err: unknown, ctx?: {
50
94
  hqRoot?: string;
51
95
  cwd?: string;
52
96
  companySlug?: string;
97
+ vaultConfig?: VaultServiceConfig;
53
98
  }): string;
54
99
  /**
55
100
  * The `.hqinclude` line that admits a named path: its HQ-relative spelling,
@@ -259,6 +304,8 @@ export interface SyncCallResult {
259
304
  aborted: boolean;
260
305
  }
261
306
  export interface PullAllDeps {
307
+ /** Formats a per-target failure; the real path binds the run's vault config. */
308
+ formatFailure?: (err: unknown) => string;
262
309
  vaultClient: PullAllVaultClient;
263
310
  sync: (options: SyncCallOptions) => Promise<SyncCallResult>;
264
311
  /**
@@ -352,6 +399,8 @@ export interface ShareCallResult {
352
399
  planLimit?: PlanLimitRefusalSummary | null;
353
400
  }
354
401
  export interface PushAllDeps {
402
+ /** Formats a per-target failure; the real path binds the run's vault config. */
403
+ formatFailure?: (err: unknown) => string;
355
404
  vaultClient: PullAllVaultClient;
356
405
  share: (options: ShareCallOptions) => Promise<ShareCallResult>;
357
406
  beforeShare?: (input: {