@indigoai-us/hq-cli 5.345.179 → 5.345.181

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 (52) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/command-catalog.generated.d.ts +126 -0
  3. package/dist/command-catalog.generated.js +155 -0
  4. package/dist/commands/bot.d.ts +29 -1
  5. package/dist/commands/bot.js +282 -22
  6. package/dist/commands/channels.d.ts +18 -1
  7. package/dist/commands/channels.js +185 -1
  8. package/dist/commands/lanes.d.ts +1 -1
  9. package/dist/commands/lanes.js +6 -2
  10. package/dist/commands/mesh.js +19 -0
  11. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2285 -1743
  12. package/dist/hq-cloud-sync-runner/package.json +1 -1
  13. package/dist/lib/bot/config.d.ts +8 -0
  14. package/dist/lib/bot/fleet-local.d.ts +7 -3
  15. package/dist/lib/bot/fleet-local.js +234 -64
  16. package/dist/lib/bot/run.js +39 -2
  17. package/dist/lib/bot/runtime-auth.d.ts +119 -0
  18. package/dist/lib/bot/runtime-auth.js +251 -0
  19. package/dist/lib/daemon/catalog.d.ts +1 -0
  20. package/dist/lib/daemon/catalog.js +1 -1
  21. package/dist/lib/daemon/run.d.ts +2 -0
  22. package/dist/lib/daemon/run.js +64 -16
  23. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  24. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  25. package/dist/lib/hot-channels/adapters/meetings.js +3 -1
  26. package/dist/lib/hot-channels/contract.d.ts +6 -0
  27. package/dist/lib/hot-channels/ledger.js +17 -0
  28. package/dist/lib/hot-channels/loop.js +2 -0
  29. package/dist/lib/lanes/accounts.d.ts +1 -17
  30. package/dist/lib/lanes/accounts.js +5 -45
  31. package/dist/lib/lanes/provider-home.d.ts +24 -0
  32. package/dist/lib/lanes/provider-home.js +52 -0
  33. package/dist/lib/lanes/spawn.d.ts +12 -0
  34. package/dist/lib/lanes/spawn.js +27 -0
  35. package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
  36. package/dist/lib/mesh/live/daemon/doctor.js +13 -0
  37. package/dist/lib/mesh/live/daemon/owner-inbox-push.d.ts +94 -0
  38. package/dist/lib/mesh/live/daemon/owner-inbox-push.js +273 -0
  39. package/dist/lib/mesh/live/daemon/run.js +31 -0
  40. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +14 -0
  41. package/dist/lib/mesh/live/daemon/work-subscribe.js +210 -3
  42. package/dist/lib/mesh/live/paths.d.ts +2 -0
  43. package/dist/lib/mesh/live/paths.js +2 -0
  44. package/dist/lib/sync-core/flag.d.ts +1 -1
  45. package/dist/lib/sync-core/flag.js +1 -1
  46. package/dist/lib/sync-core/reconcile.d.ts +1 -1
  47. package/dist/lib/sync-core/reconcile.js +9 -7
  48. package/dist/lib/sync-core/release.d.ts +3 -3
  49. package/dist/lib/sync-core/release.js +4 -4
  50. package/dist/lib/sync-core/rollout-state.d.ts +1 -0
  51. package/dist/lib/sync-core/rollout-state.js +1 -0
  52. package/package.json +8 -8
@@ -56,7 +56,8 @@ import { isAncestorProcess } from "../lib/bot/self-process.js";
56
56
  import { runtimeDisplayName, signInCommand } from "../lib/bot/runtime-sign-in.js";
57
57
  import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
58
58
  import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.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";
59
+ import { BOT_LOGIN_RUNTIMES, botAuthEnv, botRuntimeAuth, capacityAccountHome, describeBotAuth, formatBotAuth, isBotLoginRuntime, restoreBotSessions, setAsideBotSessions, signInBotRuntime, signOutBotRuntime, withRuntimeAuth, } from "../lib/bot/runtime-auth.js";
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, fleetLocalProfileNeedsAdoption, fleetProviderForRuntime, importFleetLocalMachineCredential, fleetProviderNeedsSignIn, fleetDefaultCompanies, fleetOwnerCompanies, fleetRuntimeIsDefault, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
60
61
  function collectRepeated(value, previous) {
61
62
  return [...previous, value];
62
63
  }
@@ -152,7 +153,7 @@ function fleetAuthCommand(name, config) {
152
153
  return `hq bot auth ${name} --provider ${config.runtime === "grok" ? "grok" : config.runtime === "codex" ? "chatgpt" : "claude"}`;
153
154
  }
154
155
  function runFleetRuntimeControl(name, config, action, json) {
155
- if ((action === "start" || action === "restart") && config.fleetAuthPending === true) {
156
+ if ((action === "install" || action === "start" || action === "restart") && config.fleetAuthPending === true) {
156
157
  const authCommand = fleetAuthCommand(name, config);
157
158
  if (json)
158
159
  console.log(JSON.stringify({ ok: false, name, running: false, state: "stopped", needsSignIn: true, authCommand }, null, 2));
@@ -160,10 +161,10 @@ function runFleetRuntimeControl(name, config, action, json) {
160
161
  throw Object.assign(new Error(`Sign this bot in first: ${authCommand}`), { expected: true });
161
162
  return;
162
163
  }
163
- if (action === "start" || action === "restart") {
164
+ if (action === "install" || action === "start" || action === "restart") {
164
165
  refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
165
166
  }
166
- const installService = config.fleetServiceInstallPending === true && (action === "start" || action === "restart");
167
+ const installService = action === "install" || (config.fleetServiceInstallPending === true && (action === "start" || action === "restart"));
167
168
  const result = controlFleetLocalBot(config.agentUid, installService ? "install" : action);
168
169
  if (installService && result.ok)
169
170
  patchBotConfig(botDir(name), { fleetServiceInstallPending: false, enabled: true });
@@ -433,10 +434,11 @@ async function startFleetRuntimeBot(name, dir, config, output, extra) {
433
434
  throw Object.assign(new Error(`Sign this bot in first: ${authCommand}`), { expected: true });
434
435
  return { running: false, how: "waiting for sign-in" };
435
436
  }
437
+ const adopted = await adoptFleetRuntimeProfile(name, dir, config);
436
438
  refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
437
439
  const before = readBotStatus(dir);
438
440
  const alreadyRunning = !!before && before.state === "running" && isPidAlive(before.pid);
439
- const installService = config.fleetServiceInstallPending === true;
441
+ const installService = adopted || config.fleetServiceInstallPending === true;
440
442
  const result = controlFleetLocalBot(config.agentUid, installService ? "install" : alreadyRunning ? "restart" : "start");
441
443
  if (installService && result.ok)
442
444
  patchBotConfig(dir, { fleetServiceInstallPending: false, enabled: true });
@@ -458,6 +460,29 @@ async function startFleetRuntimeBot(name, dir, config, output, extra) {
458
460
  console.log(chalk.yellow(`${name} did not report running within 45s`) + chalk.dim(` logs: ~/.hq-fleet/profiles/${config.agentUid}/logs`));
459
461
  return { running, how };
460
462
  }
463
+ async function adoptFleetRuntimeProfile(name, dir, config) {
464
+ if (!fleetLocalProfileNeedsAdoption(config.agentUid))
465
+ return false;
466
+ const installation = await installPinnedFleetLocalDistribution();
467
+ const python = await ensureFleetPython();
468
+ const companyScope = effectiveBotKind(config) === "company"
469
+ ? effectiveBotCompanies(config)
470
+ : await fleetOwnerCompanies();
471
+ await bootstrapFleetLocalBot({
472
+ agentUid: config.agentUid,
473
+ ownerUid: config.ownerUid,
474
+ hqRoot: config.hqRoot,
475
+ botDir: dir,
476
+ installation,
477
+ systemPrompt: fleetBotSystemPrompt(name, config),
478
+ provider: fleetProviderForRuntime(config.runtime, config.model),
479
+ ownerCompanies: companyScope,
480
+ }, { python, start: false });
481
+ const credentials = botCredsPath(dir);
482
+ if (fs.existsSync(credentials))
483
+ importFleetLocalMachineCredential(config.agentUid, credentials);
484
+ return true;
485
+ }
461
486
  /**
462
487
  * `output`: "json" | "text" print a result; "silent" prints nothing — used
463
488
  * when stop is a step inside rm/restart, whose own JSON must be the only
@@ -800,6 +825,11 @@ async function runBotCreateLocked(name, opts) {
800
825
  // cost an identity).
801
826
  const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
802
827
  const { kind, companies } = resolveCreateKind(opts, worker);
828
+ if (requestedFleetRuntime && kind === "company" && companies.length !== 1) {
829
+ throw Object.assign(new Error("An HQ Fleet company bot must belong to exactly one company."), {
830
+ expected: true,
831
+ });
832
+ }
803
833
  // Companies with the fleet-default flag get their personal bots on
804
834
  // the HQ Fleet Runtime; --legacy-runtime keeps the model-CLI runner.
805
835
  const fleetCandidate = requestedFleetRuntime ||
@@ -808,12 +838,11 @@ async function runBotCreateLocked(name, opts) {
808
838
  // and only where the company turned the flag on.
809
839
  const flaggedCompanies = fleetCandidate && kind === "personal" ? await fleetDefaultCompanies(hqRoot) : [];
810
840
  const useFleetRuntime = requestedFleetRuntime || (fleetCandidate && fleetRuntimeIsDefault(flaggedCompanies));
811
- const ownerCompanies = useFleetRuntime && kind === "personal" ? await fleetOwnerCompanies() : [];
812
- if (useFleetRuntime && kind !== "personal") {
813
- throw Object.assign(new Error("--hq-fleet-runtime creates a personal bot. Drop --kind company and --company."), {
814
- expected: true,
815
- });
816
- }
841
+ const ownerCompanies = useFleetRuntime
842
+ ? kind === "personal"
843
+ ? await fleetOwnerCompanies()
844
+ : companies
845
+ : [];
817
846
  const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
818
847
  if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
819
848
  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 });
@@ -1387,14 +1416,16 @@ async function runBotStatus(nameArg, opts) {
1387
1416
  const status = readBotStatus(dir);
1388
1417
  const creds = readBotCredsIdentity(dir);
1389
1418
  if (opts.json) {
1390
- console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine } : {}), credsPresent: fleet || !!creds, status }, null, 2));
1419
+ console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine } : {}), credsPresent: fleet || !!creds, login: describeBotAuth(dir, config), status }, null, 2));
1391
1420
  return;
1392
1421
  }
1393
1422
  console.log(`${chalk.bold(name)} ${row.agentUid}`);
1394
1423
  if (fleet)
1395
1424
  console.log(` engine: HQ Fleet Runtime ${config.engine?.version ?? ""} ${chalk.dim(`logs ~/.hq-fleet/profiles/${config.agentUid}/logs`)}`);
1396
1425
  console.log(` kind: ${describeKind(row.kind, row.companies)}`);
1426
+ const login = describeBotAuth(dir, config);
1397
1427
  console.log(` runtime: ${row.runtime} model ${row.model ?? "default"} thinking ${row.effort}${row.effortIsDefault ? " (default)" : ""}`);
1428
+ console.log(` login: ${login.login === "own" && !login.present ? chalk.red(formatBotAuth(login, name)) : formatBotAuth(login, name)}`);
1398
1429
  if (row.workerId)
1399
1430
  console.log(` worker: ${row.workerId}${row.companySlug ? ` (company ${row.companySlug})` : ""} ${chalk.dim(row.workerDir)}`);
1400
1431
  console.log(` memory: ${row.memory} ${chalk.dim(row.memoryDir)}`);
@@ -1421,7 +1452,13 @@ async function runBotStatus(nameArg, opts) {
1421
1452
  if (status.runtimeSignIn) {
1422
1453
  const tool = runtimeDisplayName(status.runtimeSignIn.runtime);
1423
1454
  const what = status.runtimeSignIn.state === "signed-out" ? `${tool} is not signed in for ${name}` : `${tool} needs to sign in again`;
1424
- console.log(` ${chalk.yellow(what)} — sign in from HQ, or run \`${signInCommand(status.runtimeSignIn.runtime)}\`; ${name} checks again on its own`);
1455
+ const which = login.login === "default" ? "" : ` (${formatBotAuth(login, name)})`;
1456
+ const fix = login.login === "own"
1457
+ ? `run \`hq bot login ${name} --runtime ${status.runtimeSignIn.runtime}\`, then \`hq bot restart ${name}\``
1458
+ : login.login === "account"
1459
+ ? `run \`hq capacity login ${status.runtimeSignIn.runtime} --account ${login.account}\`, then \`hq bot restart ${name}\``
1460
+ : `sign in from HQ, or run \`${signInCommand(status.runtimeSignIn.runtime)}\`; ${name} checks again on its own`;
1461
+ console.log(` ${chalk.yellow(`${what}${which}`)} — ${fix}`);
1425
1462
  }
1426
1463
  else if (status.runtimeSignInCheck) {
1427
1464
  const c = status.runtimeSignInCheck;
@@ -1701,9 +1738,9 @@ async function runBotRmLocked(validated, opts) {
1701
1738
  console.log(chalk.dim(` HQ did not finish: ${incomplete.steps.join(", ")}`));
1702
1739
  throw Object.assign(new Error(message), { expected: true });
1703
1740
  }
1704
- export async function runBotSet(nameArg, opts) {
1741
+ export async function runBotSet(nameArg, opts, deps = {}) {
1705
1742
  const name = validateBotName(nameArg);
1706
- await withBotLock(name, () => runBotSetLocked(name, opts), { command: "hq bot set" });
1743
+ await withBotLock(name, () => runBotSetLocked(name, opts, deps), { command: "hq bot set" });
1707
1744
  }
1708
1745
  /** A sentence for a refused name write: the status only, never the server's text. */
1709
1746
  function nameWriteRefused(name, err) {
@@ -1730,12 +1767,48 @@ function nameWriteRefused(name, err) {
1730
1767
  expected: true,
1731
1768
  });
1732
1769
  }
1733
- async function runBotSetLocked(nameArg, opts) {
1770
+ const defaultSetLifecycle = {
1771
+ isRunning: (dir) => {
1772
+ const s = readBotStatus(dir);
1773
+ return !!s && isPidAlive(s.pid) && s.state === "running";
1774
+ },
1775
+ stop: async (name, dir, config) => {
1776
+ await stopBot(name, dir, config, "silent");
1777
+ return waitFor(() => {
1778
+ const s = readBotStatus(dir);
1779
+ return !s || !isPidAlive(s.pid);
1780
+ }, 15_000);
1781
+ },
1782
+ start: async (name, dir, config) => (await startBot(name, dir, config, "silent")).running,
1783
+ };
1784
+ async function runBotSetLocked(nameArg, opts, deps = {}) {
1734
1785
  const { name, dir, config } = requireBot(nameArg);
1735
1786
  if (hasPromotionHold(dir))
1736
1787
  throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
1737
- if (opts.model === undefined && opts.effort === undefined && opts.displayName === undefined) {
1738
- throw Object.assign(new Error("Nothing to change: pass --model <id|default>, --effort <level|default> and/or --display-name <name>."), { expected: true });
1788
+ if (opts.runtime === undefined && opts.account === undefined && opts.model === undefined && opts.effort === undefined && opts.displayName === undefined) {
1789
+ 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 });
1790
+ }
1791
+ if (opts.runtime !== undefined && !isBotRuntimeId(opts.runtime)) {
1792
+ throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
1793
+ }
1794
+ const targetRuntime = opts.runtime ?? config.runtime;
1795
+ const switching = targetRuntime !== config.runtime;
1796
+ if ((switching || opts.account !== undefined) && opts.displayName !== undefined) {
1797
+ throw Object.assign(new Error("Change --display-name on its own, not together with --runtime or --account."), { expected: true });
1798
+ }
1799
+ let runtimeAuth = config.runtimeAuth;
1800
+ if (opts.account !== undefined) {
1801
+ const id = opts.account.trim();
1802
+ let auth = null;
1803
+ if (id && id.toLowerCase() !== "default") {
1804
+ // Checked now so a typo never reaches a turn.
1805
+ capacityAccountHome(config.hqRoot, targetRuntime, id);
1806
+ auth = { mode: "account", account: id };
1807
+ }
1808
+ runtimeAuth = withRuntimeAuth(runtimeAuth, targetRuntime, auth);
1809
+ }
1810
+ if (switching || opts.account !== undefined) {
1811
+ return switchRuntimeOrAccount({ name, dir, config, targetRuntime, switching, runtimeAuth, opts, deps });
1739
1812
  }
1740
1813
  // Only the keys this command changes are written, onto a fresh read of
1741
1814
  // bot.json: the running bot writes it too (introSentAt, kickoff state), and
@@ -1812,6 +1885,174 @@ async function runBotSetLocked(nameArg, opts) {
1812
1885
  console.log(chalk.green(`${name} is now called ${displayName}`) + chalk.dim(`${where}${how}`));
1813
1886
  }
1814
1887
  }
1888
+ /**
1889
+ * `hq bot set --runtime/--account`: probe the new setup first (nothing changes
1890
+ * when it cannot answer), stop a running bot, back up bot.json and set the
1891
+ * old runtime's session files aside, save, and start it again. A bot that does
1892
+ * not come back is rolled back to its old settings and sessions.
1893
+ */
1894
+ async function switchRuntimeOrAccount(input) {
1895
+ const { name, dir, config, targetRuntime, switching, runtimeAuth, opts, deps } = input;
1896
+ const life = deps.lifecycle ?? defaultSetLifecycle;
1897
+ const patch = { runtime: targetRuntime, runtimeAuth };
1898
+ // Model ids and thinking levels belong to one runtime: a switch resets what does not carry over.
1899
+ patch.model = opts.model !== undefined ? validateBotModel(opts.model) || undefined : switching ? undefined : config.model;
1900
+ patch.effort =
1901
+ opts.effort !== undefined
1902
+ ? validateBotEffort(targetRuntime, opts.effort) || undefined
1903
+ : config.effort && BOT_EFFORT_LEVELS[targetRuntime].includes(config.effort)
1904
+ ? config.effort
1905
+ : undefined;
1906
+ const candidate = { ...config, ...patch };
1907
+ const probeEnv = botAuthEnv(dir, candidate, process.env);
1908
+ const probe = await (deps.probe ?? probeBotRuntime)({
1909
+ runtime: targetRuntime,
1910
+ ...(candidate.model ? { model: candidate.model } : {}),
1911
+ ...(candidate.effort ? { effort: candidate.effort } : {}),
1912
+ env: probeEnv,
1913
+ });
1914
+ if (!probe.ok) {
1915
+ throw Object.assign(new Error(`${name} stays on ${config.runtime}: ${probe.detail} Nothing was changed.`), { expected: true });
1916
+ }
1917
+ const status = readBotStatus(dir);
1918
+ if (status && isPidAlive(status.pid) && selfStopProbe(status.pid)) {
1919
+ 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 });
1920
+ }
1921
+ const wasRunning = life.isRunning(dir);
1922
+ if (wasRunning && !(await life.stop(name, dir, config))) {
1923
+ throw Object.assign(new Error(`${name} did not stop, so nothing was changed. Try again, or run hq bot stop ${name} first.`), { expected: true });
1924
+ }
1925
+ let aside = null;
1926
+ // Only the settings this command changed go back, onto a fresh bot.json.
1927
+ const rollBack = () => {
1928
+ if (aside)
1929
+ restoreBotSessions(dir, aside);
1930
+ return patchBotConfig(dir, { runtime: config.runtime, runtimeAuth: config.runtimeAuth, model: config.model, effort: config.effort });
1931
+ };
1932
+ let next;
1933
+ let running = false;
1934
+ let rolledBack = false;
1935
+ try {
1936
+ if (switching)
1937
+ aside = setAsideBotSessions(dir, config.runtime);
1938
+ next = patchBotConfig(dir, patch);
1939
+ if (wasRunning)
1940
+ running = await life.start(name, dir, next);
1941
+ }
1942
+ catch (err) {
1943
+ // Any failure part-way puts the old settings and sessions back, and the bot back up.
1944
+ try {
1945
+ await life.stop(name, dir, config);
1946
+ }
1947
+ catch { /* best effort */ }
1948
+ const old = rollBack();
1949
+ if (wasRunning)
1950
+ await life.start(name, dir, old);
1951
+ throw err;
1952
+ }
1953
+ const login = describeBotAuth(dir, next);
1954
+ 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})` : ""}`);
1955
+ if (wasRunning && !running) {
1956
+ await life.stop(name, dir, next);
1957
+ const old = rollBack();
1958
+ rolledBack = true;
1959
+ running = await life.start(name, dir, old);
1960
+ createBotLogger(dir)("warn", `settings rolled back: ${name} did not start on ${next.runtime}`);
1961
+ }
1962
+ const result = {
1963
+ ok: !rolledBack,
1964
+ name,
1965
+ runtime: rolledBack ? config.runtime : next.runtime,
1966
+ previousRuntime: config.runtime,
1967
+ login: rolledBack ? describeBotAuth(dir, config) : login,
1968
+ model: rolledBack ? config.model ?? null : next.model ?? null,
1969
+ effort: effectiveBotEffort(rolledBack ? config : next),
1970
+ sessionsSetAside: aside && !rolledBack ? { dir: aside.archiveDir, files: aside.moved.length } : null,
1971
+ backup: aside ? path.join(aside.archiveDir, "bot.json") : null,
1972
+ running,
1973
+ restarted: wasRunning,
1974
+ rolledBack,
1975
+ };
1976
+ if (rolledBack)
1977
+ process.exitCode = 1;
1978
+ if (opts.json) {
1979
+ console.log(JSON.stringify(result, null, 2));
1980
+ return;
1981
+ }
1982
+ if (rolledBack) {
1983
+ 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}`));
1984
+ return;
1985
+ }
1986
+ if (switching) {
1987
+ console.log(chalk.green(`${name} now runs on ${next.runtime} (${formatBotAuth(login, name)}), model ${next.model ?? "default"}, thinking ${result.effort}.`));
1988
+ console.log(chalk.dim(` ${aside.moved.length} ${config.runtime} session file(s) set aside in ${aside.archiveDir}; DM history and memory are unchanged.`));
1989
+ }
1990
+ else {
1991
+ console.log(chalk.green(`${name} now uses ${formatBotAuth(login, name)} for ${next.runtime}.`));
1992
+ }
1993
+ if (wasRunning)
1994
+ console.log(chalk.dim(running ? " restarted" : ` did not report running; check: hq bot logs ${name}`));
1995
+ }
1996
+ /** `hq bot login <name>`: sign the bot in with a login of its own. */
1997
+ export async function runBotLogin(nameArg, opts, deps = {}) {
1998
+ const name = validateBotName(nameArg);
1999
+ await withBotLock(name, async () => {
2000
+ const { dir, config } = requireBot(name);
2001
+ const runtime = opts.runtime ?? config.runtime;
2002
+ if (!isBotLoginRuntime(runtime)) {
2003
+ throw Object.assign(new Error(`hq bot login supports ${BOT_LOGIN_RUNTIMES.join(", ")}${opts.runtime ? "" : `; ${name} runs on ${runtime}, pass --runtime`}.`), { expected: true });
2004
+ }
2005
+ if (!(deps.interactive ?? process.stdin.isTTY === true)) {
2006
+ throw Object.assign(new Error("hq bot login needs an interactive terminal: the sign-in opens in your browser."), { expected: true });
2007
+ }
2008
+ signInBotRuntime(dir, runtime, deps.spawn ? { spawn: deps.spawn } : {});
2009
+ const next = patchBotConfig(dir, { runtimeAuth: withRuntimeAuth(config.runtimeAuth, runtime, { mode: "own" }) });
2010
+ createBotLogger(dir)("info", `login: own ${runtime} login set`);
2011
+ const login = describeBotAuth(dir, { ...next, runtime });
2012
+ if (opts.json)
2013
+ console.log(JSON.stringify({ ok: true, name, runtime, login }, null, 2));
2014
+ else
2015
+ console.log(chalk.green(`${name} now uses its own ${runtime} login${runtime === config.runtime ? " from its next message" : ` when it runs on ${runtime}`}.`));
2016
+ }, { command: "hq bot login" });
2017
+ }
2018
+ /** `hq bot logout <name>`: sign the bot's own login out and delete it. */
2019
+ export async function runBotLogout(nameArg, opts, deps = {}) {
2020
+ const name = validateBotName(nameArg);
2021
+ await withBotLock(name, async () => {
2022
+ const { dir, config } = requireBot(name);
2023
+ if (opts.runtime !== undefined && !isBotLoginRuntime(opts.runtime)) {
2024
+ throw Object.assign(new Error(`--runtime must be one of ${BOT_LOGIN_RUNTIMES.join(", ")}`), { expected: true });
2025
+ }
2026
+ const runtimes = opts.runtime ? [opts.runtime] : [...BOT_LOGIN_RUNTIMES];
2027
+ let runtimeAuth = config.runtimeAuth;
2028
+ const results = [];
2029
+ for (const rt of runtimes) {
2030
+ const r = signOutBotRuntime(dir, rt, deps.spawn ? { spawn: deps.spawn } : {});
2031
+ results.push({ runtime: rt, ...r });
2032
+ // A failed vendor logout keeps the folder and the setting, so the bot never drops to your login.
2033
+ if (r.removed && botRuntimeAuth({ runtime: rt, runtimeAuth }, rt)?.mode === "own")
2034
+ runtimeAuth = withRuntimeAuth(runtimeAuth, rt, null);
2035
+ }
2036
+ patchBotConfig(dir, { runtimeAuth });
2037
+ createBotLogger(dir)("info", `logout: ${results.map((r) => `${r.runtime}=${r.removed ? "removed" : "none"}`).join(" ")}`);
2038
+ if (opts.json) {
2039
+ console.log(JSON.stringify({ ok: true, name, results }, null, 2));
2040
+ return;
2041
+ }
2042
+ const failed = results.filter((r) => r.vendorLogout === "failed");
2043
+ for (const r of results) {
2044
+ if (r.removed)
2045
+ console.log(chalk.green(`${name}'s own ${r.runtime} login is signed out and removed.`));
2046
+ else if (r.vendorLogout === "failed")
2047
+ 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.`));
2048
+ }
2049
+ if (failed.length)
2050
+ process.exitCode = 1;
2051
+ if (!results.some((r) => r.removed || r.vendorLogout === "failed"))
2052
+ console.log(`${name} has no login of its own${opts.runtime ? ` for ${opts.runtime}` : ""}.`);
2053
+ 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>).`));
2054
+ }, { command: "hq bot logout" });
2055
+ }
1815
2056
  /** `hq bot probe`: exit code 0 when the runtime answered, 1 when it did not. */
1816
2057
  export async function runBotProbe(opts, deps = {}) {
1817
2058
  const saved = opts.bot ? requireBot(opts.bot).config : undefined;
@@ -1829,6 +2070,7 @@ export async function runBotProbe(opts, deps = {}) {
1829
2070
  ...(model ? { model } : {}),
1830
2071
  ...(effort ? { effort } : {}),
1831
2072
  timeoutMs,
2073
+ ...(opts.bot && saved ? { env: botAuthEnv(botDir(validateBotName(opts.bot)), { ...saved, runtime }, process.env) } : {}),
1832
2074
  });
1833
2075
  if (!result.ok)
1834
2076
  process.exitCode = 1;
@@ -1855,7 +2097,8 @@ export function botExists(name) {
1855
2097
  export async function runBotRestart(nameArg, opts = {}) {
1856
2098
  const b = requireBot(nameArg);
1857
2099
  if (isFleetRuntimeBot(b.config)) {
1858
- runFleetRuntimeControl(b.name, b.config, "restart", opts.json === true || opts.output === "json");
2100
+ const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
2101
+ runFleetRuntimeControl(b.name, b.config, adopted ? "install" : "restart", opts.json === true || opts.output === "json");
1859
2102
  return;
1860
2103
  }
1861
2104
  const running = readBotStatus(b.dir);
@@ -1876,10 +2119,11 @@ async function runBotAuth(nameArg, opts) {
1876
2119
  if (opts.apiKey && !apiKey) {
1877
2120
  throw Object.assign(new Error("No API key was received on stdin."), { expected: true });
1878
2121
  }
2122
+ const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
1879
2123
  const result = authenticateFleetLocalBot({
1880
2124
  agentUid: b.config.agentUid,
1881
2125
  provider: opts.provider,
1882
- ...(b.config.fleetAuthPending === true ? { installService: true } : {}),
2126
+ ...(adopted || b.config.fleetAuthPending === true ? { installService: true } : {}),
1883
2127
  ...(opts.model ? { model: opts.model } : {}),
1884
2128
  ...(apiKey ? { apiKey } : {}),
1885
2129
  ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
@@ -2311,8 +2555,9 @@ async function repairLocalBot(name, api, bot, daemon) {
2311
2555
  });
2312
2556
  createBotLogger(dir)("info", `re-issued machine credentials for ${name} (${bot.agentUid}) on ${localMachineId()}`);
2313
2557
  if (isFleetRuntimeBot(config)) {
2558
+ const adopted = await adoptFleetRuntimeProfile(name, dir, config);
2314
2559
  importFleetLocalMachineCredential(bot.agentUid, botCredsPath(dir));
2315
- const restarted = controlFleetLocalBot(bot.agentUid, "restart").ok === true;
2560
+ const restarted = controlFleetLocalBot(bot.agentUid, adopted ? "install" : "restart").ok === true;
2316
2561
  return restarted ? "new credentials, HQ Fleet Runtime restarted" : "new credentials; the HQ Fleet Runtime did not restart (hq bot start " + name + ")";
2317
2562
  }
2318
2563
  if (daemon && process.platform === "darwin") {
@@ -2564,7 +2809,8 @@ export function registerBotCommand(program) {
2564
2809
  .action((name, opts) => wrap(async () => {
2565
2810
  const b = requireBot(name);
2566
2811
  if (isFleetRuntimeBot(b.config)) {
2567
- runFleetRuntimeControl(b.name, b.config, "start", opts.json === true);
2812
+ const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
2813
+ runFleetRuntimeControl(b.name, b.config, adopted ? "install" : "start", opts.json === true);
2568
2814
  return;
2569
2815
  }
2570
2816
  await startBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
@@ -2622,11 +2868,25 @@ export function registerBotCommand(program) {
2622
2868
  bot
2623
2869
  .command("set <name>")
2624
2870
  .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)")
2871
+ .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`)
2872
+ .option("--account <id>", "Use this hq capacity account's login for the bot's runtime, or default for your own sign-in")
2625
2873
  .option("--model <model>", "Model id for the runtime CLI, or default for the CLI's own default")
2626
2874
  .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>`)
2627
2875
  .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})`)
2628
2876
  .option("--json", "Print machine-readable JSON")
2629
2877
  .action((name, opts) => wrap(() => runBotSet(name, opts))());
2878
+ bot
2879
+ .command("login <name>")
2880
+ .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>")
2881
+ .option("--runtime <runtime>", `${BOT_LOGIN_RUNTIMES.join("|")} (default: the bot's runtime)`)
2882
+ .option("--json", "Print machine-readable JSON")
2883
+ .action((name, opts) => wrap(() => runBotLogin(name, opts))());
2884
+ bot
2885
+ .command("logout <name>")
2886
+ .description("Sign the bot's own model login out and delete it; the bot goes back to your default sign-in")
2887
+ .option("--runtime <runtime>", `${BOT_LOGIN_RUNTIMES.join("|")} (default: both)`)
2888
+ .option("--json", "Print machine-readable JSON")
2889
+ .action((name, opts) => wrap(() => runBotLogout(name, opts))());
2630
2890
  bot
2631
2891
  .command("set-model <name> <model>")
2632
2892
  .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")
@@ -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