@indigoai-us/hq-cli 5.345.179 → 5.345.180
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -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 +245 -13
- 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 +1 -1
- package/dist/lib/bot/fleet-local.js +82 -30
- 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,6 +56,7 @@ 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 { BOT_LOGIN_RUNTIMES, botAuthEnv, botRuntimeAuth, capacityAccountHome, describeBotAuth, formatBotAuth, isBotLoginRuntime, restoreBotSessions, setAsideBotSessions, signInBotRuntime, signOutBotRuntime, withRuntimeAuth, } from "../lib/bot/runtime-auth.js";
|
|
59
60
|
import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, runtimeCrashSinceStart, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, HQ_FLEET_RUNTIME_ENGINE_ID, authenticateFleetLocalBot, bootstrapFleetLocalBot, buildSystemPrompt, controlFleetLocalBot, ensureFleetPython, fleetLocalGatewayPid, fleetProviderForRuntime, importFleetLocalMachineCredential, fleetProviderNeedsSignIn, fleetDefaultCompanies, fleetOwnerCompanies, fleetRuntimeIsDefault, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
|
|
60
61
|
function collectRepeated(value, previous) {
|
|
61
62
|
return [...previous, value];
|
|
@@ -800,6 +801,11 @@ async function runBotCreateLocked(name, opts) {
|
|
|
800
801
|
// cost an identity).
|
|
801
802
|
const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
|
|
802
803
|
const { kind, companies } = resolveCreateKind(opts, worker);
|
|
804
|
+
if (requestedFleetRuntime && kind === "company" && companies.length !== 1) {
|
|
805
|
+
throw Object.assign(new Error("An HQ Fleet company bot must belong to exactly one company."), {
|
|
806
|
+
expected: true,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
803
809
|
// Companies with the fleet-default flag get their personal bots on
|
|
804
810
|
// the HQ Fleet Runtime; --legacy-runtime keeps the model-CLI runner.
|
|
805
811
|
const fleetCandidate = requestedFleetRuntime ||
|
|
@@ -808,12 +814,11 @@ async function runBotCreateLocked(name, opts) {
|
|
|
808
814
|
// and only where the company turned the flag on.
|
|
809
815
|
const flaggedCompanies = fleetCandidate && kind === "personal" ? await fleetDefaultCompanies(hqRoot) : [];
|
|
810
816
|
const useFleetRuntime = requestedFleetRuntime || (fleetCandidate && fleetRuntimeIsDefault(flaggedCompanies));
|
|
811
|
-
const ownerCompanies = useFleetRuntime
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
}
|
|
817
|
+
const ownerCompanies = useFleetRuntime
|
|
818
|
+
? kind === "personal"
|
|
819
|
+
? await fleetOwnerCompanies()
|
|
820
|
+
: companies
|
|
821
|
+
: [];
|
|
817
822
|
const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
|
|
818
823
|
if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
|
|
819
824
|
throw Object.assign(new Error(`The name ${RESERVED_SETUP_BOT_NAME} is reserved for HQ's assistant; create it with --worker ${RESERVED_SETUP_BOT_NAME}.`), { expected: true });
|
|
@@ -1387,14 +1392,16 @@ async function runBotStatus(nameArg, opts) {
|
|
|
1387
1392
|
const status = readBotStatus(dir);
|
|
1388
1393
|
const creds = readBotCredsIdentity(dir);
|
|
1389
1394
|
if (opts.json) {
|
|
1390
|
-
console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine } : {}), credsPresent: fleet || !!creds, status }, null, 2));
|
|
1395
|
+
console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine } : {}), credsPresent: fleet || !!creds, login: describeBotAuth(dir, config), status }, null, 2));
|
|
1391
1396
|
return;
|
|
1392
1397
|
}
|
|
1393
1398
|
console.log(`${chalk.bold(name)} ${row.agentUid}`);
|
|
1394
1399
|
if (fleet)
|
|
1395
1400
|
console.log(` engine: HQ Fleet Runtime ${config.engine?.version ?? ""} ${chalk.dim(`logs ~/.hq-fleet/profiles/${config.agentUid}/logs`)}`);
|
|
1396
1401
|
console.log(` kind: ${describeKind(row.kind, row.companies)}`);
|
|
1402
|
+
const login = describeBotAuth(dir, config);
|
|
1397
1403
|
console.log(` runtime: ${row.runtime} model ${row.model ?? "default"} thinking ${row.effort}${row.effortIsDefault ? " (default)" : ""}`);
|
|
1404
|
+
console.log(` login: ${login.login === "own" && !login.present ? chalk.red(formatBotAuth(login, name)) : formatBotAuth(login, name)}`);
|
|
1398
1405
|
if (row.workerId)
|
|
1399
1406
|
console.log(` worker: ${row.workerId}${row.companySlug ? ` (company ${row.companySlug})` : ""} ${chalk.dim(row.workerDir)}`);
|
|
1400
1407
|
console.log(` memory: ${row.memory} ${chalk.dim(row.memoryDir)}`);
|
|
@@ -1421,7 +1428,13 @@ async function runBotStatus(nameArg, opts) {
|
|
|
1421
1428
|
if (status.runtimeSignIn) {
|
|
1422
1429
|
const tool = runtimeDisplayName(status.runtimeSignIn.runtime);
|
|
1423
1430
|
const what = status.runtimeSignIn.state === "signed-out" ? `${tool} is not signed in for ${name}` : `${tool} needs to sign in again`;
|
|
1424
|
-
|
|
1431
|
+
const which = login.login === "default" ? "" : ` (${formatBotAuth(login, name)})`;
|
|
1432
|
+
const fix = login.login === "own"
|
|
1433
|
+
? `run \`hq bot login ${name} --runtime ${status.runtimeSignIn.runtime}\`, then \`hq bot restart ${name}\``
|
|
1434
|
+
: login.login === "account"
|
|
1435
|
+
? `run \`hq capacity login ${status.runtimeSignIn.runtime} --account ${login.account}\`, then \`hq bot restart ${name}\``
|
|
1436
|
+
: `sign in from HQ, or run \`${signInCommand(status.runtimeSignIn.runtime)}\`; ${name} checks again on its own`;
|
|
1437
|
+
console.log(` ${chalk.yellow(`${what}${which}`)} — ${fix}`);
|
|
1425
1438
|
}
|
|
1426
1439
|
else if (status.runtimeSignInCheck) {
|
|
1427
1440
|
const c = status.runtimeSignInCheck;
|
|
@@ -1701,9 +1714,9 @@ async function runBotRmLocked(validated, opts) {
|
|
|
1701
1714
|
console.log(chalk.dim(` HQ did not finish: ${incomplete.steps.join(", ")}`));
|
|
1702
1715
|
throw Object.assign(new Error(message), { expected: true });
|
|
1703
1716
|
}
|
|
1704
|
-
export async function runBotSet(nameArg, opts) {
|
|
1717
|
+
export async function runBotSet(nameArg, opts, deps = {}) {
|
|
1705
1718
|
const name = validateBotName(nameArg);
|
|
1706
|
-
await withBotLock(name, () => runBotSetLocked(name, opts), { command: "hq bot set" });
|
|
1719
|
+
await withBotLock(name, () => runBotSetLocked(name, opts, deps), { command: "hq bot set" });
|
|
1707
1720
|
}
|
|
1708
1721
|
/** A sentence for a refused name write: the status only, never the server's text. */
|
|
1709
1722
|
function nameWriteRefused(name, err) {
|
|
@@ -1730,12 +1743,48 @@ function nameWriteRefused(name, err) {
|
|
|
1730
1743
|
expected: true,
|
|
1731
1744
|
});
|
|
1732
1745
|
}
|
|
1733
|
-
|
|
1746
|
+
const defaultSetLifecycle = {
|
|
1747
|
+
isRunning: (dir) => {
|
|
1748
|
+
const s = readBotStatus(dir);
|
|
1749
|
+
return !!s && isPidAlive(s.pid) && s.state === "running";
|
|
1750
|
+
},
|
|
1751
|
+
stop: async (name, dir, config) => {
|
|
1752
|
+
await stopBot(name, dir, config, "silent");
|
|
1753
|
+
return waitFor(() => {
|
|
1754
|
+
const s = readBotStatus(dir);
|
|
1755
|
+
return !s || !isPidAlive(s.pid);
|
|
1756
|
+
}, 15_000);
|
|
1757
|
+
},
|
|
1758
|
+
start: async (name, dir, config) => (await startBot(name, dir, config, "silent")).running,
|
|
1759
|
+
};
|
|
1760
|
+
async function runBotSetLocked(nameArg, opts, deps = {}) {
|
|
1734
1761
|
const { name, dir, config } = requireBot(nameArg);
|
|
1735
1762
|
if (hasPromotionHold(dir))
|
|
1736
1763
|
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 });
|
|
1764
|
+
if (opts.runtime === undefined && opts.account === undefined && opts.model === undefined && opts.effort === undefined && opts.displayName === undefined) {
|
|
1765
|
+
throw Object.assign(new Error("Nothing to change: pass --runtime <runtime>, --account <id|default>, --model <id|default>, --effort <level|default> and/or --display-name <name>."), { expected: true });
|
|
1766
|
+
}
|
|
1767
|
+
if (opts.runtime !== undefined && !isBotRuntimeId(opts.runtime)) {
|
|
1768
|
+
throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
|
|
1769
|
+
}
|
|
1770
|
+
const targetRuntime = opts.runtime ?? config.runtime;
|
|
1771
|
+
const switching = targetRuntime !== config.runtime;
|
|
1772
|
+
if ((switching || opts.account !== undefined) && opts.displayName !== undefined) {
|
|
1773
|
+
throw Object.assign(new Error("Change --display-name on its own, not together with --runtime or --account."), { expected: true });
|
|
1774
|
+
}
|
|
1775
|
+
let runtimeAuth = config.runtimeAuth;
|
|
1776
|
+
if (opts.account !== undefined) {
|
|
1777
|
+
const id = opts.account.trim();
|
|
1778
|
+
let auth = null;
|
|
1779
|
+
if (id && id.toLowerCase() !== "default") {
|
|
1780
|
+
// Checked now so a typo never reaches a turn.
|
|
1781
|
+
capacityAccountHome(config.hqRoot, targetRuntime, id);
|
|
1782
|
+
auth = { mode: "account", account: id };
|
|
1783
|
+
}
|
|
1784
|
+
runtimeAuth = withRuntimeAuth(runtimeAuth, targetRuntime, auth);
|
|
1785
|
+
}
|
|
1786
|
+
if (switching || opts.account !== undefined) {
|
|
1787
|
+
return switchRuntimeOrAccount({ name, dir, config, targetRuntime, switching, runtimeAuth, opts, deps });
|
|
1739
1788
|
}
|
|
1740
1789
|
// Only the keys this command changes are written, onto a fresh read of
|
|
1741
1790
|
// bot.json: the running bot writes it too (introSentAt, kickoff state), and
|
|
@@ -1812,6 +1861,174 @@ async function runBotSetLocked(nameArg, opts) {
|
|
|
1812
1861
|
console.log(chalk.green(`${name} is now called ${displayName}`) + chalk.dim(`${where}${how}`));
|
|
1813
1862
|
}
|
|
1814
1863
|
}
|
|
1864
|
+
/**
|
|
1865
|
+
* `hq bot set --runtime/--account`: probe the new setup first (nothing changes
|
|
1866
|
+
* when it cannot answer), stop a running bot, back up bot.json and set the
|
|
1867
|
+
* old runtime's session files aside, save, and start it again. A bot that does
|
|
1868
|
+
* not come back is rolled back to its old settings and sessions.
|
|
1869
|
+
*/
|
|
1870
|
+
async function switchRuntimeOrAccount(input) {
|
|
1871
|
+
const { name, dir, config, targetRuntime, switching, runtimeAuth, opts, deps } = input;
|
|
1872
|
+
const life = deps.lifecycle ?? defaultSetLifecycle;
|
|
1873
|
+
const patch = { runtime: targetRuntime, runtimeAuth };
|
|
1874
|
+
// Model ids and thinking levels belong to one runtime: a switch resets what does not carry over.
|
|
1875
|
+
patch.model = opts.model !== undefined ? validateBotModel(opts.model) || undefined : switching ? undefined : config.model;
|
|
1876
|
+
patch.effort =
|
|
1877
|
+
opts.effort !== undefined
|
|
1878
|
+
? validateBotEffort(targetRuntime, opts.effort) || undefined
|
|
1879
|
+
: config.effort && BOT_EFFORT_LEVELS[targetRuntime].includes(config.effort)
|
|
1880
|
+
? config.effort
|
|
1881
|
+
: undefined;
|
|
1882
|
+
const candidate = { ...config, ...patch };
|
|
1883
|
+
const probeEnv = botAuthEnv(dir, candidate, process.env);
|
|
1884
|
+
const probe = await (deps.probe ?? probeBotRuntime)({
|
|
1885
|
+
runtime: targetRuntime,
|
|
1886
|
+
...(candidate.model ? { model: candidate.model } : {}),
|
|
1887
|
+
...(candidate.effort ? { effort: candidate.effort } : {}),
|
|
1888
|
+
env: probeEnv,
|
|
1889
|
+
});
|
|
1890
|
+
if (!probe.ok) {
|
|
1891
|
+
throw Object.assign(new Error(`${name} stays on ${config.runtime}: ${probe.detail} Nothing was changed.`), { expected: true });
|
|
1892
|
+
}
|
|
1893
|
+
const status = readBotStatus(dir);
|
|
1894
|
+
if (status && isPidAlive(status.pid) && selfStopProbe(status.pid)) {
|
|
1895
|
+
throw Object.assign(new Error(`${name} cannot change its runtime or login from inside one of its own turns. Run this from outside the bot. Nothing was changed.`), { expected: true });
|
|
1896
|
+
}
|
|
1897
|
+
const wasRunning = life.isRunning(dir);
|
|
1898
|
+
if (wasRunning && !(await life.stop(name, dir, config))) {
|
|
1899
|
+
throw Object.assign(new Error(`${name} did not stop, so nothing was changed. Try again, or run hq bot stop ${name} first.`), { expected: true });
|
|
1900
|
+
}
|
|
1901
|
+
let aside = null;
|
|
1902
|
+
// Only the settings this command changed go back, onto a fresh bot.json.
|
|
1903
|
+
const rollBack = () => {
|
|
1904
|
+
if (aside)
|
|
1905
|
+
restoreBotSessions(dir, aside);
|
|
1906
|
+
return patchBotConfig(dir, { runtime: config.runtime, runtimeAuth: config.runtimeAuth, model: config.model, effort: config.effort });
|
|
1907
|
+
};
|
|
1908
|
+
let next;
|
|
1909
|
+
let running = false;
|
|
1910
|
+
let rolledBack = false;
|
|
1911
|
+
try {
|
|
1912
|
+
if (switching)
|
|
1913
|
+
aside = setAsideBotSessions(dir, config.runtime);
|
|
1914
|
+
next = patchBotConfig(dir, patch);
|
|
1915
|
+
if (wasRunning)
|
|
1916
|
+
running = await life.start(name, dir, next);
|
|
1917
|
+
}
|
|
1918
|
+
catch (err) {
|
|
1919
|
+
// Any failure part-way puts the old settings and sessions back, and the bot back up.
|
|
1920
|
+
try {
|
|
1921
|
+
await life.stop(name, dir, config);
|
|
1922
|
+
}
|
|
1923
|
+
catch { /* best effort */ }
|
|
1924
|
+
const old = rollBack();
|
|
1925
|
+
if (wasRunning)
|
|
1926
|
+
await life.start(name, dir, old);
|
|
1927
|
+
throw err;
|
|
1928
|
+
}
|
|
1929
|
+
const login = describeBotAuth(dir, next);
|
|
1930
|
+
createBotLogger(dir)("info", `settings: runtime=${next.runtime} login=${login.login}${login.account ? ` account=${login.account}` : ""} model=${next.model ?? "default"} effort=${effectiveBotEffort(next)}${aside ? ` (sessions set aside in ${aside.archiveDir})` : ""}`);
|
|
1931
|
+
if (wasRunning && !running) {
|
|
1932
|
+
await life.stop(name, dir, next);
|
|
1933
|
+
const old = rollBack();
|
|
1934
|
+
rolledBack = true;
|
|
1935
|
+
running = await life.start(name, dir, old);
|
|
1936
|
+
createBotLogger(dir)("warn", `settings rolled back: ${name} did not start on ${next.runtime}`);
|
|
1937
|
+
}
|
|
1938
|
+
const result = {
|
|
1939
|
+
ok: !rolledBack,
|
|
1940
|
+
name,
|
|
1941
|
+
runtime: rolledBack ? config.runtime : next.runtime,
|
|
1942
|
+
previousRuntime: config.runtime,
|
|
1943
|
+
login: rolledBack ? describeBotAuth(dir, config) : login,
|
|
1944
|
+
model: rolledBack ? config.model ?? null : next.model ?? null,
|
|
1945
|
+
effort: effectiveBotEffort(rolledBack ? config : next),
|
|
1946
|
+
sessionsSetAside: aside && !rolledBack ? { dir: aside.archiveDir, files: aside.moved.length } : null,
|
|
1947
|
+
backup: aside ? path.join(aside.archiveDir, "bot.json") : null,
|
|
1948
|
+
running,
|
|
1949
|
+
restarted: wasRunning,
|
|
1950
|
+
rolledBack,
|
|
1951
|
+
};
|
|
1952
|
+
if (rolledBack)
|
|
1953
|
+
process.exitCode = 1;
|
|
1954
|
+
if (opts.json) {
|
|
1955
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
if (rolledBack) {
|
|
1959
|
+
console.log(chalk.red(`${name} did not start on ${next.runtime}, so it is back on ${config.runtime} with its old sessions. Check: hq bot logs ${name}`));
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
if (switching) {
|
|
1963
|
+
console.log(chalk.green(`${name} now runs on ${next.runtime} (${formatBotAuth(login, name)}), model ${next.model ?? "default"}, thinking ${result.effort}.`));
|
|
1964
|
+
console.log(chalk.dim(` ${aside.moved.length} ${config.runtime} session file(s) set aside in ${aside.archiveDir}; DM history and memory are unchanged.`));
|
|
1965
|
+
}
|
|
1966
|
+
else {
|
|
1967
|
+
console.log(chalk.green(`${name} now uses ${formatBotAuth(login, name)} for ${next.runtime}.`));
|
|
1968
|
+
}
|
|
1969
|
+
if (wasRunning)
|
|
1970
|
+
console.log(chalk.dim(running ? " restarted" : ` did not report running; check: hq bot logs ${name}`));
|
|
1971
|
+
}
|
|
1972
|
+
/** `hq bot login <name>`: sign the bot in with a login of its own. */
|
|
1973
|
+
export async function runBotLogin(nameArg, opts, deps = {}) {
|
|
1974
|
+
const name = validateBotName(nameArg);
|
|
1975
|
+
await withBotLock(name, async () => {
|
|
1976
|
+
const { dir, config } = requireBot(name);
|
|
1977
|
+
const runtime = opts.runtime ?? config.runtime;
|
|
1978
|
+
if (!isBotLoginRuntime(runtime)) {
|
|
1979
|
+
throw Object.assign(new Error(`hq bot login supports ${BOT_LOGIN_RUNTIMES.join(", ")}${opts.runtime ? "" : `; ${name} runs on ${runtime}, pass --runtime`}.`), { expected: true });
|
|
1980
|
+
}
|
|
1981
|
+
if (!(deps.interactive ?? process.stdin.isTTY === true)) {
|
|
1982
|
+
throw Object.assign(new Error("hq bot login needs an interactive terminal: the sign-in opens in your browser."), { expected: true });
|
|
1983
|
+
}
|
|
1984
|
+
signInBotRuntime(dir, runtime, deps.spawn ? { spawn: deps.spawn } : {});
|
|
1985
|
+
const next = patchBotConfig(dir, { runtimeAuth: withRuntimeAuth(config.runtimeAuth, runtime, { mode: "own" }) });
|
|
1986
|
+
createBotLogger(dir)("info", `login: own ${runtime} login set`);
|
|
1987
|
+
const login = describeBotAuth(dir, { ...next, runtime });
|
|
1988
|
+
if (opts.json)
|
|
1989
|
+
console.log(JSON.stringify({ ok: true, name, runtime, login }, null, 2));
|
|
1990
|
+
else
|
|
1991
|
+
console.log(chalk.green(`${name} now uses its own ${runtime} login${runtime === config.runtime ? " from its next message" : ` when it runs on ${runtime}`}.`));
|
|
1992
|
+
}, { command: "hq bot login" });
|
|
1993
|
+
}
|
|
1994
|
+
/** `hq bot logout <name>`: sign the bot's own login out and delete it. */
|
|
1995
|
+
export async function runBotLogout(nameArg, opts, deps = {}) {
|
|
1996
|
+
const name = validateBotName(nameArg);
|
|
1997
|
+
await withBotLock(name, async () => {
|
|
1998
|
+
const { dir, config } = requireBot(name);
|
|
1999
|
+
if (opts.runtime !== undefined && !isBotLoginRuntime(opts.runtime)) {
|
|
2000
|
+
throw Object.assign(new Error(`--runtime must be one of ${BOT_LOGIN_RUNTIMES.join(", ")}`), { expected: true });
|
|
2001
|
+
}
|
|
2002
|
+
const runtimes = opts.runtime ? [opts.runtime] : [...BOT_LOGIN_RUNTIMES];
|
|
2003
|
+
let runtimeAuth = config.runtimeAuth;
|
|
2004
|
+
const results = [];
|
|
2005
|
+
for (const rt of runtimes) {
|
|
2006
|
+
const r = signOutBotRuntime(dir, rt, deps.spawn ? { spawn: deps.spawn } : {});
|
|
2007
|
+
results.push({ runtime: rt, ...r });
|
|
2008
|
+
// A failed vendor logout keeps the folder and the setting, so the bot never drops to your login.
|
|
2009
|
+
if (r.removed && botRuntimeAuth({ runtime: rt, runtimeAuth }, rt)?.mode === "own")
|
|
2010
|
+
runtimeAuth = withRuntimeAuth(runtimeAuth, rt, null);
|
|
2011
|
+
}
|
|
2012
|
+
patchBotConfig(dir, { runtimeAuth });
|
|
2013
|
+
createBotLogger(dir)("info", `logout: ${results.map((r) => `${r.runtime}=${r.removed ? "removed" : "none"}`).join(" ")}`);
|
|
2014
|
+
if (opts.json) {
|
|
2015
|
+
console.log(JSON.stringify({ ok: true, name, results }, null, 2));
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
const failed = results.filter((r) => r.vendorLogout === "failed");
|
|
2019
|
+
for (const r of results) {
|
|
2020
|
+
if (r.removed)
|
|
2021
|
+
console.log(chalk.green(`${name}'s own ${r.runtime} login is signed out and removed.`));
|
|
2022
|
+
else if (r.vendorLogout === "failed")
|
|
2023
|
+
console.log(chalk.yellow(`${r.runtime} logout did not finish, so ${name}'s own ${r.runtime} login was kept. Make sure ${r.runtime} is installed, then run hq bot logout ${name} --runtime ${r.runtime} again.`));
|
|
2024
|
+
}
|
|
2025
|
+
if (failed.length)
|
|
2026
|
+
process.exitCode = 1;
|
|
2027
|
+
if (!results.some((r) => r.removed || r.vendorLogout === "failed"))
|
|
2028
|
+
console.log(`${name} has no login of its own${opts.runtime ? ` for ${opts.runtime}` : ""}.`);
|
|
2029
|
+
console.log(chalk.dim(` ${name} now uses your default sign-in for ${runtimes.join(" and ")} unless an account is set (hq bot set ${name} --account <id>).`));
|
|
2030
|
+
}, { command: "hq bot logout" });
|
|
2031
|
+
}
|
|
1815
2032
|
/** `hq bot probe`: exit code 0 when the runtime answered, 1 when it did not. */
|
|
1816
2033
|
export async function runBotProbe(opts, deps = {}) {
|
|
1817
2034
|
const saved = opts.bot ? requireBot(opts.bot).config : undefined;
|
|
@@ -1829,6 +2046,7 @@ export async function runBotProbe(opts, deps = {}) {
|
|
|
1829
2046
|
...(model ? { model } : {}),
|
|
1830
2047
|
...(effort ? { effort } : {}),
|
|
1831
2048
|
timeoutMs,
|
|
2049
|
+
...(opts.bot && saved ? { env: botAuthEnv(botDir(validateBotName(opts.bot)), { ...saved, runtime }, process.env) } : {}),
|
|
1832
2050
|
});
|
|
1833
2051
|
if (!result.ok)
|
|
1834
2052
|
process.exitCode = 1;
|
|
@@ -2622,11 +2840,25 @@ export function registerBotCommand(program) {
|
|
|
2622
2840
|
bot
|
|
2623
2841
|
.command("set <name>")
|
|
2624
2842
|
.description("Change the model and thinking level a bot uses (applies from its next message), or the name people see for it (saved in HQ; a running bot restarts to use it)")
|
|
2843
|
+
.option("--runtime <runtime>", `Switch the model CLI: ${BOT_RUNTIMES.join("|")}. Checked with one test turn first; the old runtime's sessions are set aside (backed up), DM history and memory stay`)
|
|
2844
|
+
.option("--account <id>", "Use this hq capacity account's login for the bot's runtime, or default for your own sign-in")
|
|
2625
2845
|
.option("--model <model>", "Model id for the runtime CLI, or default for the CLI's own default")
|
|
2626
2846
|
.option("--display-name <name>", `The name people see for the bot in HQ (up to ${BOT_DISPLAY_NAME_MAX_CHARS} chars). The handle stays <name>`)
|
|
2627
2847
|
.option("--effort <level>", `Thinking level (claude ${BOT_EFFORT_LEVELS.claude.join("|")}; codex ${BOT_EFFORT_LEVELS.codex.join("|")}; grok ${BOT_EFFORT_LEVELS.grok.join("|")}), or default (${DEFAULT_BOT_EFFORT})`)
|
|
2628
2848
|
.option("--json", "Print machine-readable JSON")
|
|
2629
2849
|
.action((name, opts) => wrap(() => runBotSet(name, opts))());
|
|
2850
|
+
bot
|
|
2851
|
+
.command("login <name>")
|
|
2852
|
+
.description("Give the bot a model login of its own, so it does not use yours: runs the runtime's own sign-in into ~/.hq/bots/<name>/auth/<runtime>")
|
|
2853
|
+
.option("--runtime <runtime>", `${BOT_LOGIN_RUNTIMES.join("|")} (default: the bot's runtime)`)
|
|
2854
|
+
.option("--json", "Print machine-readable JSON")
|
|
2855
|
+
.action((name, opts) => wrap(() => runBotLogin(name, opts))());
|
|
2856
|
+
bot
|
|
2857
|
+
.command("logout <name>")
|
|
2858
|
+
.description("Sign the bot's own model login out and delete it; the bot goes back to your default sign-in")
|
|
2859
|
+
.option("--runtime <runtime>", `${BOT_LOGIN_RUNTIMES.join("|")} (default: both)`)
|
|
2860
|
+
.option("--json", "Print machine-readable JSON")
|
|
2861
|
+
.action((name, opts) => wrap(() => runBotLogout(name, opts))());
|
|
2630
2862
|
bot
|
|
2631
2863
|
.command("set-model <name> <model>")
|
|
2632
2864
|
.description("Set the model this bot asks its runtime for (stored in the bot's own settings; the runtime's own config is never changed), or default to clear it")
|
|
@@ -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
|
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
3
3
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
4
4
|
import { suggestChannels, withChannelSuggestions } from "./channel-suggest.js";
|
|
5
|
-
import { callerFromToken, channelSlug, fetchChannelMembers, fetchChannels, matchChannelsByName, } from "./dm.js";
|
|
5
|
+
import { callerFromToken, channelSlug, fetchChannelMembers, fetchChannels, matchChannelsByName, matchContactByName, } from "./dm.js";
|
|
6
6
|
/**
|
|
7
7
|
* Human label for a channel row. Named channels (personal/company) show their
|
|
8
8
|
* name + a `hq dm <slug>` hint; unnamed group DMs are identified by their
|
|
@@ -294,6 +294,176 @@ async function runChannelsDelete(channelName, opts) {
|
|
|
294
294
|
process.exit(1);
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
+
// ─── Channel membership: add / remove ────────────────────────────────────────
|
|
298
|
+
/**
|
|
299
|
+
* Resolve the `<member>` argument of `hq channels add|remove` to a person or
|
|
300
|
+
* agent uid. Accepts a uid (`prs_…`, `agt_…`), an email, or a display name /
|
|
301
|
+
* @handle matched against the caller's DM contacts (which include the bots the
|
|
302
|
+
* caller owns). Pure → unit-testable. Returns an error sentence on no or
|
|
303
|
+
* ambiguous match so the caller never guesses. Feedback e165b286.
|
|
304
|
+
*/
|
|
305
|
+
export function resolveMemberTarget(contacts, raw) {
|
|
306
|
+
const target = raw.trim().replace(/^@/, "");
|
|
307
|
+
if (!target)
|
|
308
|
+
return { ok: false, error: "Name a member: a @handle, an email, or a prs_/agt_ uid." };
|
|
309
|
+
if (/^(prs|agt)_[A-Za-z0-9_-]+$/.test(target)) {
|
|
310
|
+
const known = contacts.find((c) => c.personUid === target);
|
|
311
|
+
return { ok: true, personUid: target, displayName: known?.displayName?.trim() || target };
|
|
312
|
+
}
|
|
313
|
+
if (target.includes("@")) {
|
|
314
|
+
const email = target.toLowerCase();
|
|
315
|
+
const hits = contacts.filter((c) => c.personUid && c.email?.trim().toLowerCase() === email);
|
|
316
|
+
const unique = [...new Map(hits.map((c) => [c.personUid, c])).values()];
|
|
317
|
+
if (unique.length === 1) {
|
|
318
|
+
return { ok: true, personUid: unique[0].personUid, displayName: unique[0].displayName?.trim() || email };
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
ok: false,
|
|
322
|
+
error: unique.length === 0
|
|
323
|
+
? `No contact with the email '${target}'. They must share a company with you; pass their prs_/agt_ uid to add them anyway.`
|
|
324
|
+
: `'${target}' matches ${unique.length} contacts. Pass the prs_/agt_ uid instead.`,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const match = matchContactByName(contacts, target);
|
|
328
|
+
if (match?.personUid) {
|
|
329
|
+
return { ok: true, personUid: match.personUid, displayName: match.displayName?.trim() || target };
|
|
330
|
+
}
|
|
331
|
+
const names = contacts
|
|
332
|
+
.map((c) => c.displayName?.trim())
|
|
333
|
+
.filter((n) => Boolean(n))
|
|
334
|
+
.sort((a, b) => a.localeCompare(b));
|
|
335
|
+
return {
|
|
336
|
+
ok: false,
|
|
337
|
+
error: `No contact matches '${raw.trim()}'. Contacts: ${names.slice(0, 12).join(", ") || "(none)"}` +
|
|
338
|
+
`${names.length > 12 ? ", …" : ""}. Use the exact name, an email, or a prs_/agt_ uid.`,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
async function fetchDmContacts(token) {
|
|
342
|
+
const res = await vaultApiFetch({ token, path: "/v1/notify/contacts" });
|
|
343
|
+
if (!res.ok)
|
|
344
|
+
return [];
|
|
345
|
+
const data = (await res.json().catch(() => ({})));
|
|
346
|
+
return Array.isArray(data.contacts) ? data.contacts : [];
|
|
347
|
+
}
|
|
348
|
+
async function resolveOneChannel(token, channelName) {
|
|
349
|
+
const channels = await fetchChannels(token);
|
|
350
|
+
const matches = matchChannelsByName(channels, channelName);
|
|
351
|
+
if (matches.length === 0) {
|
|
352
|
+
const message = `No channel named '${channelName}'. Run \`hq channels\` to see your channels.`;
|
|
353
|
+
throw new Error(withChannelSuggestions(message, suggestChannels(channels, channelName)));
|
|
354
|
+
}
|
|
355
|
+
if (matches.length > 1) {
|
|
356
|
+
const scopes = matches.map((match) => match.scope ?? "?").join(", ");
|
|
357
|
+
throw new Error(`'${channelName}' matches ${matches.length} channels (${scopes}). Name one of them.`);
|
|
358
|
+
}
|
|
359
|
+
return matches[0];
|
|
360
|
+
}
|
|
361
|
+
/** The server's refusal, as one plain sentence. */
|
|
362
|
+
export async function channelMembershipError(res, verb, fallback) {
|
|
363
|
+
const err = (await res.json().catch(() => ({})));
|
|
364
|
+
switch (err.code) {
|
|
365
|
+
case "CHANNEL_NOT_MEMBER":
|
|
366
|
+
return "Join the channel before adding members (so far you only browse it). Post once with `hq dm <channel> \"…\"`, or add yourself first with your own uid.";
|
|
367
|
+
case "INVITEE_NOT_FOUND":
|
|
368
|
+
return "That person or bot is not reachable from this company.";
|
|
369
|
+
case "CHANNEL_NOT_OWNER":
|
|
370
|
+
return verb === "add"
|
|
371
|
+
? "Only the channel owner or a company admin can add members."
|
|
372
|
+
: "Only the channel owner or a company admin can remove other members, and the channel owner cannot be removed.";
|
|
373
|
+
default: {
|
|
374
|
+
const detail = err.error ?? err.message;
|
|
375
|
+
return detail ? `${fallback} (HTTP ${res.status}): ${detail}` : `${fallback} (HTTP ${res.status})`;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* `hq channels add <channel> <member…>`: POST /v1/notify/channels/{id}/members
|
|
381
|
+
* once per member. Agents (`agt_…`) are allowed; this is how an owner adds a
|
|
382
|
+
* personal bot to a channel so everyone there can @mention it. The server
|
|
383
|
+
* reports `alreadyMember` on a repeat, which is printed, not treated as an error.
|
|
384
|
+
*/
|
|
385
|
+
async function runChannelsAdd(channelName, members, opts) {
|
|
386
|
+
let failed;
|
|
387
|
+
try {
|
|
388
|
+
const token = await ensureCognitoToken();
|
|
389
|
+
const channel = await resolveOneChannel(token, channelName);
|
|
390
|
+
const contacts = await fetchDmContacts(token);
|
|
391
|
+
const results = [];
|
|
392
|
+
for (const raw of members) {
|
|
393
|
+
const target = resolveMemberTarget(contacts, raw);
|
|
394
|
+
if (!target.ok) {
|
|
395
|
+
results.push({ member: raw, ok: false, error: target.error });
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const res = await vaultApiFetch({
|
|
399
|
+
token,
|
|
400
|
+
path: `/v1/notify/channels/${encodeURIComponent(channel.channelId)}/members`,
|
|
401
|
+
method: "POST",
|
|
402
|
+
body: { toPersonUid: target.personUid },
|
|
403
|
+
});
|
|
404
|
+
if (!res.ok) {
|
|
405
|
+
results.push({ member: raw, personUid: target.personUid, ok: false, error: await channelMembershipError(res, "add", "Could not add the member") });
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const data = (await res.json().catch(() => ({})));
|
|
409
|
+
results.push({ member: target.displayName, personUid: target.personUid, ok: true, alreadyMember: data.alreadyMember === true });
|
|
410
|
+
}
|
|
411
|
+
const label = channel.name ?? channelName;
|
|
412
|
+
if (opts.json) {
|
|
413
|
+
console.log(JSON.stringify({ channelId: channel.channelId, name: label, results }, null, 2));
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
for (const r of results) {
|
|
417
|
+
if (r.ok) {
|
|
418
|
+
console.log(r.alreadyMember ? chalk.dim(`${r.member} is already in #${label}.`) : chalk.green(`Added ${r.member} to #${label}.`));
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
console.error(chalk.red(`${r.member}: ${r.error}`));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
failed = results.some((r) => !r.ok);
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
429
|
+
failed = true;
|
|
430
|
+
}
|
|
431
|
+
if (failed)
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
/** `hq channels remove <channel> <member>`: DELETE /v1/notify/channels/{id}/members/{uid}. */
|
|
435
|
+
async function runChannelsRemove(channelName, member, opts) {
|
|
436
|
+
try {
|
|
437
|
+
const token = await ensureCognitoToken();
|
|
438
|
+
const channel = await resolveOneChannel(token, channelName);
|
|
439
|
+
// The member is in the channel, so resolve against the roster first, then contacts.
|
|
440
|
+
const roster = await fetchChannelMembers(token, channel.channelId);
|
|
441
|
+
const rosterContacts = roster.map((m) => ({ personUid: m.personUid, email: m.email, displayName: m.displayName }));
|
|
442
|
+
let target = resolveMemberTarget(rosterContacts, member);
|
|
443
|
+
if (!target.ok)
|
|
444
|
+
target = resolveMemberTarget(await fetchDmContacts(token), member);
|
|
445
|
+
if (!target.ok)
|
|
446
|
+
throw new Error(target.error);
|
|
447
|
+
const res = await vaultApiFetch({
|
|
448
|
+
token,
|
|
449
|
+
path: `/v1/notify/channels/${encodeURIComponent(channel.channelId)}/members/${encodeURIComponent(target.personUid)}`,
|
|
450
|
+
method: "DELETE",
|
|
451
|
+
});
|
|
452
|
+
if (!res.ok)
|
|
453
|
+
throw new Error(await channelMembershipError(res, "remove", "Could not remove the member"));
|
|
454
|
+
const label = channel.name ?? channelName;
|
|
455
|
+
if (opts.json) {
|
|
456
|
+
console.log(JSON.stringify({ channelId: channel.channelId, personUid: target.personUid, removed: true }, null, 2));
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
console.log(chalk.green(`Removed ${target.displayName} from #${label}.`));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
297
467
|
export function registerChannelsCommand(program) {
|
|
298
468
|
const channels = program
|
|
299
469
|
.command("channels")
|
|
@@ -329,5 +499,19 @@ export function registerChannelsCommand(program) {
|
|
|
329
499
|
.action(async (channelName, opts) => {
|
|
330
500
|
await runChannelsDelete(channelName, opts);
|
|
331
501
|
});
|
|
502
|
+
channels
|
|
503
|
+
.command("add <channel> <member...>")
|
|
504
|
+
.description("Add people or your own bots to a channel, by @handle, email, or prs_/agt_ uid.")
|
|
505
|
+
.option("--json", "Output the per-member results as JSON.")
|
|
506
|
+
.action(async (channelName, members, opts) => {
|
|
507
|
+
await runChannelsAdd(channelName, members, opts);
|
|
508
|
+
});
|
|
509
|
+
channels
|
|
510
|
+
.command("remove <channel> <member>")
|
|
511
|
+
.description("Remove a member or bot from a channel (owner or company admin), or leave it yourself.")
|
|
512
|
+
.option("--json", "Output the result as JSON.")
|
|
513
|
+
.action(async (channelName, member, opts) => {
|
|
514
|
+
await runChannelsRemove(channelName, member, opts);
|
|
515
|
+
});
|
|
332
516
|
}
|
|
333
517
|
//# sourceMappingURL=channels.js.map
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -147,7 +147,7 @@ export declare function checkpointInstruction(worker: Pick<ResolvedLaneWorker, "
|
|
|
147
147
|
export declare function composeWorkerBrief(worker: ResolvedLaneWorker, taskBrief: string, hqRoot: string): string;
|
|
148
148
|
/**
|
|
149
149
|
* A lane with no worker still carries the standing authorization paragraph,
|
|
150
|
-
* the foreground-only rule
|
|
150
|
+
* the foreground-only rule, the temp-home rule and the self-review rule.
|
|
151
151
|
*/
|
|
152
152
|
export declare function composeGeneralBrief(taskBrief: string, hqRoot: string): string;
|
|
153
153
|
export declare const DISPATCH_TERMINAL_REASON: "outpost_dispatch_failed";
|
package/dist/commands/lanes.js
CHANGED
|
@@ -108,7 +108,7 @@ import { resolveKrFreshnessForShow } from "../lib/lanes/kr-freshness.js";
|
|
|
108
108
|
import { ensureSessionHome } from "../lib/lanes/session-relink.js";
|
|
109
109
|
import { defaultProbe, validateProfileLaneModel, validateLaneModel, } from "../lib/lanes/models.js";
|
|
110
110
|
import { readLaneClaudeCredential } from "../lib/lanes/claude-credential.js";
|
|
111
|
-
import { parseCliModel, refuseOutpostSelfTarget, resolveLaneModel, FOREGROUND_ONLY_RULE, STANDING_AUTHORIZATION, tempHomeRule, spawnLaneWorker, spawnEnvelopeRepairRequest, laneRunDir, readSpawnManifest, stopLaneProcesses, } from "../lib/lanes/spawn.js";
|
|
111
|
+
import { parseCliModel, refuseOutpostSelfTarget, resolveLaneModel, FOREGROUND_ONLY_RULE, STANDING_AUTHORIZATION, selfReviewRule, tempHomeRule, spawnLaneWorker, spawnEnvelopeRepairRequest, laneRunDir, readSpawnManifest, stopLaneProcesses, } from "../lib/lanes/spawn.js";
|
|
112
112
|
import { handOffInstallHere } from "../lib/daemon/handoff.js";
|
|
113
113
|
export { spawnLaneWorker };
|
|
114
114
|
export function openPullRequestsForProject(hqRoot, metadata) {
|
|
@@ -3041,11 +3041,13 @@ export function composeWorkerBrief(worker, taskBrief, hqRoot) {
|
|
|
3041
3041
|
"",
|
|
3042
3042
|
tempHomeRule(hqRoot),
|
|
3043
3043
|
"",
|
|
3044
|
+
selfReviewRule(hqRoot),
|
|
3045
|
+
"",
|
|
3044
3046
|
].join("\n");
|
|
3045
3047
|
}
|
|
3046
3048
|
/**
|
|
3047
3049
|
* A lane with no worker still carries the standing authorization paragraph,
|
|
3048
|
-
* the foreground-only rule
|
|
3050
|
+
* the foreground-only rule, the temp-home rule and the self-review rule.
|
|
3049
3051
|
*/
|
|
3050
3052
|
export function composeGeneralBrief(taskBrief, hqRoot) {
|
|
3051
3053
|
return [
|
|
@@ -3058,6 +3060,8 @@ export function composeGeneralBrief(taskBrief, hqRoot) {
|
|
|
3058
3060
|
"",
|
|
3059
3061
|
tempHomeRule(hqRoot),
|
|
3060
3062
|
"",
|
|
3063
|
+
selfReviewRule(hqRoot),
|
|
3064
|
+
"",
|
|
3061
3065
|
].join("\n");
|
|
3062
3066
|
}
|
|
3063
3067
|
function parseMaxRuntimeSeconds(raw, workerId) {
|
package/dist/commands/mesh.js
CHANGED
|
@@ -3071,6 +3071,25 @@ export function registerMeshCommand(program, deps = {}) {
|
|
|
3071
3071
|
.description("Report daemon running/MQTT/spool/held/dead-letter/outbox/flush health")
|
|
3072
3072
|
.option("--json", "Print machine-readable JSON")
|
|
3073
3073
|
.action((opts) => wrap(() => runDaemonDoctorCmd(opts))());
|
|
3074
|
+
mesh
|
|
3075
|
+
.command("push-status")
|
|
3076
|
+
.description("Report whether the owner DM push signal is live or stale, with the last event time")
|
|
3077
|
+
.option("--json", "Print machine-readable JSON")
|
|
3078
|
+
.action((opts) => wrap(() => runMeshPushStatus(opts))());
|
|
3079
|
+
}
|
|
3080
|
+
async function runMeshPushStatus(opts) {
|
|
3081
|
+
const { ownerPushStatusForAccount, readOwnerInboxPushState, formatOwnerPushStatus } = await import("../lib/mesh/live/daemon/owner-inbox-push.js");
|
|
3082
|
+
const { describeCognitoTokenSource } = await import("../utils/cognito-session.js");
|
|
3083
|
+
const { actorKind } = describeCognitoTokenSource({ home: os.homedir(), env: process.env });
|
|
3084
|
+
const status = ownerPushStatusForAccount(readOwnerInboxPushState(workMeshRoot(undefined, process.env)), Date.now(), actorKind);
|
|
3085
|
+
if (opts.json) {
|
|
3086
|
+
console.log(JSON.stringify(status, null, 2));
|
|
3087
|
+
}
|
|
3088
|
+
else {
|
|
3089
|
+
console.log(formatOwnerPushStatus(status));
|
|
3090
|
+
}
|
|
3091
|
+
if (status.status === "stale")
|
|
3092
|
+
process.exitCode = 1;
|
|
3074
3093
|
}
|
|
3075
3094
|
async function runDaemonRun(opts = {}) {
|
|
3076
3095
|
const { runMeshDaemon } = await import("../lib/mesh/live/daemon/run.js");
|