@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.
|
|
1
|
+
{"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.105","bundleSha256":"f57aa12671cdfcf08ce39f11ce6885d6e52d5478f2ae77a75d38d79162194ce5","formatVersion":1}
|
package/dist/lib/bot/config.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* bot.json — non-secret per-bot configuration (local-bots US-003/US-005).
|
|
3
3
|
*/
|
|
4
|
+
import type { BotRuntimeAuthMap } from "./runtime-auth.js";
|
|
4
5
|
export declare const BOT_RUNTIMES: readonly ["claude", "codex", "grok"];
|
|
5
6
|
export type BotRuntimeId = (typeof BOT_RUNTIMES)[number];
|
|
6
7
|
export declare const HQ_FLEET_RUNTIME_ENGINE_ID: "hq-fleet-runtime";
|
|
@@ -167,6 +168,13 @@ export interface BotConfig {
|
|
|
167
168
|
* setup bots, which only need files and shell commands.
|
|
168
169
|
*/
|
|
169
170
|
lean?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Which model login each runtime uses (see runtime-auth.ts): unset = the
|
|
173
|
+
* owner's default sign-in; { mode: "own" } = the bot's own login under
|
|
174
|
+
* ~/.hq/bots/<name>/auth/<runtime>; { mode: "account", account } = a
|
|
175
|
+
* capacity account home. Names only, never a credential.
|
|
176
|
+
*/
|
|
177
|
+
runtimeAuth?: BotRuntimeAuthMap;
|
|
170
178
|
}
|
|
171
179
|
export declare function readBotConfig(dir: string): BotConfig | null;
|
|
172
180
|
export declare function writeBotConfig(dir: string, config: BotConfig): void;
|
|
@@ -133,7 +133,7 @@ export interface FleetLocalRemovalResult {
|
|
|
133
133
|
credentialsRemoved: number;
|
|
134
134
|
}
|
|
135
135
|
export declare const HQ_FLEET_LOCAL_RELEASE: FleetLocalReleasePin;
|
|
136
|
-
export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\
|
|
136
|
+
export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEARXr2I8RNcw2c0Ep77XkYGwylresXBFn8dm9pxN2Zq/U=\n-----END PUBLIC KEY-----\n";
|
|
137
137
|
export declare function fleetHome(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
138
138
|
export declare function verifyFleetLocalRelease(files: FleetLocalReleaseFiles, release?: FleetLocalReleasePin, publicKey?: string): void;
|
|
139
139
|
export declare function resolveFleetReleaseToken(env?: NodeJS.ProcessEnv): string;
|
|
@@ -4,6 +4,7 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { HQ_FLEET_RUNTIME_ENGINE_ID, } from "./config.js";
|
|
7
|
+
import { withBotLock } from "./lock.js";
|
|
7
8
|
import { ensureFleetPython, fleetUvEnv } from "./fleet-python.js";
|
|
8
9
|
import { ensureCognitoToken } from "../../utils/cognito-session.js";
|
|
9
10
|
import { vaultApiFetch } from "../../utils/vault-api.js";
|
|
@@ -35,18 +36,18 @@ export function fleetProviderNeedsSignIn(provider) {
|
|
|
35
36
|
}
|
|
36
37
|
export const HQ_FLEET_LOCAL_RELEASE = {
|
|
37
38
|
repository: "indigoai-us/hq-fleet-local",
|
|
38
|
-
tag: "v0.4.
|
|
39
|
-
version: "0.4.
|
|
40
|
-
artifact: "hq-fleet-local-0.4.
|
|
41
|
-
artifactSha256: "
|
|
39
|
+
tag: "v0.4.14",
|
|
40
|
+
version: "0.4.14",
|
|
41
|
+
artifact: "hq-fleet-local-0.4.14.zip",
|
|
42
|
+
artifactSha256: "9ff6ad4f7235988468e1913008c5650f5d064210078d6d20c407b17120310f47",
|
|
42
43
|
runtimeArtifact: "hq-fleet-runtime-abccac13bd9e.tar",
|
|
43
44
|
runtimeArtifactSha256: "29bcfe718ad7fbd3636afd00623772f2a25bc84c4f6f10d8d57f38e549d528d5",
|
|
44
|
-
manifestSha256: "
|
|
45
|
-
signatureSha256: "
|
|
45
|
+
manifestSha256: "88e71a733a5f57089764cbf9947fb88025c2263c5f3d62a2c6f5ab3635538871",
|
|
46
|
+
signatureSha256: "b892b54d6fcb2aaca86d049d7662fe5549a250d81877d6c529288e79ae1565d9",
|
|
46
47
|
runtimeCommit: "abccac13bd9ea593b24c8cd9c0ba28d5c6050cc9",
|
|
47
48
|
};
|
|
48
49
|
export const HQ_FLEET_LOCAL_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
49
|
-
|
|
50
|
+
MCowBQYDK2VwAyEARXr2I8RNcw2c0Ep77XkYGwylresXBFn8dm9pxN2Zq/U=
|
|
50
51
|
-----END PUBLIC KEY-----
|
|
51
52
|
`;
|
|
52
53
|
function digest(value) {
|
|
@@ -162,7 +163,7 @@ function trustedReleaseLink(url) {
|
|
|
162
163
|
return false;
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
|
-
async function downloadFleetLocalReleaseFromHq(release, fetcher, links) {
|
|
166
|
+
async function downloadFleetLocalReleaseFromHq(release, fetcher, links, cachedRuntime) {
|
|
166
167
|
const get = async (name, timeoutMs) => {
|
|
167
168
|
const url = links.get(name);
|
|
168
169
|
if (!url)
|
|
@@ -178,7 +179,7 @@ async function downloadFleetLocalReleaseFromHq(release, fetcher, links) {
|
|
|
178
179
|
};
|
|
179
180
|
const [archive, runtimeArchive, manifest, signature] = await Promise.all([
|
|
180
181
|
get(release.artifact, FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS),
|
|
181
|
-
get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
|
|
182
|
+
cachedRuntime ?? get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
|
|
182
183
|
get("release-manifest.json", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
183
184
|
get("release-manifest.sig", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
184
185
|
]);
|
|
@@ -193,7 +194,7 @@ async function responseBytes(response, label) {
|
|
|
193
194
|
export const FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
194
195
|
export const FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS = 5 * 60_000;
|
|
195
196
|
export const FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS = 20 * 60_000;
|
|
196
|
-
async function downloadFleetLocalRelease(release, fetcher, token) {
|
|
197
|
+
async function downloadFleetLocalRelease(release, fetcher, token, cachedRuntime) {
|
|
197
198
|
const headers = {
|
|
198
199
|
Accept: "application/vnd.github+json",
|
|
199
200
|
Authorization: `Bearer ${token}`,
|
|
@@ -223,7 +224,7 @@ async function downloadFleetLocalRelease(release, fetcher, token) {
|
|
|
223
224
|
};
|
|
224
225
|
const [archive, runtimeArchive, manifest, signature] = await Promise.all([
|
|
225
226
|
get(release.artifact, FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS),
|
|
226
|
-
get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
|
|
227
|
+
cachedRuntime ?? get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
|
|
227
228
|
get("release-manifest.json", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
228
229
|
get("release-manifest.sig", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
229
230
|
]);
|
|
@@ -274,6 +275,24 @@ function installFiles(target, files, release, publicKey) {
|
|
|
274
275
|
fs.rmSync(staging, { recursive: true, force: true });
|
|
275
276
|
}
|
|
276
277
|
}
|
|
278
|
+
function cachedRuntimeArchive(root, release) {
|
|
279
|
+
const distributions = path.join(root, "distributions", "hq-fleet-local");
|
|
280
|
+
if (!fs.existsSync(distributions))
|
|
281
|
+
return null;
|
|
282
|
+
for (const entry of fs.readdirSync(distributions, { withFileTypes: true })) {
|
|
283
|
+
if (!entry.isDirectory() || entry.name.startsWith("."))
|
|
284
|
+
continue;
|
|
285
|
+
const directory = path.join(distributions, entry.name);
|
|
286
|
+
for (const file of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
287
|
+
if (!file.isFile() || !file.name.startsWith("hq-fleet-runtime-") || !file.name.endsWith(".tar"))
|
|
288
|
+
continue;
|
|
289
|
+
const bytes = fs.readFileSync(path.join(directory, file.name));
|
|
290
|
+
if (digest(bytes) === release.runtimeArtifactSha256)
|
|
291
|
+
return bytes;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
277
296
|
export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
278
297
|
const release = deps.release ?? HQ_FLEET_LOCAL_RELEASE;
|
|
279
298
|
const publicKey = deps.publicKey ?? HQ_FLEET_LOCAL_PUBLIC_KEY;
|
|
@@ -286,9 +305,10 @@ export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
|
286
305
|
else {
|
|
287
306
|
const fetcher = deps.fetch ?? fetch;
|
|
288
307
|
const links = await (deps.hqReleaseLinks ?? fetchHqFleetReleaseLinks)(release);
|
|
308
|
+
const cachedRuntime = cachedRuntimeArchive(root, release);
|
|
289
309
|
const files = links
|
|
290
|
-
? await downloadFleetLocalReleaseFromHq(release, fetcher, links)
|
|
291
|
-
: await downloadFleetLocalRelease(release, fetcher, (deps.token ?? resolveFleetReleaseToken)());
|
|
310
|
+
? await downloadFleetLocalReleaseFromHq(release, fetcher, links, cachedRuntime)
|
|
311
|
+
: await downloadFleetLocalRelease(release, fetcher, (deps.token ?? resolveFleetReleaseToken)(), cachedRuntime);
|
|
292
312
|
verifyFleetLocalRelease(files, release, publicKey);
|
|
293
313
|
installFiles(dir, files, release, publicKey);
|
|
294
314
|
}
|
|
@@ -744,21 +764,30 @@ export function importFleetLocalMachineCredential(agentUid, machineCredentials,
|
|
|
744
764
|
], { input: machineValue, stdio: ["pipe", "ignore", "pipe"] }, "machine credential import");
|
|
745
765
|
fs.rmSync(machineCredentials, { force: true });
|
|
746
766
|
}
|
|
747
|
-
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
767
|
+
async function installSharedFleetRuntime(installation, root, python, platform, run) {
|
|
768
|
+
const identity = digest(Buffer.from(JSON.stringify({
|
|
769
|
+
version: installation.release.version,
|
|
770
|
+
distribution: installation.release.artifactSha256,
|
|
771
|
+
runtime: installation.release.runtimeArtifactSha256,
|
|
772
|
+
python: python.python,
|
|
773
|
+
platform,
|
|
774
|
+
extras: ["mcp", "anthropic"],
|
|
775
|
+
})));
|
|
776
|
+
const shared = path.join(root, "runtimes", identity);
|
|
777
|
+
return withBotLock(identity, async () => {
|
|
778
|
+
const marker = path.join(shared, "ready.json");
|
|
779
|
+
const sources = path.join(shared, "sources");
|
|
780
|
+
const distributionSource = path.join(sources, "distribution");
|
|
781
|
+
const runtimeSource = path.join(sources, "runtime");
|
|
782
|
+
const runtimeDir = path.join(shared, "runtime");
|
|
783
|
+
const executables = ["python", "hq-fleet", "hq-fleet-local"];
|
|
784
|
+
if (fs.existsSync(marker)) {
|
|
785
|
+
if (!executables.every((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)))) {
|
|
786
|
+
throw expectedError("The shared HQ Fleet Runtime is incomplete. Reinstall it before creating another bot.");
|
|
787
|
+
}
|
|
788
|
+
return shared;
|
|
789
|
+
}
|
|
790
|
+
fs.rmSync(shared, { recursive: true, force: true });
|
|
762
791
|
fs.mkdirSync(distributionSource, { recursive: true, mode: 0o700 });
|
|
763
792
|
fs.mkdirSync(runtimeSource, { recursive: true, mode: 0o700 });
|
|
764
793
|
const uvEnv = fleetUvEnv(python.pythonDir);
|
|
@@ -766,14 +795,14 @@ export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
|
766
795
|
"-m",
|
|
767
796
|
"zipfile",
|
|
768
797
|
"-e",
|
|
769
|
-
|
|
798
|
+
installation.archive,
|
|
770
799
|
distributionSource,
|
|
771
800
|
], { stdio: "ignore" }, "distribution extraction");
|
|
772
801
|
checkedRun(run, python.python, [
|
|
773
802
|
"-m",
|
|
774
803
|
"tarfile",
|
|
775
804
|
"-e",
|
|
776
|
-
|
|
805
|
+
installation.runtimeArchive,
|
|
777
806
|
runtimeSource,
|
|
778
807
|
], { stdio: "ignore" }, "runtime extraction");
|
|
779
808
|
checkedRun(run, python.uv, [
|
|
@@ -794,6 +823,29 @@ export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
|
794
823
|
`${runtimeSource}[mcp,anthropic]`,
|
|
795
824
|
distributionSource,
|
|
796
825
|
], { stdio: ["ignore", 2, 2], env: uvEnv }, "package installation");
|
|
826
|
+
if (!executables.every((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)))) {
|
|
827
|
+
throw expectedError("HQ Fleet Local installed without its runtime executables.");
|
|
828
|
+
}
|
|
829
|
+
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: installation.engine })}\n`, { mode: 0o600 });
|
|
830
|
+
return shared;
|
|
831
|
+
}, { root: path.join(root, "runtimes"), command: "HQ Fleet Runtime installation", waitMs: 20 * 60_000, staleMs: 2 * 60 * 60_000 });
|
|
832
|
+
}
|
|
833
|
+
export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
834
|
+
const run = deps.run ?? execFileSync;
|
|
835
|
+
const platform = deps.platform ?? process.platform;
|
|
836
|
+
const root = deps.fleetHome ?? fleetHome();
|
|
837
|
+
const python = deps.python ?? await ensureFleetPython({ fleetHome: root, platform, run });
|
|
838
|
+
const profileDir = profileRoot(input.agentUid, root);
|
|
839
|
+
const runtimeDir = path.join(profileDir, "runtime");
|
|
840
|
+
const marker = path.join(profileDir, "bootstrap.json");
|
|
841
|
+
fs.mkdirSync(profileDir, { recursive: true, mode: 0o700 });
|
|
842
|
+
if (!fs.existsSync(marker)) {
|
|
843
|
+
const shared = await installSharedFleetRuntime(input.installation, root, python, platform, run);
|
|
844
|
+
for (const name of ["sources", "runtime"]) {
|
|
845
|
+
const target = path.join(profileDir, name);
|
|
846
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
847
|
+
fs.symlinkSync(path.join(shared, name), target, platform === "win32" ? "junction" : "dir");
|
|
848
|
+
}
|
|
797
849
|
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: input.installation.engine }, null, 2)}\n`, { mode: 0o600 });
|
|
798
850
|
}
|
|
799
851
|
const { systemPrompt, ...profileInput } = input;
|
package/dist/lib/bot/run.js
CHANGED
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
*
|
|
47
47
|
* Everything with a side effect is injectable so the loop is unit-testable.
|
|
48
48
|
*/
|
|
49
|
+
import { botAuthEnv, describeBotAuth, formatBotAuth } from "./runtime-auth.js";
|
|
49
50
|
import { ownerContextBlock } from "./owner-context.js";
|
|
50
51
|
import { randomUUID } from "node:crypto";
|
|
51
52
|
import { hasPromotionHold } from "./promotion-hold.js";
|
|
@@ -558,6 +559,12 @@ export async function runBot(deps) {
|
|
|
558
559
|
setStatus({ ...fallback(), pid, state: "starting", canDrainForPromotion: false, runtimeSignIn: null, cliVersion });
|
|
559
560
|
log("info", `starting ${config.name} on hq-cli ${cliVersion} (${config.runtime}, ${kind} bot${companies.length ? ` of ${companies.join(", ")}` : ""}) as ${config.agentUid} for ${config.ownerUid}`);
|
|
560
561
|
log("info", `${runtime.id} binary: ${describeRuntimeBinary(runtime, turnEnv(process.env, kind, config.agentUid, companySlug), turnCwd)}`);
|
|
562
|
+
{
|
|
563
|
+
const login = describeBotAuth(dir, config);
|
|
564
|
+
log("info", `${runtime.id} login: ${formatBotAuth(login, config.name)}`);
|
|
565
|
+
if (login.login === "own" && !login.present)
|
|
566
|
+
setStatus({ lastError: `own ${runtime.id} login missing: run hq bot login ${config.name} --runtime ${runtime.id}` });
|
|
567
|
+
}
|
|
561
568
|
deps.cliUpdate?.startup(log);
|
|
562
569
|
try {
|
|
563
570
|
const record = await api.getAgent(config.agentUid);
|
|
@@ -1048,7 +1055,15 @@ export async function runBot(deps) {
|
|
|
1048
1055
|
// resets). Claude gets the full hook set via its payload session id;
|
|
1049
1056
|
// Codex/Grok only see HQ_SESSION_ID.
|
|
1050
1057
|
const sid = session?.sessionId ?? randomUUID();
|
|
1051
|
-
|
|
1058
|
+
let env;
|
|
1059
|
+
try {
|
|
1060
|
+
// The bot's own login or account, when set. A missing one fails the
|
|
1061
|
+
// turn loudly (status + reply); it never falls back to the owner's login.
|
|
1062
|
+
env = withOsUser(botAuthEnv(dir, { ...live, runtime: config.runtime }, turnEnv(process.env, kind, config.agentUid, companySlug)));
|
|
1063
|
+
}
|
|
1064
|
+
catch (err) {
|
|
1065
|
+
throw new CompanyBindingError(`I can't answer yet: ${err instanceof Error ? err.message : String(err)} I did not use anyone else's login; this work is still pending.`);
|
|
1066
|
+
}
|
|
1052
1067
|
let bind = { env };
|
|
1053
1068
|
if (companySlug) {
|
|
1054
1069
|
try {
|
|
@@ -1941,7 +1956,29 @@ export async function runBot(deps) {
|
|
|
1941
1956
|
// The job's stored company is checked against the bot's current config
|
|
1942
1957
|
// (read fresh from disk) every time it fires. A company the bot no longer
|
|
1943
1958
|
// serves is never synced: the run is skipped and the job removed.
|
|
1944
|
-
const
|
|
1959
|
+
const freshConfig = readBotConfig(dir);
|
|
1960
|
+
if (!freshConfig) {
|
|
1961
|
+
// bot.json is missing or unreadable, so which company the bot serves is
|
|
1962
|
+
// unknown. Skip this run without writing anything; the job stays for the
|
|
1963
|
+
// next fire.
|
|
1964
|
+
const text = `Skipped the hot-channel sync for ${syncCompany}: the bot's config (bot.json) could not be read, so nothing was written. The job will try again at its next run.`;
|
|
1965
|
+
markProcessed(dir, id, now().toISOString());
|
|
1966
|
+
try {
|
|
1967
|
+
await sendToPerson(text);
|
|
1968
|
+
}
|
|
1969
|
+
catch (err) {
|
|
1970
|
+
log("warn", `report for run ${id} of job ${jobId} was not delivered: ${err instanceof Error ? err.message : String(err)}`);
|
|
1971
|
+
}
|
|
1972
|
+
await completeJobRun(jobId, id, { status: "skipped-precondition", output: jobRunOutput(text) });
|
|
1973
|
+
markReported("skipped-precondition");
|
|
1974
|
+
await api.ackInbox(config.agentUid, id);
|
|
1975
|
+
clearInflight(dir, id);
|
|
1976
|
+
attempts.delete(id);
|
|
1977
|
+
setStatus({ lastActivityAt: now().toISOString() });
|
|
1978
|
+
log("warn", `hot-signal-sync job ${jobId} run ${id} skipped: bot.json missing or unreadable`);
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
const currentCompany = currentHotSignalSyncCompany(freshConfig);
|
|
1945
1982
|
if (currentCompany !== syncCompany) {
|
|
1946
1983
|
let removedNote = "";
|
|
1947
1984
|
try {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which model login a local bot uses, per runtime.
|
|
3
|
+
*
|
|
4
|
+
* - default: the owner's own sign-in for that runtime (no config dir set).
|
|
5
|
+
* - own: a sign-in that belongs to the bot alone, kept in
|
|
6
|
+
* ~/.hq/bots/<name>/auth/<runtime>/ (CLAUDE_CONFIG_DIR / CODEX_HOME).
|
|
7
|
+
* - account: an existing `hq capacity` / lanes account home, by id.
|
|
8
|
+
*
|
|
9
|
+
* Also the session set-aside a runtime switch needs: session files belong to
|
|
10
|
+
* one runtime, so they move to a backup folder and the new runtime starts
|
|
11
|
+
* fresh. DM history, memory and the cloud identity are never touched.
|
|
12
|
+
*
|
|
13
|
+
* This module never reads, prints, copies or hashes a credential. It only
|
|
14
|
+
* names directories and checks whether they exist.
|
|
15
|
+
*/
|
|
16
|
+
import type { BotConfig, BotRuntimeId } from "./config.js";
|
|
17
|
+
import type { LanesConfig } from "../lanes/types.js";
|
|
18
|
+
/** Runtimes `hq bot login` can sign in for (each has a config-dir variable and a login command). */
|
|
19
|
+
export declare const BOT_LOGIN_RUNTIMES: readonly ["claude", "codex"];
|
|
20
|
+
export type BotLoginRuntime = (typeof BOT_LOGIN_RUNTIMES)[number];
|
|
21
|
+
export declare function isBotLoginRuntime(value: unknown): value is BotLoginRuntime;
|
|
22
|
+
export type BotRuntimeAuth = {
|
|
23
|
+
mode: "own";
|
|
24
|
+
} | {
|
|
25
|
+
mode: "account";
|
|
26
|
+
account: string;
|
|
27
|
+
};
|
|
28
|
+
export type BotRuntimeAuthMap = Partial<Record<BotRuntimeId, BotRuntimeAuth>>;
|
|
29
|
+
export declare const BOT_AUTH_DIRNAME = "auth";
|
|
30
|
+
export declare const BOT_SESSION_ARCHIVE_DIRNAME = "session-archive";
|
|
31
|
+
export declare const VENDOR_LOGIN: Record<BotLoginRuntime, {
|
|
32
|
+
command: string;
|
|
33
|
+
args: string[];
|
|
34
|
+
}>;
|
|
35
|
+
export declare const VENDOR_LOGOUT: Record<BotLoginRuntime, {
|
|
36
|
+
command: string;
|
|
37
|
+
args: string[];
|
|
38
|
+
}>;
|
|
39
|
+
/** The bot's own config home for a runtime: <botDir>/auth/<runtime>. */
|
|
40
|
+
export declare function botOwnAuthHome(dir: string, runtime: BotRuntimeId): string;
|
|
41
|
+
/** The saved login choice for a runtime, or null for the owner's default. Tolerant of junk on read. */
|
|
42
|
+
export declare function botRuntimeAuth(config: Pick<BotConfig, "runtime" | "runtimeAuth">, runtime?: BotRuntimeId): BotRuntimeAuth | null;
|
|
43
|
+
/** A copy of the map with `runtime` set to `auth` (null = back to the owner's default). */
|
|
44
|
+
export declare function withRuntimeAuth(map: BotRuntimeAuthMap | undefined, runtime: BotRuntimeId, auth: BotRuntimeAuth | null): BotRuntimeAuthMap | undefined;
|
|
45
|
+
export interface AccountLookupDeps {
|
|
46
|
+
loadConfig?: (hqRoot: string) => LanesConfig;
|
|
47
|
+
existsSync?: (p: string) => boolean;
|
|
48
|
+
}
|
|
49
|
+
/** The home directory of a capacity account for a runtime. Throws an expected error when it is not usable. */
|
|
50
|
+
export declare function capacityAccountHome(hqRoot: string, runtime: BotRuntimeId, accountId: string, deps?: AccountLookupDeps): string;
|
|
51
|
+
/**
|
|
52
|
+
* The environment a bot's model turn runs with: `base` with the runtime's
|
|
53
|
+
* config-dir variable pointed at the bot's chosen login. A missing own login
|
|
54
|
+
* or a gone account is an error: falling back to the owner's login is the
|
|
55
|
+
* thing this setting exists to prevent.
|
|
56
|
+
*/
|
|
57
|
+
export declare function botAuthEnv(dir: string, config: Pick<BotConfig, "name" | "runtime" | "runtimeAuth" | "hqRoot">, base: NodeJS.ProcessEnv, deps?: AccountLookupDeps): NodeJS.ProcessEnv;
|
|
58
|
+
export interface BotAuthDescription {
|
|
59
|
+
runtime: BotRuntimeId;
|
|
60
|
+
login: "default" | "own" | "account" | "invalid";
|
|
61
|
+
account?: string;
|
|
62
|
+
/** The config-dir variable the bot's turns set (never its value's contents). */
|
|
63
|
+
envVar?: string;
|
|
64
|
+
/** Own login: whether its folder exists. Never a token check. */
|
|
65
|
+
present?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export declare function describeBotAuth(dir: string, config: Pick<BotConfig, "runtime" | "runtimeAuth">, existsSync?: (p: string) => boolean): BotAuthDescription;
|
|
68
|
+
export declare function formatBotAuth(d: BotAuthDescription, name: string): string;
|
|
69
|
+
export type SpawnInteractive = (command: string, args: string[], env: NodeJS.ProcessEnv) => {
|
|
70
|
+
status: number | null;
|
|
71
|
+
error?: Error;
|
|
72
|
+
};
|
|
73
|
+
/** Runs the vendor's own login/logout with the terminal attached. Its output goes to the terminal only, never to HQ's log. */
|
|
74
|
+
export declare const defaultSpawnInteractive: SpawnInteractive;
|
|
75
|
+
/** Logout runs quietly: nothing it prints is shown or logged. */
|
|
76
|
+
export declare const defaultSpawnQuiet: SpawnInteractive;
|
|
77
|
+
/**
|
|
78
|
+
* Sign a bot in with its own login for `runtime`. The vendor CLI writes the
|
|
79
|
+
* credential into the bot's home (or, for Claude on macOS, a keychain item
|
|
80
|
+
* keyed by that home). HQ never sees it.
|
|
81
|
+
*/
|
|
82
|
+
export declare function signInBotRuntime(dir: string, runtime: BotLoginRuntime, deps?: {
|
|
83
|
+
spawn?: SpawnInteractive;
|
|
84
|
+
quietSpawn?: SpawnInteractive;
|
|
85
|
+
env?: NodeJS.ProcessEnv;
|
|
86
|
+
}): {
|
|
87
|
+
home: string;
|
|
88
|
+
created: boolean;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Sign the bot's own login out and delete its home. The vendor logout runs
|
|
92
|
+
* first so a Claude keychain item keyed by the home is removed too.
|
|
93
|
+
*/
|
|
94
|
+
export declare function signOutBotRuntime(dir: string, runtime: BotLoginRuntime, deps?: {
|
|
95
|
+
spawn?: SpawnInteractive;
|
|
96
|
+
env?: NodeJS.ProcessEnv;
|
|
97
|
+
}): {
|
|
98
|
+
removed: boolean;
|
|
99
|
+
vendorLogout: "ok" | "failed" | "skipped";
|
|
100
|
+
};
|
|
101
|
+
/** True for the model-session files of a bot dir (owner DM, other DMs, rooms, jobs). */
|
|
102
|
+
export declare function isBotSessionFile(name: string): boolean;
|
|
103
|
+
export interface SessionSetAside {
|
|
104
|
+
archiveDir: string;
|
|
105
|
+
moved: string[];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Before a runtime switch: back up bot.json and move every session file into
|
|
109
|
+
* <dir>/session-archive/<stamp>-<oldRuntime>/, so the new runtime starts
|
|
110
|
+
* fresh and the old sessions can be put back.
|
|
111
|
+
*/
|
|
112
|
+
export declare function setAsideBotSessions(dir: string, oldRuntime: BotRuntimeId, now?: Date): SessionSetAside;
|
|
113
|
+
/**
|
|
114
|
+
* Undo setAsideBotSessions: the moved session files go back. bot.json is not
|
|
115
|
+
* overwritten (the bot may have written fields since); the caller patches the
|
|
116
|
+
* settings it changed back. The backup folder stays for manual recovery.
|
|
117
|
+
*/
|
|
118
|
+
export declare function restoreBotSessions(dir: string, aside: SessionSetAside): void;
|
|
119
|
+
//# sourceMappingURL=runtime-auth.d.ts.map
|