@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.
- package/CHANGELOG.md +23 -0
- package/dist/command-catalog.generated.d.ts +126 -0
- package/dist/command-catalog.generated.js +155 -0
- package/dist/commands/bot.d.ts +29 -1
- package/dist/commands/bot.js +282 -22
- package/dist/commands/channels.d.ts +18 -1
- package/dist/commands/channels.js +185 -1
- package/dist/commands/lanes.d.ts +1 -1
- package/dist/commands/lanes.js +6 -2
- package/dist/commands/mesh.js +19 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2285 -1743
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/config.d.ts +8 -0
- package/dist/lib/bot/fleet-local.d.ts +7 -3
- package/dist/lib/bot/fleet-local.js +234 -64
- package/dist/lib/bot/run.js +39 -2
- package/dist/lib/bot/runtime-auth.d.ts +119 -0
- package/dist/lib/bot/runtime-auth.js +251 -0
- package/dist/lib/daemon/catalog.d.ts +1 -0
- package/dist/lib/daemon/catalog.js +1 -1
- package/dist/lib/daemon/run.d.ts +2 -0
- package/dist/lib/daemon/run.js +64 -16
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/hot-channels/adapters/meetings.js +3 -1
- package/dist/lib/hot-channels/contract.d.ts +6 -0
- package/dist/lib/hot-channels/ledger.js +17 -0
- package/dist/lib/hot-channels/loop.js +2 -0
- package/dist/lib/lanes/accounts.d.ts +1 -17
- package/dist/lib/lanes/accounts.js +5 -45
- package/dist/lib/lanes/provider-home.d.ts +24 -0
- package/dist/lib/lanes/provider-home.js +52 -0
- package/dist/lib/lanes/spawn.d.ts +12 -0
- package/dist/lib/lanes/spawn.js +27 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/doctor.js +13 -0
- package/dist/lib/mesh/live/daemon/owner-inbox-push.d.ts +94 -0
- package/dist/lib/mesh/live/daemon/owner-inbox-push.js +273 -0
- package/dist/lib/mesh/live/daemon/run.js +31 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +14 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.js +210 -3
- package/dist/lib/mesh/live/paths.d.ts +2 -0
- package/dist/lib/mesh/live/paths.js +2 -0
- package/dist/lib/sync-core/flag.d.ts +1 -1
- package/dist/lib/sync-core/flag.js +1 -1
- package/dist/lib/sync-core/reconcile.d.ts +1 -1
- package/dist/lib/sync-core/reconcile.js +9 -7
- package/dist/lib/sync-core/release.d.ts +3 -3
- package/dist/lib/sync-core/release.js +4 -4
- package/dist/lib/sync-core/rollout-state.d.ts +1 -0
- package/dist/lib/sync-core/rollout-state.js +1 -0
- package/package.json +8 -8
package/dist/commands/bot.js
CHANGED
|
@@ -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 {
|
|
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
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|