@indigoai-us/hq-cli 5.345.176 → 5.345.178

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 CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.178] — 2026-10-29
6
+ - `hq dm send --attach` now uploads the bytes it read with their exact length, so a file changed after the size check no longer stalls and fails with `Upload failed (HTTP 400)`. A refused upload now names the storage error (for example `RequestTimeout`).
7
+ - `hq daemon disable mesh` now keeps `mesh` off on machines where it is not on by default,
8
+ such as an agent kit with `sync-core` turned on. In 5.345.176 the disable was not saved
9
+ there, so `sync-core` turned `mesh` back on; the 5.345.176 note that disabling `mesh`
10
+ still turns it off held only for machine types that run `mesh` by default.
11
+ - `hq run` now skips `# @optional` secrets the caller cannot read or that are missing, instead of failing the whole run. Required secrets still fail.
12
+ - The company-gated HQ Fleet default now supports new personal Claude, Codex and Grok bots. Codex and Grok bots stay stopped until their own provider sign-in succeeds; creation and status show the sign-in command. Cancelled sign-in can be retried without creating another bot. Existing bots and the explicit legacy runner are preserved.
13
+ - Where the company flag `agents.local-bots-hq-fleet-default` is on, `hq bot create`
14
+ makes personal Claude bots (the setup bot included) on the HQ Fleet Runtime.
15
+ These bots run on the owner's Claude Code login and need no sign-in of their own.
16
+ `--legacy-runtime` keeps the model-CLI runner, and Codex and Grok bots keep it
17
+ unless `--hq-fleet-runtime` is passed. `--hq-fleet-runtime` now takes
18
+ `--runtime claude|codex|grok` and `--model` to choose the bot's model login.
19
+ - HQ Fleet Runtime bots send their intro and run their kickoff on first start.
20
+ `hq bot status` and `hq bot list` read their status file.
21
+ `hq bot adopt` and `hq bot restore` bring them back on the runtime instead of as
22
+ Claude CLI bots. The bot's saved settings now keep the model login it uses.
23
+ - Pins hq-fleet-local v0.4.12. On that release a bot gives its owner the owner's
24
+ companies that turned the flag on, and gives a teammate only the ones the
25
+ teammate also belongs to. A tool call is blocked when an HQ check cannot run.
26
+ - The fleet-default flag is read for the owner's own active memberships, and a
27
+ bot is given only the companies that turned it on. `HQ_BOT_FLEET_DEFAULT=1`
28
+ skips the flag and gives the bot every active membership whose company
29
+ folder is on the computer.
30
+ - HQ Fleet Runtime bots install a smaller runtime (the mcp and anthropic extras
31
+ only), so creating one takes less time and disk space.
32
+ - `hq sync pull`, `push` and `now` (including `pull --all` and the first sync after setup) now refresh the HQ session when the access token expires during a long run and retry the rejected request once. Before, the sync read the token once at the start, so a run that outlasted the one-hour token failed every remaining target with 401 Unauthorized. If the session is rejected, the sync stops with "Run `hq login`, then rerun the sync". A network or server failure during the refresh gets its own message. The refreshed token must belong to the account that started the run: if a mid-run `hq login` or the machine-identity fallback changes the account, the sync stops instead of continuing as a different account. Feedback 3d3fab0b-9475-472d-91ce-9e7aa5a7443c.
33
+ - A sync run's account is now fixed once the run starts. If the session would switch to another account mid-run, including a fallback to this computer's machine credentials after a rejected refresh, the sync stops with an explanation instead of continuing as that account. A run whose starting token does not identify an account does not start.
34
+ - The daemon now records the newest change number the server announced for each vault, so the sync engine can skip background requests when it already has that change once it reads this.
35
+
36
+ ## [5.345.177] — 2026-10-28
37
+ - The new sync engine now installs as release v0.1.21, and company vaults move to full mode only on v0.1.21 or later. A remote delete the server refuses because the file changed now re-reads the server's changes within seconds instead of waiting for the hourly check. Deleting a file that is already gone on the server now counts as done instead of stopping the sync until someone repairs it, and a held remote delete no longer stops the rest of the sync. A file this machine just replaced is no longer downloaded again or turned into a conflict copy while the server catches up.
38
+
5
39
  ## [5.345.176] — 2026-10-28
6
40
  - `hq bot list --json` rows carry `runtimeCrash`. It is set when the bot's coding tool crashed before a turn started, the crash came after the bot last started, and no turn has worked since; otherwise it is null. It gives the time, exit code, how long the tool ran, and the first line of what the tool printed. A restart or a working turn clears it. HQ uses it to warn before sending the bot work it would fail, for example the Daily Brief's Create brief.
7
41
  - A local bot started by the HQ app on macOS now restarts itself, between turns, when an HQ app update replaces the app's launcher. Before this, macOS stopped letting the bot's coding tool read the Documents folder after the update, and every message failed within milliseconds ("Claude Code stopped with an error") until the bot was restarted by hand.
@@ -18787,10 +18787,18 @@ export declare const COMMAND_CATALOG: readonly [{
18787
18787
  readonly flags: "--runtime <runtime>";
18788
18788
  readonly description: "Model CLI to run the bot with: claude|codex|grok";
18789
18789
  readonly hidden: false;
18790
+ }, {
18791
+ readonly flags: "--provider <provider>";
18792
+ readonly description: "HQ Fleet provider: claude|codex|grok (interactive Fleet creation prompts when omitted)";
18793
+ readonly hidden: false;
18790
18794
  }, {
18791
18795
  readonly flags: "--hq-fleet-runtime";
18792
18796
  readonly description: "Create this personal bot with the local HQ Fleet Runtime";
18793
18797
  readonly hidden: false;
18798
+ }, {
18799
+ readonly flags: "--legacy-runtime";
18800
+ readonly description: "Run this bot with the model CLI even where the HQ Fleet Runtime is the default";
18801
+ readonly hidden: false;
18794
18802
  }, {
18795
18803
  readonly flags: "--model <model>";
18796
18804
  readonly description: "Model override passed to the runtime CLI";
@@ -22893,11 +22893,21 @@ export const COMMAND_CATALOG = [
22893
22893
  "description": "Model CLI to run the bot with: claude|codex|grok",
22894
22894
  "hidden": false
22895
22895
  },
22896
+ {
22897
+ "flags": "--provider <provider>",
22898
+ "description": "HQ Fleet provider: claude|codex|grok (interactive Fleet creation prompts when omitted)",
22899
+ "hidden": false
22900
+ },
22896
22901
  {
22897
22902
  "flags": "--hq-fleet-runtime",
22898
22903
  "description": "Create this personal bot with the local HQ Fleet Runtime",
22899
22904
  "hidden": false
22900
22905
  },
22906
+ {
22907
+ "flags": "--legacy-runtime",
22908
+ "description": "Run this bot with the model CLI even where the HQ Fleet Runtime is the default",
22909
+ "hidden": false
22910
+ },
22901
22911
  {
22902
22912
  "flags": "--model <model>",
22903
22913
  "description": "Model override passed to the runtime CLI",
@@ -12,6 +12,9 @@ export interface BotRow {
12
12
  agentUid: string;
13
13
  ownerUid: string;
14
14
  runtime: BotRuntimeId;
15
+ needsSignIn?: boolean;
16
+ authCommand?: string;
17
+ serviceInstallPending?: boolean;
15
18
  model?: string;
16
19
  /** Thinking level the bot runs with (its setting, or the default). */
17
20
  effort: string;
@@ -135,7 +138,9 @@ export declare function resolveCreateKind(opts: {
135
138
  };
136
139
  export interface BotCreateOptions {
137
140
  runtime?: string;
141
+ provider?: string;
138
142
  hqFleetRuntime?: boolean;
143
+ legacyRuntime?: boolean;
139
144
  model?: string;
140
145
  effort?: string;
141
146
  autoApprove?: boolean;
@@ -52,7 +52,7 @@ import { isAncestorProcess } from "../lib/bot/self-process.js";
52
52
  import { runtimeDisplayName, signInCommand } from "../lib/bot/runtime-sign-in.js";
53
53
  import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
54
54
  import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.js";
55
- import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, 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";
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, fleetProviderForRuntime, importFleetLocalMachineCredential, fleetProviderNeedsSignIn, fleetDefaultCompanies, fleetOwnerCompanies, fleetRuntimeIsDefault, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
56
56
  function collectRepeated(value, previous) {
57
57
  return [...previous, value];
58
58
  }
@@ -144,11 +144,25 @@ function fleetBotSystemPrompt(name, config, overrides = {}) {
144
144
  }),
145
145
  });
146
146
  }
147
+ function fleetAuthCommand(name, config) {
148
+ return `hq bot auth ${name} --provider ${config.runtime === "grok" ? "grok" : config.runtime === "codex" ? "chatgpt" : "claude"}`;
149
+ }
147
150
  function runFleetRuntimeControl(name, config, action, json) {
151
+ if ((action === "start" || action === "restart") && config.fleetAuthPending === true) {
152
+ const authCommand = fleetAuthCommand(name, config);
153
+ if (json)
154
+ console.log(JSON.stringify({ ok: false, name, running: false, state: "stopped", needsSignIn: true, authCommand }, null, 2));
155
+ else
156
+ throw Object.assign(new Error(`Sign this bot in first: ${authCommand}`), { expected: true });
157
+ return;
158
+ }
148
159
  if (action === "start" || action === "restart") {
149
160
  refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
150
161
  }
151
- const result = controlFleetLocalBot(config.agentUid, action);
162
+ const installService = config.fleetServiceInstallPending === true && (action === "start" || action === "restart");
163
+ const result = controlFleetLocalBot(config.agentUid, installService ? "install" : action);
164
+ if (installService && result.ok)
165
+ patchBotConfig(botDir(name), { fleetServiceInstallPending: false, enabled: true });
152
166
  if (json) {
153
167
  console.log(JSON.stringify({ name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
154
168
  return;
@@ -192,7 +206,7 @@ function rowFor(name, opts = {}) {
192
206
  const dir = botDir(name);
193
207
  const config = readBotConfig(dir);
194
208
  const fleet = isFleetRuntimeBot(config);
195
- const status = fleet ? null : readBotStatus(dir);
209
+ const status = readBotStatus(dir);
196
210
  const pid = fleet ? fleetLocalGatewayPid(config.agentUid) : (status?.pid ?? null);
197
211
  const processAlive = pid !== null && isPidAlive(pid) && (fleet || status?.state === "running");
198
212
  // Turns this bot has taken and not finished: what "thinking" means.
@@ -220,7 +234,7 @@ function rowFor(name, opts = {}) {
220
234
  model: config.model,
221
235
  effort: effectiveBotEffort(config),
222
236
  effortIsDefault: !config.effort,
223
- state: processAlive ? "running" : status?.state === "failed" ? "failed" : "stopped",
237
+ state: processAlive ? "running" : !fleet && status?.state === "failed" ? "failed" : "stopped",
224
238
  pid: processAlive ? pid : null,
225
239
  processAlive,
226
240
  hosting: hasActivePromotion(dir, config.agentUid, config.ownerUid) ? "cloud" : "local",
@@ -234,8 +248,10 @@ function rowFor(name, opts = {}) {
234
248
  busySince: processAlive ? (inflightTurns[0]?.startedAt ?? null) : null,
235
249
  runtimeSignIn: status?.runtimeSignIn ?? null,
236
250
  runtimeSignInCheck: status?.runtimeSignInCheck ?? null,
237
- runtimeSignedIn: runtimeSignedIn(status, processAlive),
238
- signInCommand: signInCommand(config.runtime),
251
+ runtimeSignedIn: config.fleetAuthPending === true ? false : runtimeSignedIn(status, processAlive),
252
+ ...(fleet && config.fleetAuthPending === true ? { needsSignIn: true, authCommand: fleetAuthCommand(name, config) } : {}),
253
+ ...(fleet && config.fleetServiceInstallPending === true ? { needsSignIn: false, serviceInstallPending: true } : {}),
254
+ signInCommand: fleet ? fleetAuthCommand(name, config) : signInCommand(config.runtime),
239
255
  runtimeCrash: processAlive ? runtimeCrashSinceStart(status) : null,
240
256
  daemonInstalled,
241
257
  daemonLoaded,
@@ -340,6 +356,8 @@ function stopProcess(dir) {
340
356
  async function startBot(name, dir, config, output, extra = {}) {
341
357
  if (hasPromotionHold(dir))
342
358
  throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
359
+ if (isFleetRuntimeBot(config))
360
+ return startFleetRuntimeBot(name, dir, config, output, extra);
343
361
  if (config.enabled === false)
344
362
  patchBotConfig(dir, { enabled: true });
345
363
  const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
@@ -401,6 +419,41 @@ async function startBot(name, dir, config, output, extra = {}) {
401
419
  }
402
420
  return { running, how };
403
421
  }
422
+ /** Start (or restart) an HQ Fleet Runtime bot under its own service, with fresh instructions. */
423
+ async function startFleetRuntimeBot(name, dir, config, output, extra) {
424
+ if (config.fleetAuthPending === true) {
425
+ const authCommand = fleetAuthCommand(name, config);
426
+ if (output === "json")
427
+ console.log(JSON.stringify({ ok: false, name, running: false, needsSignIn: true, authCommand }, null, 2));
428
+ else if (output !== "silent")
429
+ throw Object.assign(new Error(`Sign this bot in first: ${authCommand}`), { expected: true });
430
+ return { running: false, how: "waiting for sign-in" };
431
+ }
432
+ refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
433
+ const before = readBotStatus(dir);
434
+ const alreadyRunning = !!before && before.state === "running" && isPidAlive(before.pid);
435
+ const installService = config.fleetServiceInstallPending === true;
436
+ const result = controlFleetLocalBot(config.agentUid, installService ? "install" : alreadyRunning ? "restart" : "start");
437
+ if (installService && result.ok)
438
+ patchBotConfig(dir, { fleetServiceInstallPending: false, enabled: true });
439
+ const how = alreadyRunning ? "restarted by the HQ Fleet Runtime" : "started by the HQ Fleet Runtime";
440
+ const running = result.ok === true && (await waitFor(() => {
441
+ const s = readBotStatus(dir);
442
+ return !!s && s.state === "running" && isPidAlive(s.pid);
443
+ }, 45_000));
444
+ const status = readBotStatus(dir);
445
+ if (output === "silent")
446
+ return { running, how };
447
+ if (output === "json") {
448
+ console.log(JSON.stringify({ ok: running, name, ...extra, running, how, state: status?.state ?? "unknown", pid: status?.pid ?? null, lastError: null }, null, 2));
449
+ return { running, how };
450
+ }
451
+ if (running)
452
+ console.log(chalk.green(`${name} is running`) + chalk.dim(` (${how})`));
453
+ else
454
+ console.log(chalk.yellow(`${name} did not report running within 45s`) + chalk.dim(` logs: ~/.hq-fleet/profiles/${config.agentUid}/logs`));
455
+ return { running, how };
456
+ }
404
457
  /**
405
458
  * `output`: "json" | "text" print a result; "silent" prints nothing — used
406
459
  * when stop is a step inside rm/restart, whose own JSON must be the only
@@ -436,6 +489,14 @@ function requestSelfStopDrain(botPid) {
436
489
  async function stopBot(name, dir, config, output) {
437
490
  if (hasPromotionHold(dir))
438
491
  throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
492
+ if (isFleetRuntimeBot(config)) {
493
+ const result = controlFleetLocalBot(config.agentUid, "stop");
494
+ if (output === "json")
495
+ console.log(JSON.stringify({ ok: result.ok === true, name, stopped: result.ok === true, how: "stopped by the HQ Fleet Runtime" }, null, 2));
496
+ else if (output === "text")
497
+ console.log(chalk.green(`${name} stopped`));
498
+ return;
499
+ }
439
500
  const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
440
501
  const hqDaemon = daemonPaths();
441
502
  const before = readBotStatus(dir);
@@ -546,6 +607,28 @@ export function resolveCreateKind(opts, worker) {
546
607
  }
547
608
  return { kind, companies };
548
609
  }
610
+ async function promptFleetProvider() {
611
+ if (process.stdin.isTTY !== true || process.stderr.isTTY !== true) {
612
+ throw Object.assign(new Error("--provider is required with --hq-fleet-runtime in a non-interactive command (claude, codex, or grok)."), { expected: true });
613
+ }
614
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
615
+ try {
616
+ const answer = await new Promise((resolve) => {
617
+ rl.question("Provider: 1) Codex 2) Claude 3) Grok [1]: ", resolve);
618
+ });
619
+ const selected = answer.trim().toLowerCase();
620
+ if (selected === "" || selected === "1" || selected === "codex")
621
+ return "codex";
622
+ if (selected === "2" || selected === "claude")
623
+ return "claude";
624
+ if (selected === "3" || selected === "grok")
625
+ return "grok";
626
+ throw Object.assign(new Error("Provider must be claude, codex, or grok."), { expected: true });
627
+ }
628
+ finally {
629
+ rl.close();
630
+ }
631
+ }
549
632
  /**
550
633
  * `hq bot create`. Runs under the bot's lock, so a create racing an adopt,
551
634
  * restore, rm or set of the same bot waits for it instead of tripping over
@@ -664,11 +747,21 @@ async function finishExistingSetupBot(name, dir, config, displayName, opts) {
664
747
  console.log(chalk.yellow(`${name} did not report running within 15s`) + chalk.dim(` logs: hq bot logs ${name}`));
665
748
  }
666
749
  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 });
750
+ const requestedFleetRuntime = opts.hqFleetRuntime === true;
751
+ if (requestedFleetRuntime && opts.legacyRuntime) {
752
+ throw Object.assign(new Error("Pick one of --hq-fleet-runtime and --legacy-runtime."), { expected: true });
753
+ }
754
+ if (requestedFleetRuntime && (opts.effort || opts.lean)) {
755
+ throw Object.assign(new Error("--hq-fleet-runtime sets its own thinking level. Do not combine it with --effort or --lean."), { expected: true });
670
756
  }
671
- const runtime = opts.runtime ?? "claude";
757
+ if (opts.provider && !isBotRuntimeId(opts.provider)) {
758
+ throw Object.assign(new Error(`--provider must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
759
+ }
760
+ if (opts.provider && opts.runtime && opts.provider !== opts.runtime) {
761
+ throw Object.assign(new Error("--provider and --runtime must name the same provider when both are used."), { expected: true });
762
+ }
763
+ const selectedProvider = opts.provider ?? opts.runtime ?? (requestedFleetRuntime ? await promptFleetProvider() : undefined);
764
+ const runtime = selectedProvider ?? "claude";
672
765
  if (!isBotRuntimeId(runtime)) {
673
766
  throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
674
767
  }
@@ -703,6 +796,15 @@ async function runBotCreateLocked(name, opts) {
703
796
  // cost an identity).
704
797
  const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
705
798
  const { kind, companies } = resolveCreateKind(opts, worker);
799
+ // Companies with the fleet-default flag get their personal bots on
800
+ // the HQ Fleet Runtime; --legacy-runtime keeps the model-CLI runner.
801
+ const fleetCandidate = requestedFleetRuntime ||
802
+ (!opts.legacyRuntime && kind === "personal" && !opts.effort && !opts.lean);
803
+ // Owner turns on the bot may use only these companies: the owner's own,
804
+ // and only where the company turned the flag on.
805
+ const flaggedCompanies = fleetCandidate && kind === "personal" ? await fleetDefaultCompanies(hqRoot) : [];
806
+ const useFleetRuntime = requestedFleetRuntime || (fleetCandidate && fleetRuntimeIsDefault(flaggedCompanies));
807
+ const ownerCompanies = useFleetRuntime && kind === "personal" ? await fleetOwnerCompanies() : [];
706
808
  if (useFleetRuntime && kind !== "personal") {
707
809
  throw Object.assign(new Error("--hq-fleet-runtime creates a personal bot. Drop --kind company and --company."), {
708
810
  expected: true,
@@ -726,6 +828,8 @@ async function runBotCreateLocked(name, opts) {
726
828
  ? await installPinnedFleetLocalDistribution()
727
829
  : null;
728
830
  const fleetPython = fleetInstallation ? await ensureFleetPython() : null;
831
+ const fleetProvider = fleetInstallation ? fleetProviderForRuntime(runtime, model) : null;
832
+ const fleetAuthPending = fleetProvider !== null && fleetProviderNeedsSignIn(fleetProvider);
729
833
  const token = await ownerToken();
730
834
  const owner = await resolveCallerPersonUid(token, undefined, { command: "hq bot create" });
731
835
  if (!owner)
@@ -932,7 +1036,8 @@ async function runBotCreateLocked(name, opts) {
932
1036
  ...(kickoff ? { kickoff } : {}),
933
1037
  ...(opts.lean && runtime === "claude" ? { lean: true } : {}),
934
1038
  createdAt: new Date().toISOString(),
935
- enabled: true,
1039
+ enabled: !fleetAuthPending,
1040
+ ...(fleetAuthPending ? { fleetAuthPending: true } : {}),
936
1041
  };
937
1042
  writeBotConfig(dir, config);
938
1043
  // A company bot acts as itself, so it must be a member of each of its
@@ -971,7 +1076,13 @@ async function runBotCreateLocked(name, opts) {
971
1076
  installation: fleetInstallation,
972
1077
  machineCredentials: botCredsPath(dir),
973
1078
  systemPrompt: fleetBotSystemPrompt(name, config, { kind, companies }),
974
- }, { start: opts.start !== false, python: fleetPython })
1079
+ provider: fleetProvider,
1080
+ ownerCompanies,
1081
+ firstStart: {
1082
+ intro: introDmText(name, runtime, intro, { kind, companies }),
1083
+ ...(kickoff ? { kickoff } : {}),
1084
+ },
1085
+ }, { start: opts.start !== false && !fleetAuthPending, python: fleetPython })
975
1086
  : null;
976
1087
  const fleetProfile = fleetBootstrap?.profile ?? null;
977
1088
  const runtimeLabel = fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime;
@@ -998,6 +1109,7 @@ async function runBotCreateLocked(name, opts) {
998
1109
  kickoff: kickoff ?? null,
999
1110
  model: model ?? null,
1000
1111
  effort: fleetInstallation ? null : effort ?? DEFAULT_BOT_EFFORT,
1112
+ ...(fleetProvider ? { provider: fleetProvider.id, needsSignIn: fleetAuthPending, ...(fleetAuthPending ? { authCommand: fleetAuthCommand(name, config) } : {}) } : {}),
1001
1113
  };
1002
1114
  let daemonMessage = null;
1003
1115
  if (!fleetInstallation && opts.daemon !== false && process.platform === "darwin") {
@@ -1015,6 +1127,9 @@ async function runBotCreateLocked(name, opts) {
1015
1127
  console.log(chalk.dim(` state: ${dir}`));
1016
1128
  if (fleetProfile)
1017
1129
  console.log(chalk.dim(` profile: ${fleetProfile}`));
1130
+ if (fleetProvider && fleetProviderNeedsSignIn(fleetProvider)) {
1131
+ console.log(chalk.yellow(` sign it in before it can answer: hq bot auth ${name} --provider ${runtime === "grok" ? "grok" : "chatgpt"}`));
1132
+ }
1018
1133
  if (daemonMessage)
1019
1134
  console.log(chalk.dim(` ${daemonMessage}`));
1020
1135
  }
@@ -1232,25 +1347,26 @@ async function runBotList(opts) {
1232
1347
  }
1233
1348
  async function runBotStatus(nameArg, opts) {
1234
1349
  const { name, dir, config } = requireBot(nameArg);
1235
- if (isFleetRuntimeBot(config)) {
1236
- runFleetRuntimeControl(name, config, "status", opts.json === true);
1237
- return;
1238
- }
1350
+ const fleet = isFleetRuntimeBot(config);
1239
1351
  const row = rowFor(name);
1240
1352
  await decorateOnline([row]);
1241
1353
  const status = readBotStatus(dir);
1242
1354
  const creds = readBotCredsIdentity(dir);
1243
1355
  if (opts.json) {
1244
- console.log(JSON.stringify({ ...row, credsPresent: !!creds, status }, null, 2));
1356
+ console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine } : {}), credsPresent: fleet || !!creds, status }, null, 2));
1245
1357
  return;
1246
1358
  }
1247
1359
  console.log(`${chalk.bold(name)} ${row.agentUid}`);
1360
+ if (fleet)
1361
+ console.log(` engine: HQ Fleet Runtime ${config.engine?.version ?? ""} ${chalk.dim(`logs ~/.hq-fleet/profiles/${config.agentUid}/logs`)}`);
1248
1362
  console.log(` kind: ${describeKind(row.kind, row.companies)}`);
1249
1363
  console.log(` runtime: ${row.runtime} model ${row.model ?? "default"} thinking ${row.effort}${row.effortIsDefault ? " (default)" : ""}`);
1250
1364
  if (row.workerId)
1251
1365
  console.log(` worker: ${row.workerId}${row.companySlug ? ` (company ${row.companySlug})` : ""} ${chalk.dim(row.workerDir)}`);
1252
1366
  console.log(` memory: ${row.memory} ${chalk.dim(row.memoryDir)}`);
1253
1367
  console.log(` process: ${row.processAlive ? chalk.green(`running (pid ${row.pid})`) : chalk.gray(row.state)}`);
1368
+ if (row.needsSignIn)
1369
+ console.log(` sign-in: required (${row.authCommand})`);
1254
1370
  console.log(` HQ online: ${humanOnline(row)}${row.lastHeartbeatAt ? chalk.dim(` last heartbeat ${row.lastHeartbeatAt}`) : ""}${row.lastHeartbeatOk === false && status?.lastHeartbeatError ? chalk.dim(` (${status.lastHeartbeatError})`) : ""}`);
1255
1371
  if (status?.lastInboxPollOk === false) {
1256
1372
  console.log(` inbox: ${chalk.red("cannot receive")}${status.lastInboxError ? chalk.dim(` ${status.lastInboxError}`) : ""}`);
@@ -1263,7 +1379,7 @@ async function runBotStatus(nameArg, opts) {
1263
1379
  if (row.daemonNodeMissing) {
1264
1380
  console.log(` ${chalk.red(`node missing: ${row.daemonNodeMissing}`)} — launchd cannot start ${name}; fix with: hq bot start ${name}`);
1265
1381
  }
1266
- console.log(` creds: ${creds ? `present for ${creds.entityUid}` : chalk.red("missing")}`);
1382
+ console.log(` creds: ${fleet ? "in the system keychain" : creds ? `present for ${creds.entityUid}` : chalk.red("missing")}`);
1267
1383
  if (status) {
1268
1384
  console.log(` started: ${status.startedAt} restarts: ${status.restarts} replies: ${status.repliesSent}`);
1269
1385
  if (status.cliVersion)
@@ -1729,17 +1845,30 @@ async function runBotAuth(nameArg, opts) {
1729
1845
  const result = authenticateFleetLocalBot({
1730
1846
  agentUid: b.config.agentUid,
1731
1847
  provider: opts.provider,
1848
+ ...(b.config.fleetAuthPending === true ? { installService: true } : {}),
1732
1849
  ...(opts.model ? { model: opts.model } : {}),
1733
1850
  ...(apiKey ? { apiKey } : {}),
1734
1851
  ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
1735
1852
  ...(opts.transport ? { transport: opts.transport } : {}),
1736
1853
  openBrowser: opts.browser !== false,
1737
1854
  });
1855
+ const signedInRuntime = result.runtimeProvider === "anthropic" ? "claude" : result.runtimeProvider === "xai-oauth" ? "grok" : result.runtimeProvider === "openai-codex" ? "codex" : null;
1856
+ if (result.authenticated !== true)
1857
+ throw Object.assign(new Error("The provider has not completed sign-in."), { expected: true });
1858
+ patchBotConfig(b.dir, {
1859
+ ...(signedInRuntime ? { runtime: signedInRuntime } : {}),
1860
+ model: result.model,
1861
+ ...(b.config.fleetAuthPending === true ? { fleetAuthPending: false, fleetServiceInstallPending: !result.restarted, enabled: result.restarted } : {}),
1862
+ });
1863
+ const pid = fleetLocalGatewayPid(b.config.agentUid);
1864
+ const running = pid !== null && isPidAlive(pid);
1738
1865
  if (opts.json) {
1739
- console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
1866
+ 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
1867
  return;
1741
1868
  }
1742
- console.log(chalk.green(`${b.name} is signed in to ${result.provider} and running ${result.model}.`));
1869
+ console.log(running
1870
+ ? chalk.green(`${b.name} is signed in to ${result.provider} and running ${result.model}.`)
1871
+ : chalk.yellow(`${b.name} is signed in to ${result.provider}, but did not start. Run hq bot start ${b.name}.`));
1743
1872
  }
1744
1873
  // ── adopt / restore (bring a bot you own back to this computer) ──────────────
1745
1874
  /**
@@ -1993,6 +2122,9 @@ async function adoptRemoteBotLocked(input) {
1993
2122
  region: DEFAULT_COGNITO.region,
1994
2123
  });
1995
2124
  const local = parseBotLocalConfig(reissued.localConfig ?? bot.localConfig);
2125
+ const fleetEngine = local?.engine?.id === HQ_FLEET_RUNTIME_ENGINE_ID;
2126
+ if (fleetEngine && local && !local.runtime)
2127
+ local.runtime = "codex";
1996
2128
  const { config, notes } = configFromLocalConfig({
1997
2129
  name,
1998
2130
  agentUid: bot.agentUid,
@@ -2003,14 +2135,40 @@ async function adoptRemoteBotLocked(input) {
2003
2135
  ...(input.overrides ? { overrides: input.overrides } : {}),
2004
2136
  ...(input.preserveFirstStart ? { preserveFirstStart: true } : {}),
2005
2137
  });
2138
+ // A bot made on the HQ Fleet Runtime comes back on it, on the release this CLI pins.
2139
+ const fleetInstallation = fleetEngine ? await installPinnedFleetLocalDistribution() : null;
2140
+ const fleetPython = fleetInstallation ? await ensureFleetPython() : null;
2141
+ if (fleetInstallation)
2142
+ config.engine = fleetInstallation.engine;
2006
2143
  writeBotConfig(dir, config);
2007
2144
  const scaffoldOpts = { memoryDir: config.memoryDir };
2008
2145
  const scaffold = config.workerSource === "worker"
2009
2146
  ? scaffoldBotMemory(input.hqRoot, name, undefined, scaffoldOpts)
2010
2147
  : scaffoldBotWorker(input.hqRoot, name, bot.agentUid, config.runtime, undefined, scaffoldOpts);
2148
+ if (fleetInstallation && fleetPython) {
2149
+ await bootstrapFleetLocalBot({
2150
+ agentUid: bot.agentUid,
2151
+ ownerUid: config.ownerUid,
2152
+ hqRoot: input.hqRoot,
2153
+ botDir: dir,
2154
+ installation: fleetInstallation,
2155
+ machineCredentials: botCredsPath(dir),
2156
+ systemPrompt: fleetBotSystemPrompt(name, config),
2157
+ provider: fleetProviderForRuntime(config.runtime, config.model),
2158
+ ownerCompanies: await fleetOwnerCompanies(),
2159
+ ...(input.preserveFirstStart
2160
+ ? {
2161
+ firstStart: {
2162
+ intro: introDmText(name, config.runtime, config.intro, { kind: effectiveBotKind(config), companies: effectiveBotCompanies(config) }),
2163
+ ...(config.kickoff ? { kickoff: config.kickoff } : {}),
2164
+ },
2165
+ }
2166
+ : {}),
2167
+ }, { python: fleetPython, start: false });
2168
+ }
2011
2169
  createBotLogger(dir)("info", `adopted ${name} (${bot.agentUid}) on ${localMachineId()} kind=${config.kind}${local ? "" : " (no saved settings; defaults)"} runtime=${config.runtime}`);
2012
2170
  let daemonMessage = null;
2013
- if (input.daemon && process.platform === "darwin") {
2171
+ if (!fleetInstallation && input.daemon && process.platform === "darwin") {
2014
2172
  daemonMessage = installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot: input.hqRoot })).message;
2015
2173
  }
2016
2174
  return {
@@ -2118,6 +2276,11 @@ async function repairLocalBot(name, api, bot, daemon) {
2118
2276
  region: DEFAULT_COGNITO.region,
2119
2277
  });
2120
2278
  createBotLogger(dir)("info", `re-issued machine credentials for ${name} (${bot.agentUid}) on ${localMachineId()}`);
2279
+ if (isFleetRuntimeBot(config)) {
2280
+ importFleetLocalMachineCredential(bot.agentUid, botCredsPath(dir));
2281
+ const restarted = controlFleetLocalBot(bot.agentUid, "restart").ok === true;
2282
+ return restarted ? "new credentials, HQ Fleet Runtime restarted" : "new credentials; the HQ Fleet Runtime did not restart (hq bot start " + name + ")";
2283
+ }
2121
2284
  if (daemon && process.platform === "darwin") {
2122
2285
  installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }));
2123
2286
  }
@@ -2308,7 +2471,9 @@ export function registerBotCommand(program) {
2308
2471
  .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
2472
  .option("--company <slug>", "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation", collectRepeated, [])
2310
2473
  .option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`)
2474
+ .option("--provider <provider>", `HQ Fleet provider: ${BOT_RUNTIMES.join("|")} (interactive Fleet creation prompts when omitted)`)
2311
2475
  .option("--hq-fleet-runtime", "Create this personal bot with the local HQ Fleet Runtime")
2476
+ .option("--legacy-runtime", "Run this bot with the model CLI even where the HQ Fleet Runtime is the default")
2312
2477
  .option("--model <model>", "Model override passed to the runtime CLI")
2313
2478
  .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
2479
  .option("--no-auto-approve", "Keep the runtime's permission gates (default: everything pre-approved; a headless bot cannot answer prompts)")
@@ -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: {