@indigoai-us/hq-cli 5.345.188 → 5.345.190
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 +131 -0
- package/dist/command-catalog.generated.d.ts +63 -0
- package/dist/command-catalog.generated.js +78 -0
- package/dist/commands/bot.js +15 -3
- package/dist/commands/cloud.js +16 -2
- package/dist/commands/core-company-index-scope-flag.d.ts +2 -1
- package/dist/commands/core-company-index-scope-flag.js +2 -1
- package/dist/commands/core.js +0 -5
- package/dist/commands/dm-attach.d.ts +73 -0
- package/dist/commands/dm-attach.js +104 -0
- package/dist/commands/dm.d.ts +10 -0
- package/dist/commands/dm.js +114 -2
- package/dist/commands/files.d.ts +18 -0
- package/dist/commands/files.js +73 -0
- package/dist/commands/sources.js +15 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +45054 -36140
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/fleet-local.d.ts +43 -1
- package/dist/lib/bot/fleet-local.js +204 -41
- package/dist/lib/bot/fleet-python.d.ts +6 -0
- package/dist/lib/bot/fleet-python.js +24 -5
- package/dist/lib/bot/probe.d.ts +7 -1
- package/dist/lib/bot/probe.js +11 -3
- package/dist/lib/bot/run.d.ts +9 -0
- package/dist/lib/bot/run.js +114 -8
- package/dist/lib/bot/runtime-failure.d.ts +3 -0
- package/dist/lib/bot/runtime-failure.js +20 -2
- 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/index-render/session-scope.d.ts +3 -3
- package/dist/lib/index-render/session-scope.js +11 -9
- package/dist/lib/index-render/shared.d.ts +6 -4
- package/dist/lib/index-render/shared.js +33 -4
- package/dist/utils/cognito-session.d.ts +4 -2
- package/dist/utils/cognito-session.js +27 -42
- package/dist/utils/hq-root-resolver.d.ts +27 -1
- package/dist/utils/hq-root-resolver.js +65 -33
- package/package.json +14 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.107","bundleSha256":"
|
|
1
|
+
{"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.107","bundleSha256":"4ffaf3a579e9222aa2e9a1850e355d4a152d6d3323cf0583154cf28e1d45459a","formatVersion":1}
|
|
@@ -99,8 +99,34 @@ export interface FleetLocalBootstrapDeps {
|
|
|
99
99
|
start?: boolean;
|
|
100
100
|
python?: FleetPython;
|
|
101
101
|
}
|
|
102
|
+
interface SharedFleetRuntime {
|
|
103
|
+
id: string;
|
|
104
|
+
root: string;
|
|
105
|
+
runtimeDir: string;
|
|
106
|
+
sourcesDir: string;
|
|
107
|
+
/** Extracted runtime source tree (`sources/runtime`, or `s/r` on Windows). */
|
|
108
|
+
runtimeSource: string;
|
|
109
|
+
/** Extracted distribution tree (`sources/distribution`, or `s/d` on Windows). */
|
|
110
|
+
distributionSource: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Hex characters of the runtime identity used for the shared runtime
|
|
114
|
+
* directory name on Windows. The full 64-character identity plus
|
|
115
|
+
* `sources\runtime` pushed the runtime's deepest files past MAX_PATH (260).
|
|
116
|
+
*/
|
|
117
|
+
export declare const WINDOWS_RUNTIME_DIR_ID_LENGTH = 16;
|
|
118
|
+
/** Longest path Windows accepts when long paths are turned off (MAX_PATH minus the terminator). */
|
|
119
|
+
export declare const WINDOWS_MAX_PATH_CHARS = 259;
|
|
120
|
+
/**
|
|
121
|
+
* Where a shared runtime lives. macOS and Linux keep the original layout
|
|
122
|
+
* (`runtimes/<64 hex>/{runtime,sources/runtime,sources/distribution}`).
|
|
123
|
+
* Windows uses `runtimes/<16 hex>/{v,s\r,s\d}` so deep runtime files stay
|
|
124
|
+
* under MAX_PATH; `ready.json` records the full identity.
|
|
125
|
+
*/
|
|
126
|
+
export declare function sharedRuntimeLayout(root: string, id: string, platform: NodeJS.Platform): SharedFleetRuntime;
|
|
102
127
|
type FleetLocalProfileDeps = Pick<FleetLocalBootstrapDeps, "fleetHome" | "platform"> & {
|
|
103
128
|
runtimeRoot?: string;
|
|
129
|
+
runtime?: SharedFleetRuntime;
|
|
104
130
|
};
|
|
105
131
|
export interface FleetLocalBootstrapResult {
|
|
106
132
|
profile: string;
|
|
@@ -139,7 +165,15 @@ export declare const HQ_FLEET_LOCAL_RELEASE: FleetLocalReleasePin;
|
|
|
139
165
|
export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEASJoapvo0WMTg+Wu3Jvt0nzlXLOBEr+G1aJRi0JkEb4A=\n-----END PUBLIC KEY-----\n";
|
|
140
166
|
export declare function fleetHome(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
141
167
|
export declare function verifyFleetLocalRelease(files: FleetLocalReleaseFiles, release?: FleetLocalReleasePin, publicKey?: string): void;
|
|
142
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Warning text when the signed manifest asks for a newer hq CLI than this
|
|
170
|
+
* one, or null. Advisory only: the pinned runtime is still installed, since
|
|
171
|
+
* blocking would strand every user until that CLI version is published.
|
|
172
|
+
*/
|
|
173
|
+
export declare function fleetLocalCliCompatibilityWarning(manifest: Buffer, release?: FleetLocalReleasePin, cliVersion?: string): string | null;
|
|
174
|
+
/** `gh auth token` fallback for the release download token. */
|
|
175
|
+
export declare const FLEET_RELEASE_TOKEN_TIMEOUT_MS = 15000;
|
|
176
|
+
export declare function resolveFleetReleaseToken(env?: NodeJS.ProcessEnv, run?: FleetLocalExec): string;
|
|
143
177
|
export declare function fetchHqFleetReleaseLinks(release: FleetLocalReleasePin): Promise<Map<string, string> | null>;
|
|
144
178
|
export declare const FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS = 30000;
|
|
145
179
|
export declare const FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS: number;
|
|
@@ -170,6 +204,14 @@ export declare function writeFleetLocalSoul(agentUid: string, systemPrompt: stri
|
|
|
170
204
|
export declare function scanFleetLocalInstructions(agentUid: string, text: string, deps?: FleetLocalControlDeps): string[];
|
|
171
205
|
/** Checks the instructions with the runtime's scanner, then makes them the bot's persona. */
|
|
172
206
|
export declare function refreshFleetLocalInstructions(agentUid: string, systemPrompt: string, deps?: FleetLocalControlDeps): string;
|
|
207
|
+
/** Last few non-empty stderr lines of a failed child process, trimmed for an error message. */
|
|
208
|
+
export declare function stderrTail(error: unknown): string;
|
|
209
|
+
/**
|
|
210
|
+
* Message for a Windows extraction that cannot fit under MAX_PATH, or null
|
|
211
|
+
* when it fits or long paths are on. `longest` is the longest member name in
|
|
212
|
+
* the archive; members land at `<destination>\<name>`.
|
|
213
|
+
*/
|
|
214
|
+
export declare function windowsExtractionPathProblem(destination: string, longest: number, longPathsEnabled: number | null): string | null;
|
|
173
215
|
/** Moves a fresh machine credential into the bot's secure store and deletes the file. */
|
|
174
216
|
export declare function importFleetLocalMachineCredential(agentUid: string, machineCredentials: string, deps?: FleetLocalControlDeps): void;
|
|
175
217
|
export declare function bootstrapFleetLocalBot(input: FleetLocalProfileInput & {
|
|
@@ -6,6 +6,8 @@ import * as path from "node:path";
|
|
|
6
6
|
import { HQ_FLEET_RUNTIME_ENGINE_ID, } from "./config.js";
|
|
7
7
|
import { withBotLock } from "./lock.js";
|
|
8
8
|
import { ensureFleetPython, fleetUvEnv } from "./fleet-python.js";
|
|
9
|
+
import semver from "semver";
|
|
10
|
+
import { CLI_VERSION } from "../../cli-version.js";
|
|
9
11
|
import { ensureCognitoToken } from "../../utils/cognito-session.js";
|
|
10
12
|
import { vaultApiFetch } from "../../utils/vault-api.js";
|
|
11
13
|
/** The model the HQ Fleet Runtime asks for when a Claude bot names none. */
|
|
@@ -34,6 +36,46 @@ export function fleetProviderForRuntime(runtime, model) {
|
|
|
34
36
|
export function fleetProviderNeedsSignIn(provider) {
|
|
35
37
|
return provider.id !== "anthropic";
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Hex characters of the runtime identity used for the shared runtime
|
|
41
|
+
* directory name on Windows. The full 64-character identity plus
|
|
42
|
+
* `sources\runtime` pushed the runtime's deepest files past MAX_PATH (260).
|
|
43
|
+
*/
|
|
44
|
+
export const WINDOWS_RUNTIME_DIR_ID_LENGTH = 16;
|
|
45
|
+
/** Longest path Windows accepts when long paths are turned off (MAX_PATH minus the terminator). */
|
|
46
|
+
export const WINDOWS_MAX_PATH_CHARS = 259;
|
|
47
|
+
function legacySharedRuntimeLayout(root, id) {
|
|
48
|
+
const shared = path.join(root, "runtimes", id);
|
|
49
|
+
const sources = path.join(shared, "sources");
|
|
50
|
+
return {
|
|
51
|
+
id,
|
|
52
|
+
root: shared,
|
|
53
|
+
runtimeDir: path.join(shared, "runtime"),
|
|
54
|
+
sourcesDir: sources,
|
|
55
|
+
runtimeSource: path.join(sources, "runtime"),
|
|
56
|
+
distributionSource: path.join(sources, "distribution"),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Where a shared runtime lives. macOS and Linux keep the original layout
|
|
61
|
+
* (`runtimes/<64 hex>/{runtime,sources/runtime,sources/distribution}`).
|
|
62
|
+
* Windows uses `runtimes/<16 hex>/{v,s\r,s\d}` so deep runtime files stay
|
|
63
|
+
* under MAX_PATH; `ready.json` records the full identity.
|
|
64
|
+
*/
|
|
65
|
+
export function sharedRuntimeLayout(root, id, platform) {
|
|
66
|
+
if (platform !== "win32")
|
|
67
|
+
return legacySharedRuntimeLayout(root, id);
|
|
68
|
+
const shared = path.join(root, "runtimes", id.slice(0, WINDOWS_RUNTIME_DIR_ID_LENGTH));
|
|
69
|
+
const sources = path.join(shared, "s");
|
|
70
|
+
return {
|
|
71
|
+
id,
|
|
72
|
+
root: shared,
|
|
73
|
+
runtimeDir: path.join(shared, "v"),
|
|
74
|
+
sourcesDir: sources,
|
|
75
|
+
runtimeSource: path.join(sources, "r"),
|
|
76
|
+
distributionSource: path.join(sources, "d"),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
37
79
|
export const HQ_FLEET_LOCAL_RELEASE = {
|
|
38
80
|
repository: "indigoai-us/hq-fleet-local",
|
|
39
81
|
tag: "v0.4.17",
|
|
@@ -105,18 +147,47 @@ export function verifyFleetLocalRelease(files, release = HQ_FLEET_LOCAL_RELEASE,
|
|
|
105
147
|
throw expectedError(`HQ Fleet Local ${release.version} manifest signature is invalid.`);
|
|
106
148
|
}
|
|
107
149
|
}
|
|
108
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Warning text when the signed manifest asks for a newer hq CLI than this
|
|
152
|
+
* one, or null. Advisory only: the pinned runtime is still installed, since
|
|
153
|
+
* blocking would strand every user until that CLI version is published.
|
|
154
|
+
*/
|
|
155
|
+
export function fleetLocalCliCompatibilityWarning(manifest, release = HQ_FLEET_LOCAL_RELEASE, cliVersion = CLI_VERSION) {
|
|
156
|
+
let minimum;
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(manifest.toString("utf8"));
|
|
159
|
+
minimum = parsed.compatibility?.minimum_hq_cli_version;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
if (typeof minimum !== "string")
|
|
165
|
+
return null;
|
|
166
|
+
const required = semver.valid(semver.coerce(minimum));
|
|
167
|
+
const current = semver.valid(semver.coerce(cliVersion));
|
|
168
|
+
if (!required || !current || semver.gte(current, required))
|
|
169
|
+
return null;
|
|
170
|
+
return `HQ Fleet Local ${release.version} asks for hq CLI ${minimum} or newer; this is ${cliVersion}. ` +
|
|
171
|
+
"Update with `npm install -g @indigoai-us/hq-cli` if the bot misbehaves.";
|
|
172
|
+
}
|
|
173
|
+
/** `gh auth token` fallback for the release download token. */
|
|
174
|
+
export const FLEET_RELEASE_TOKEN_TIMEOUT_MS = 15_000;
|
|
175
|
+
export function resolveFleetReleaseToken(env = process.env, run = execFileSync) {
|
|
109
176
|
for (const name of ["HQ_FLEET_RELEASE_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"]) {
|
|
110
177
|
const value = env[name]?.trim();
|
|
111
178
|
if (value)
|
|
112
179
|
return value;
|
|
113
180
|
}
|
|
114
181
|
try {
|
|
115
|
-
|
|
182
|
+
// gh can block on a keychain prompt; a create must fail, not hang.
|
|
183
|
+
const output = run("gh", ["auth", "token"], {
|
|
116
184
|
encoding: "utf8",
|
|
117
185
|
stdio: ["ignore", "pipe", "ignore"],
|
|
118
186
|
windowsHide: true,
|
|
119
|
-
|
|
187
|
+
timeout: FLEET_RELEASE_TOKEN_TIMEOUT_MS,
|
|
188
|
+
killSignal: "SIGKILL",
|
|
189
|
+
});
|
|
190
|
+
const value = (Buffer.isBuffer(output) ? output.toString("utf8") : output).trim();
|
|
120
191
|
if (value)
|
|
121
192
|
return value;
|
|
122
193
|
}
|
|
@@ -299,8 +370,14 @@ export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
|
299
370
|
const root = deps.fleetHome ?? fleetHome();
|
|
300
371
|
const dir = path.join(root, "distributions", "hq-fleet-local", release.version);
|
|
301
372
|
const current = installedFiles(dir, release);
|
|
373
|
+
const warnCompatibility = (manifest) => {
|
|
374
|
+
const warning = fleetLocalCliCompatibilityWarning(manifest, release);
|
|
375
|
+
if (warning)
|
|
376
|
+
process.stderr.write(`Warning: ${warning}\n`);
|
|
377
|
+
};
|
|
302
378
|
if (current) {
|
|
303
379
|
verifyFleetLocalRelease(current, release, publicKey);
|
|
380
|
+
warnCompatibility(current.manifest);
|
|
304
381
|
}
|
|
305
382
|
else {
|
|
306
383
|
const fetcher = deps.fetch ?? fetch;
|
|
@@ -310,6 +387,7 @@ export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
|
310
387
|
? await downloadFleetLocalReleaseFromHq(release, fetcher, links, cachedRuntime)
|
|
311
388
|
: await downloadFleetLocalRelease(release, fetcher, (deps.token ?? resolveFleetReleaseToken)(), cachedRuntime);
|
|
312
389
|
verifyFleetLocalRelease(files, release, publicKey);
|
|
390
|
+
warnCompatibility(files.manifest);
|
|
313
391
|
installFiles(dir, files, release, publicKey);
|
|
314
392
|
}
|
|
315
393
|
return {
|
|
@@ -371,18 +449,19 @@ function venvExecutable(runtimeDir, name, platform) {
|
|
|
371
449
|
? path.join(runtimeDir, "Scripts", `${name}.exe`)
|
|
372
450
|
: path.join(runtimeDir, "bin", name);
|
|
373
451
|
}
|
|
374
|
-
function sharedRuntimeFromMarker(profileDir, root) {
|
|
452
|
+
function sharedRuntimeFromMarker(profileDir, root, platform = process.platform) {
|
|
375
453
|
try {
|
|
376
454
|
const marker = JSON.parse(fs.readFileSync(path.join(profileDir, "bootstrap.json"), "utf8"));
|
|
377
455
|
if (marker.schemaVersion !== 2 || typeof marker.runtimeId !== "string" || !/^[a-f0-9]{64}$/.test(marker.runtimeId))
|
|
378
456
|
return null;
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
root
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
457
|
+
const layout = sharedRuntimeLayout(root, marker.runtimeId, platform);
|
|
458
|
+
if (platform === "win32" && !fs.existsSync(path.join(layout.root, "ready.json"))) {
|
|
459
|
+
// Bots installed before the short Windows layout keep using their runtime.
|
|
460
|
+
const legacy = legacySharedRuntimeLayout(root, marker.runtimeId);
|
|
461
|
+
if (fs.existsSync(path.join(legacy.root, "ready.json")))
|
|
462
|
+
return legacy;
|
|
463
|
+
}
|
|
464
|
+
return layout;
|
|
386
465
|
}
|
|
387
466
|
catch {
|
|
388
467
|
return null;
|
|
@@ -420,7 +499,7 @@ function fleetLocalExecutables(agentUid, deps) {
|
|
|
420
499
|
const root = deps.fleetHome ?? fleetHome();
|
|
421
500
|
const platform = deps.platform ?? process.platform;
|
|
422
501
|
const profileDir = profileRoot(agentUid, root);
|
|
423
|
-
const shared = sharedRuntimeFromMarker(profileDir, root);
|
|
502
|
+
const shared = sharedRuntimeFromMarker(profileDir, root, platform);
|
|
424
503
|
const runtimeDir = shared?.runtimeDir ?? path.join(profileDir, "runtime");
|
|
425
504
|
const sourcesDir = shared?.sourcesDir ?? path.join(profileDir, "sources");
|
|
426
505
|
const profile = path.join(profileDir, "profile.json");
|
|
@@ -478,12 +557,12 @@ export function fleetLocalProfileNeedsAdoption(agentUid, deps = {}) {
|
|
|
478
557
|
if (!entries.includes(canonicalName))
|
|
479
558
|
return true;
|
|
480
559
|
const profileDir = path.join(profiles, canonicalName);
|
|
481
|
-
const
|
|
560
|
+
const platform = deps.platform ?? process.platform;
|
|
561
|
+
const shared = sharedRuntimeFromMarker(profileDir, root, platform);
|
|
482
562
|
if (!shared)
|
|
483
563
|
return true;
|
|
484
564
|
if (!profileEngineMatchesRelease(profileDir, deps.release ?? HQ_FLEET_LOCAL_RELEASE))
|
|
485
565
|
return true;
|
|
486
|
-
const platform = deps.platform ?? process.platform;
|
|
487
566
|
if (!["hq-fleet", "hq-fleet-local", "python"].every((name) => fs.existsSync(venvExecutable(shared.runtimeDir, name, platform))))
|
|
488
567
|
return true;
|
|
489
568
|
if (hasEmbeddedRuntime(profileDir, platform) || fs.existsSync(path.join(profileDir, "sources")))
|
|
@@ -633,7 +712,7 @@ export function removeFleetLocalBot(agentUid, deps = {}) {
|
|
|
633
712
|
}
|
|
634
713
|
const platform = deps.platform ?? process.platform;
|
|
635
714
|
const legacyRuntime = path.join(profileDir, "runtime");
|
|
636
|
-
const shared = sharedRuntimeFromMarker(profileDir, deps.fleetHome ?? fleetHome());
|
|
715
|
+
const shared = sharedRuntimeFromMarker(profileDir, deps.fleetHome ?? fleetHome(), platform);
|
|
637
716
|
const installedRuntime = shared?.runtimeDir ?? legacyRuntime;
|
|
638
717
|
if (!["hq-fleet", "hq-fleet-local"].every((name) => fs.existsSync(venvExecutable(installedRuntime, name, platform)))) {
|
|
639
718
|
fs.rmSync(profileDir, { recursive: true, force: true });
|
|
@@ -751,9 +830,11 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
751
830
|
}
|
|
752
831
|
const root = deps.fleetHome ?? fleetHome();
|
|
753
832
|
const dir = profileRoot(input.agentUid, root);
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
const
|
|
833
|
+
const layout = deps.runtime ??
|
|
834
|
+
(deps.runtimeRoot ? undefined : sharedRuntimeFromMarker(dir, root, deps.platform ?? process.platform) ?? undefined);
|
|
835
|
+
const runtimeRoot = deps.runtimeRoot ?? dir;
|
|
836
|
+
const runtimeDir = layout?.runtimeDir ?? path.join(runtimeRoot, "runtime");
|
|
837
|
+
const distributionSource = layout?.distributionSource ?? path.join(runtimeRoot, "sources", "distribution");
|
|
757
838
|
const destination = path.join(dir, "profile.json");
|
|
758
839
|
const temporary = path.join(dir, `.profile-${process.pid}-${crypto.randomUUID()}`);
|
|
759
840
|
const profile = {
|
|
@@ -766,7 +847,7 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
766
847
|
profileDir: path.resolve(dir),
|
|
767
848
|
runtime: { commit: input.installation.release.runtimeCommit, path: path.resolve(runtimeDir) },
|
|
768
849
|
paths: {
|
|
769
|
-
pluginSource: path.resolve(path.join(
|
|
850
|
+
pluginSource: path.resolve(path.join(distributionSource, "src", "hq_fleet_local", "plugins", "hqdm")),
|
|
770
851
|
statusFile: path.resolve(path.join(input.botDir, "status.json")),
|
|
771
852
|
logsDir: path.resolve(path.join(input.botDir, "logs")),
|
|
772
853
|
},
|
|
@@ -827,11 +908,11 @@ export function scanFleetLocalInstructions(agentUid, text, deps = {}) {
|
|
|
827
908
|
const run = deps.run ?? execFileSync;
|
|
828
909
|
const root = deps.fleetHome ?? fleetHome();
|
|
829
910
|
const profileDir = profileRoot(agentUid, root);
|
|
830
|
-
const
|
|
911
|
+
const platform = deps.platform ?? process.platform;
|
|
912
|
+
const shared = sharedRuntimeFromMarker(profileDir, root, platform);
|
|
831
913
|
const runtimeDir = shared?.runtimeDir ?? path.join(profileDir, "runtime");
|
|
832
|
-
const
|
|
833
|
-
const
|
|
834
|
-
const source = path.join(sourcesDir, "runtime");
|
|
914
|
+
const python = venvExecutable(runtimeDir, "python", platform);
|
|
915
|
+
const source = shared?.runtimeSource ?? path.join(profileDir, "sources", "runtime");
|
|
835
916
|
if (!fs.existsSync(python) || !fs.existsSync(source)) {
|
|
836
917
|
throw expectedError("HQ Fleet Local is not installed for this bot, so its instructions could not be checked.");
|
|
837
918
|
}
|
|
@@ -861,13 +942,81 @@ export function refreshFleetLocalInstructions(agentUid, systemPrompt, deps = {})
|
|
|
861
942
|
}
|
|
862
943
|
return writeFleetLocalSoul(agentUid, systemPrompt, deps);
|
|
863
944
|
}
|
|
945
|
+
const STDERR_TAIL_LINES = 6;
|
|
946
|
+
const STDERR_TAIL_CHARS = 800;
|
|
947
|
+
/** Last few non-empty stderr lines of a failed child process, trimmed for an error message. */
|
|
948
|
+
export function stderrTail(error) {
|
|
949
|
+
const raw = error?.stderr;
|
|
950
|
+
const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : typeof raw === "string" ? raw : "";
|
|
951
|
+
const lines = text.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
952
|
+
const tail = lines.slice(-STDERR_TAIL_LINES).join("\n");
|
|
953
|
+
return tail.length > STDERR_TAIL_CHARS ? `...${tail.slice(-STDERR_TAIL_CHARS)}` : tail;
|
|
954
|
+
}
|
|
864
955
|
function checkedRun(run, executable, args, options, label) {
|
|
956
|
+
// Keep stdout quiet but capture stderr, so a failure names its cause
|
|
957
|
+
// (for example a path-too-long error during extraction).
|
|
958
|
+
const stdio = options.stdio === "ignore" ? ["ignore", "ignore", "pipe"] : options.stdio;
|
|
865
959
|
try {
|
|
866
|
-
run(executable, args, {
|
|
960
|
+
run(executable, args, {
|
|
961
|
+
windowsHide: true,
|
|
962
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
963
|
+
...options,
|
|
964
|
+
...(stdio === undefined ? {} : { stdio }),
|
|
965
|
+
});
|
|
867
966
|
}
|
|
868
|
-
catch {
|
|
869
|
-
|
|
967
|
+
catch (error) {
|
|
968
|
+
const tail = stderrTail(error);
|
|
969
|
+
throw expectedError(`HQ Fleet Local ${label} failed.${tail ? `\n${tail}` : ""}`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
const FLEET_TAR_PATH_PROBE = [
|
|
973
|
+
"import json, sys, tarfile",
|
|
974
|
+
"longest = 0",
|
|
975
|
+
"with tarfile.open(sys.argv[1]) as archive:",
|
|
976
|
+
" for member in archive:",
|
|
977
|
+
" longest = max(longest, len(member.name.rstrip('/')))",
|
|
978
|
+
"enabled = None",
|
|
979
|
+
"try:",
|
|
980
|
+
" import winreg",
|
|
981
|
+
" key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SYSTEM\\CurrentControlSet\\Control\\FileSystem')",
|
|
982
|
+
" enabled = int(winreg.QueryValueEx(key, 'LongPathsEnabled')[0])",
|
|
983
|
+
"except Exception:",
|
|
984
|
+
" pass",
|
|
985
|
+
"print(json.dumps({'longest': longest, 'longPathsEnabled': enabled}))",
|
|
986
|
+
].join("\n");
|
|
987
|
+
/**
|
|
988
|
+
* Message for a Windows extraction that cannot fit under MAX_PATH, or null
|
|
989
|
+
* when it fits or long paths are on. `longest` is the longest member name in
|
|
990
|
+
* the archive; members land at `<destination>\<name>`.
|
|
991
|
+
*/
|
|
992
|
+
export function windowsExtractionPathProblem(destination, longest, longPathsEnabled) {
|
|
993
|
+
const needed = destination.length + 1 + longest;
|
|
994
|
+
if (needed <= WINDOWS_MAX_PATH_CHARS || longPathsEnabled === 1)
|
|
995
|
+
return null;
|
|
996
|
+
return `The HQ Fleet Runtime cannot be extracted on this PC: its deepest file needs a ${needed}-character path under ${destination}, ` +
|
|
997
|
+
`over the Windows limit of ${WINDOWS_MAX_PATH_CHARS}, and Windows long paths are turned off (LongPathsEnabled = ${longPathsEnabled ?? "unset"}). ` +
|
|
998
|
+
"Ask an administrator to set HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled to 1, or set HQ_FLEET_HOME to a shorter folder such as C:\\hqf, then retry.";
|
|
999
|
+
}
|
|
1000
|
+
function assertWindowsExtractionFits(run, python, archive, destination) {
|
|
1001
|
+
let probe;
|
|
1002
|
+
try {
|
|
1003
|
+
const output = run(python, ["-I", "-c", FLEET_TAR_PATH_PROBE, archive], {
|
|
1004
|
+
windowsHide: true,
|
|
1005
|
+
encoding: "utf8",
|
|
1006
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1007
|
+
maxBuffer: 1024 * 1024,
|
|
1008
|
+
});
|
|
1009
|
+
probe = JSON.parse((Buffer.isBuffer(output) ? output.toString("utf8") : output).trim());
|
|
870
1010
|
}
|
|
1011
|
+
catch {
|
|
1012
|
+
return; // The probe is advisory; extraction reports its own error.
|
|
1013
|
+
}
|
|
1014
|
+
if (typeof probe?.longest !== "number")
|
|
1015
|
+
return;
|
|
1016
|
+
const enabled = typeof probe.longPathsEnabled === "number" ? probe.longPathsEnabled : null;
|
|
1017
|
+
const problem = windowsExtractionPathProblem(destination, probe.longest, enabled);
|
|
1018
|
+
if (problem)
|
|
1019
|
+
throw expectedError(problem);
|
|
871
1020
|
}
|
|
872
1021
|
/** Moves a fresh machine credential into the bot's secure store and deletes the file. */
|
|
873
1022
|
export function importFleetLocalMachineCredential(agentUid, machineCredentials, deps = {}) {
|
|
@@ -896,25 +1045,37 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
896
1045
|
platform,
|
|
897
1046
|
extras: ["mcp", "anthropic"],
|
|
898
1047
|
})));
|
|
899
|
-
const
|
|
900
|
-
const
|
|
901
|
-
id: identity,
|
|
902
|
-
root: shared,
|
|
903
|
-
runtimeDir: path.join(shared, "runtime"),
|
|
904
|
-
sourcesDir: path.join(shared, "sources"),
|
|
905
|
-
});
|
|
1048
|
+
const layout = sharedRuntimeLayout(root, identity, platform);
|
|
1049
|
+
const shared = layout.root;
|
|
906
1050
|
return withBotLock(identity, async () => {
|
|
907
1051
|
const marker = path.join(shared, "ready.json");
|
|
908
|
-
const
|
|
909
|
-
const distributionSource = path.join(sources, "distribution");
|
|
910
|
-
const runtimeSource = path.join(sources, "runtime");
|
|
911
|
-
const runtimeDir = path.join(shared, "runtime");
|
|
1052
|
+
const { distributionSource, runtimeSource, runtimeDir } = layout;
|
|
912
1053
|
const executables = ["python", "hq-fleet", "hq-fleet-local"];
|
|
1054
|
+
if (platform === "win32") {
|
|
1055
|
+
const legacy = legacySharedRuntimeLayout(root, identity);
|
|
1056
|
+
if (fs.existsSync(path.join(legacy.root, "ready.json")) &&
|
|
1057
|
+
executables.every((name) => fs.existsSync(venvExecutable(legacy.runtimeDir, name, platform)))) {
|
|
1058
|
+
// A runtime installed before the short Windows layout keeps serving bots.
|
|
1059
|
+
return legacy;
|
|
1060
|
+
}
|
|
1061
|
+
// A half-extracted long-path runtime is never usable; clear it.
|
|
1062
|
+
fs.rmSync(legacy.root, { recursive: true, force: true });
|
|
1063
|
+
}
|
|
913
1064
|
if (fs.existsSync(marker)) {
|
|
1065
|
+
let recordedId;
|
|
1066
|
+
try {
|
|
1067
|
+
recordedId = JSON.parse(fs.readFileSync(marker, "utf8")).runtimeId;
|
|
1068
|
+
}
|
|
1069
|
+
catch {
|
|
1070
|
+
recordedId = undefined;
|
|
1071
|
+
}
|
|
1072
|
+
if (recordedId !== undefined && recordedId !== identity) {
|
|
1073
|
+
throw expectedError(`The shared HQ Fleet Runtime folder ${shared} belongs to a different runtime. Remove it and retry.`);
|
|
1074
|
+
}
|
|
914
1075
|
if (!executables.every((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)))) {
|
|
915
1076
|
throw expectedError("The shared HQ Fleet Runtime is incomplete. Reinstall it before creating another bot.");
|
|
916
1077
|
}
|
|
917
|
-
return
|
|
1078
|
+
return layout;
|
|
918
1079
|
}
|
|
919
1080
|
fs.rmSync(shared, { recursive: true, force: true });
|
|
920
1081
|
fs.mkdirSync(distributionSource, { recursive: true, mode: 0o700 });
|
|
@@ -927,6 +1088,8 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
927
1088
|
installation.archive,
|
|
928
1089
|
distributionSource,
|
|
929
1090
|
], { stdio: "ignore" }, "distribution extraction");
|
|
1091
|
+
if (platform === "win32")
|
|
1092
|
+
assertWindowsExtractionFits(run, python.python, installation.runtimeArchive, runtimeSource);
|
|
930
1093
|
checkedRun(run, python.python, [
|
|
931
1094
|
"-m",
|
|
932
1095
|
"tarfile",
|
|
@@ -955,8 +1118,8 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
955
1118
|
if (!executables.every((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)))) {
|
|
956
1119
|
throw expectedError("HQ Fleet Local installed without its runtime executables.");
|
|
957
1120
|
}
|
|
958
|
-
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: installation.engine })}\n`, { mode: 0o600 });
|
|
959
|
-
return
|
|
1121
|
+
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, runtimeId: identity, engine: installation.engine })}\n`, { mode: 0o600 });
|
|
1122
|
+
return layout;
|
|
960
1123
|
}, { root: path.join(root, "runtimes"), command: "HQ Fleet Runtime installation", waitMs: 20 * 60_000, staleMs: 2 * 60 * 60_000 });
|
|
961
1124
|
}
|
|
962
1125
|
function writeNativeProfileMarker(profileDir, shared, engine) {
|
|
@@ -982,7 +1145,7 @@ export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
|
982
1145
|
const { systemPrompt, machineCredentials, ...profileInput } = input;
|
|
983
1146
|
if (systemPrompt !== undefined)
|
|
984
1147
|
refreshFleetLocalInstructions(input.agentUid, systemPrompt, { fleetHome: root, platform, run });
|
|
985
|
-
const profile = writeFleetLocalProfile(profileInput, { fleetHome: root, platform,
|
|
1148
|
+
const profile = writeFleetLocalProfile(profileInput, { fleetHome: root, platform, runtime: shared });
|
|
986
1149
|
const helper = venvExecutable(shared.runtimeDir, "hq-fleet-local", platform);
|
|
987
1150
|
const runtime = venvExecutable(shared.runtimeDir, "hq-fleet", platform);
|
|
988
1151
|
if (!fs.existsSync(helper) || !fs.existsSync(runtime)) {
|
|
@@ -26,6 +26,12 @@ export interface FleetPython {
|
|
|
26
26
|
export declare const HQ_FLEET_PYTHON_VERSION = "3.13.17";
|
|
27
27
|
export declare const HQ_FLEET_PYTHON_TOOL: FleetPythonToolPin;
|
|
28
28
|
export declare const FLEET_PYTHON_TOOL_DOWNLOAD_TIMEOUT_MS: number;
|
|
29
|
+
/** Unpacking the pinned installer archive. */
|
|
30
|
+
export declare const FLEET_PYTHON_EXTRACT_TIMEOUT_MS: number;
|
|
31
|
+
/** `uv python install`: downloads and unpacks the private Python. */
|
|
32
|
+
export declare const FLEET_PYTHON_INSTALL_TIMEOUT_MS: number;
|
|
33
|
+
/** `uv python find`: local lookup only. */
|
|
34
|
+
export declare const FLEET_PYTHON_FIND_TIMEOUT_MS = 60000;
|
|
29
35
|
/** One lock for the whole machine: the installer and the private Python are shared by every bot. */
|
|
30
36
|
export declare const FLEET_PYTHON_LOCK_NAME = "hq-fleet-python";
|
|
31
37
|
/**
|
|
@@ -18,6 +18,12 @@ export const HQ_FLEET_PYTHON_TOOL = {
|
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
20
|
export const FLEET_PYTHON_TOOL_DOWNLOAD_TIMEOUT_MS = 5 * 60_000;
|
|
21
|
+
/** Unpacking the pinned installer archive. */
|
|
22
|
+
export const FLEET_PYTHON_EXTRACT_TIMEOUT_MS = 2 * 60_000;
|
|
23
|
+
/** `uv python install`: downloads and unpacks the private Python. */
|
|
24
|
+
export const FLEET_PYTHON_INSTALL_TIMEOUT_MS = 15 * 60_000;
|
|
25
|
+
/** `uv python find`: local lookup only. */
|
|
26
|
+
export const FLEET_PYTHON_FIND_TIMEOUT_MS = 60_000;
|
|
21
27
|
/** One lock for the whole machine: the installer and the private Python are shared by every bot. */
|
|
22
28
|
export const FLEET_PYTHON_LOCK_NAME = "hq-fleet-python";
|
|
23
29
|
function expectedError(message) {
|
|
@@ -27,9 +33,13 @@ function toolBinaryName(platform) {
|
|
|
27
33
|
return platform === "win32" ? "uv.exe" : "uv";
|
|
28
34
|
}
|
|
29
35
|
/** The last lines a failed child process wrote, or its exit code, so the user sees why. */
|
|
30
|
-
function causeOf(error) {
|
|
36
|
+
function causeOf(error, timeoutMs) {
|
|
31
37
|
if (error && typeof error === "object") {
|
|
32
38
|
const failure = error;
|
|
39
|
+
// A child killed by execFileSync's timeout: say so instead of a bare signal.
|
|
40
|
+
if (failure.code === "ETIMEDOUT") {
|
|
41
|
+
return timeoutMs !== undefined ? `timed out after ${Math.round(timeoutMs / 1000)}s` : "timed out";
|
|
42
|
+
}
|
|
33
43
|
const stderr = failure.stderr === undefined ? "" : (Buffer.isBuffer(failure.stderr) ? failure.stderr.toString("utf8") : String(failure.stderr));
|
|
34
44
|
const tail = stderr.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-3).join(" ");
|
|
35
45
|
if (tail)
|
|
@@ -132,10 +142,15 @@ async function installTool(deps) {
|
|
|
132
142
|
const extracted = path.join(staging, "extracted");
|
|
133
143
|
fs.mkdirSync(extracted, { mode: 0o700 });
|
|
134
144
|
try {
|
|
135
|
-
run(tarExecutable(platform, deps.env ?? process.env), ["-xf", archive, "-C", extracted], {
|
|
145
|
+
run(tarExecutable(platform, deps.env ?? process.env), ["-xf", archive, "-C", extracted], {
|
|
146
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
147
|
+
windowsHide: true,
|
|
148
|
+
timeout: FLEET_PYTHON_EXTRACT_TIMEOUT_MS,
|
|
149
|
+
killSignal: "SIGKILL",
|
|
150
|
+
});
|
|
136
151
|
}
|
|
137
152
|
catch (error) {
|
|
138
|
-
throw expectedError(`HQ Fleet Local could not unpack its runtime installer (${causeOf(error)}).`);
|
|
153
|
+
throw expectedError(`HQ Fleet Local could not unpack its runtime installer (${causeOf(error, FLEET_PYTHON_EXTRACT_TIMEOUT_MS)}).`);
|
|
139
154
|
}
|
|
140
155
|
const unpacked = findFile(extracted, toolBinaryName(platform));
|
|
141
156
|
if (!unpacked)
|
|
@@ -179,10 +194,12 @@ async function ensureFleetPythonLocked(root, deps) {
|
|
|
179
194
|
stdio: ["ignore", "ignore", "pipe"],
|
|
180
195
|
env,
|
|
181
196
|
windowsHide: true,
|
|
197
|
+
timeout: FLEET_PYTHON_INSTALL_TIMEOUT_MS,
|
|
198
|
+
killSignal: "SIGKILL",
|
|
182
199
|
});
|
|
183
200
|
}
|
|
184
201
|
catch (error) {
|
|
185
|
-
throw expectedError(`HQ Fleet Local could not install its private Python ${version} (${causeOf(error)}). If this mentions a download, check your internet connection and retry.`);
|
|
202
|
+
throw expectedError(`HQ Fleet Local could not install its private Python ${version} (${causeOf(error, FLEET_PYTHON_INSTALL_TIMEOUT_MS)}). If this mentions a download, check your internet connection and retry.`);
|
|
186
203
|
}
|
|
187
204
|
let found;
|
|
188
205
|
try {
|
|
@@ -191,11 +208,13 @@ async function ensureFleetPythonLocked(root, deps) {
|
|
|
191
208
|
env,
|
|
192
209
|
encoding: "utf8",
|
|
193
210
|
windowsHide: true,
|
|
211
|
+
timeout: FLEET_PYTHON_FIND_TIMEOUT_MS,
|
|
212
|
+
killSignal: "SIGKILL",
|
|
194
213
|
});
|
|
195
214
|
found = (Buffer.isBuffer(output) ? output.toString("utf8") : output).trim();
|
|
196
215
|
}
|
|
197
216
|
catch (error) {
|
|
198
|
-
throw expectedError(`HQ Fleet Local installed its private Python but could not locate it (${causeOf(error)}).`);
|
|
217
|
+
throw expectedError(`HQ Fleet Local installed its private Python but could not locate it (${causeOf(error, FLEET_PYTHON_FIND_TIMEOUT_MS)}).`);
|
|
199
218
|
}
|
|
200
219
|
const python = path.resolve(found);
|
|
201
220
|
if (!found || !fs.existsSync(python) || !isInside(pythonDir, python, platform)) {
|
package/dist/lib/bot/probe.d.ts
CHANGED
|
@@ -20,7 +20,9 @@ export declare const PROBE_SYSTEM_PROMPT = "You are an HQ bot being checked befo
|
|
|
20
20
|
/** A probe that has not answered by then is reported as `transient`. */
|
|
21
21
|
export declare const PROBE_DEFAULT_TIMEOUT_MS = 120000;
|
|
22
22
|
/** The plain sentence for a failed probe: what is wrong and the one fix. */
|
|
23
|
-
export declare function probeFailureText(cls: RuntimeFailureClass, runtime: BotRuntimeId | string
|
|
23
|
+
export declare function probeFailureText(cls: RuntimeFailureClass, runtime: BotRuntimeId | string, usageLimit?: {
|
|
24
|
+
resetsAt?: string;
|
|
25
|
+
}): string;
|
|
24
26
|
export interface RuntimeProbeResult {
|
|
25
27
|
ok: boolean;
|
|
26
28
|
/** null when ok; otherwise the same classes a bot's failure DM uses. */
|
|
@@ -30,6 +32,10 @@ export interface RuntimeProbeResult {
|
|
|
30
32
|
runtime: BotRuntimeId;
|
|
31
33
|
/** The model asked for; null = the runtime's own default. */
|
|
32
34
|
model: string | null;
|
|
35
|
+
/** The tool's plan ran out of usage (class `transient`). */
|
|
36
|
+
usageLimit?: boolean;
|
|
37
|
+
/** When that limit resets, as the tool wrote it ("9pm (Asia/Dhaka)"). */
|
|
38
|
+
resetsAt?: string;
|
|
33
39
|
/** The CLI was too old for `from`, and the probe answered on `to`. */
|
|
34
40
|
modelFallback?: {
|
|
35
41
|
from: string;
|
package/dist/lib/bot/probe.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
16
|
import * as os from "node:os";
|
|
17
17
|
import * as path from "node:path";
|
|
18
|
-
import { classifyRuntimeError } from "./runtime-failure.js";
|
|
18
|
+
import { classifyRuntimeError, isRuntimeUsageLimit, usageLimitResetsAt } from "./runtime-failure.js";
|
|
19
19
|
import { runtimeDisplayName } from "./runtime-sign-in.js";
|
|
20
20
|
import { RuntimeError, runRuntimeTurn, runtimeFor } from "./runtime/index.js";
|
|
21
21
|
import { turnEnv } from "./run.js";
|
|
@@ -24,8 +24,12 @@ export const PROBE_SYSTEM_PROMPT = "You are an HQ bot being checked before it is
|
|
|
24
24
|
/** A probe that has not answered by then is reported as `transient`. */
|
|
25
25
|
export const PROBE_DEFAULT_TIMEOUT_MS = 120_000;
|
|
26
26
|
/** The plain sentence for a failed probe: what is wrong and the one fix. */
|
|
27
|
-
export function probeFailureText(cls, runtime) {
|
|
27
|
+
export function probeFailureText(cls, runtime, usageLimit) {
|
|
28
28
|
const tool = runtimeDisplayName(runtime);
|
|
29
|
+
if (cls === "transient" && usageLimit) {
|
|
30
|
+
const when = usageLimit.resetsAt ? ` It resets ${usageLimit.resetsAt}.` : "";
|
|
31
|
+
return `${tool} has hit its plan's usage limit.${when} Check again after it resets, or pick another tool.`;
|
|
32
|
+
}
|
|
29
33
|
switch (cls) {
|
|
30
34
|
case "signed-out":
|
|
31
35
|
return `${tool} is not signed in on this computer. Sign in to ${tool} in HQ, then check again.`;
|
|
@@ -84,11 +88,15 @@ export async function probeBotRuntime(opts) {
|
|
|
84
88
|
const raw = err instanceof RuntimeError ? `${err.message}\n${err.stderrExcerpt}` : err instanceof Error ? err.message : String(err);
|
|
85
89
|
// Same rule as a bot turn: the runtime's reported error decides, never a tail of its output.
|
|
86
90
|
const cls = classifyRuntimeError(err);
|
|
91
|
+
const limited = cls === "transient" && isRuntimeUsageLimit(raw);
|
|
92
|
+
const resetsAt = limited ? usageLimitResetsAt(raw) : undefined;
|
|
87
93
|
return {
|
|
88
94
|
ok: false,
|
|
89
95
|
class: cls,
|
|
90
|
-
detail: probeFailureText(cls, opts.runtime),
|
|
96
|
+
detail: probeFailureText(cls, opts.runtime, limited ? { resetsAt } : undefined),
|
|
91
97
|
...base,
|
|
98
|
+
...(limited ? { usageLimit: true } : {}),
|
|
99
|
+
...(resetsAt ? { resetsAt } : {}),
|
|
92
100
|
diagnostic: raw.slice(0, 400),
|
|
93
101
|
durationMs: now() - started,
|
|
94
102
|
};
|
package/dist/lib/bot/run.d.ts
CHANGED
|
@@ -71,6 +71,15 @@ export declare const API_BACKOFF_CAP_MS: number;
|
|
|
71
71
|
/** Honor Retry-After up to this; a bogus 30-day header must not park the bot. */
|
|
72
72
|
export declare const API_RETRY_AFTER_CAP_MS: number;
|
|
73
73
|
export declare const API_RETRY_AFTER_FLOOR_MS = 1000;
|
|
74
|
+
/**
|
|
75
|
+
* Delays between startup identity-check tries when HQ cannot be reached
|
|
76
|
+
* (five tries over about three minutes). After the last one the bot stops in
|
|
77
|
+
* a visible failed state the person can retry.
|
|
78
|
+
*/
|
|
79
|
+
export declare const STARTUP_VERIFY_RETRY_DELAYS_MS: readonly number[];
|
|
80
|
+
export declare function isTransientApiError(err: unknown): boolean;
|
|
81
|
+
/** Plain failed-state reason after the startup retries run out. */
|
|
82
|
+
export declare function startupUnreachableReason(name: string): string;
|
|
74
83
|
/**
|
|
75
84
|
* How long to wait after a failed inbox poll or heartbeat.
|
|
76
85
|
* 429/5xx (and transport errors) back off exponentially, honouring Retry-After
|