@indigoai-us/hq-cli 5.345.199 → 5.345.200
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 +26 -0
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +6 -4
- package/dist/command-catalog.generated.d.ts +44 -0
- package/dist/command-catalog.generated.js +52 -0
- package/dist/commands/bot-runtime.d.ts +35 -0
- package/dist/commands/bot-runtime.js +115 -0
- package/dist/commands/bot.d.ts +6 -0
- package/dist/commands/bot.js +39 -10
- package/dist/commands/creators.js +2 -1
- package/dist/commands/library-remove.js +2 -1
- package/dist/commands/library.js +5 -4
- package/dist/commands/mesh.d.ts +0 -7
- package/dist/commands/mesh.js +5 -1
- package/dist/commands/sync-held.js +2 -2
- package/dist/lib/bot/fleet-download.d.ts +27 -0
- package/dist/lib/bot/fleet-download.js +162 -0
- package/dist/lib/bot/fleet-local.d.ts +51 -2
- package/dist/lib/bot/fleet-local.js +177 -74
- package/dist/lib/bot/fleet-python.d.ts +2 -0
- package/dist/lib/bot/fleet-python.js +29 -0
- package/dist/lib/lanes/mesh-events.js +3 -1
- package/dist/lib/mesh/api.js +2 -1
- package/dist/lib/mesh/live/daemon/run.d.ts +0 -10
- package/dist/lib/mesh/live/daemon/run.js +3 -0
- package/dist/lib/mesh/live/emit.d.ts +2 -11
- package/dist/lib/mesh/live/emit.js +20 -2
- package/dist/lib/mesh/live/flush.d.ts +2 -0
- package/dist/lib/mesh/live/flush.js +34 -3
- package/dist/lib/mesh/live/reporting-privacy.d.ts +7 -0
- package/dist/lib/mesh/live/reporting-privacy.js +47 -0
- package/dist/lib/work-context/company.d.ts +2 -3
- package/dist/lib/work-context/company.js +4 -22
- package/dist/secrets-env-action.js +5 -2
- package/dist/utils/cognito-callback-port.d.ts +2 -0
- package/dist/utils/cognito-callback-port.js +10 -5
- package/dist/utils/cognito-config.js +6 -1
- package/dist/utils/cognito-session.js +6 -1
- package/dist/utils/interactive-caller.d.ts +8 -0
- package/dist/utils/interactive-caller.js +10 -0
- package/dist/utils/login-browser.js +2 -1
- package/package.json +7 -7
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** A download that receives no bytes for this long is treated as stalled and retried. */
|
|
2
|
+
export declare const FLEET_DOWNLOAD_STALL_MS = 90000;
|
|
3
|
+
/** How many times a stalled or broken download is resumed before giving up. */
|
|
4
|
+
export declare const FLEET_DOWNLOAD_MAX_RESUMES = 5;
|
|
5
|
+
export interface FleetDownloadProgress {
|
|
6
|
+
name: string;
|
|
7
|
+
received: number;
|
|
8
|
+
total: number | null;
|
|
9
|
+
}
|
|
10
|
+
export interface FleetDownloadOptions {
|
|
11
|
+
fetch?: typeof fetch;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
stallMs?: number;
|
|
14
|
+
maxResumes?: number;
|
|
15
|
+
onProgress?: (progress: FleetDownloadProgress) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Download `url` into `destination`, writing through `<destination>.part` and
|
|
19
|
+
* resuming from the bytes already there. Every attempt that receives nothing
|
|
20
|
+
* for `stallMs` is retried from where it stopped, up to `maxResumes` times.
|
|
21
|
+
* The file is renamed into place only when the body ended cleanly and, when
|
|
22
|
+
* the server told us its size, the sizes match.
|
|
23
|
+
*/
|
|
24
|
+
export declare function downloadFleetFile(url: string, destination: string, options?: FleetDownloadOptions): Promise<string>;
|
|
25
|
+
/** sha256 of a file on disk, streamed. */
|
|
26
|
+
export declare function fileSha256(file: string): string;
|
|
27
|
+
//# sourceMappingURL=fleet-download.d.ts.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
/** A download that receives no bytes for this long is treated as stalled and retried. */
|
|
5
|
+
export const FLEET_DOWNLOAD_STALL_MS = 90_000;
|
|
6
|
+
/** How many times a stalled or broken download is resumed before giving up. */
|
|
7
|
+
export const FLEET_DOWNLOAD_MAX_RESUMES = 5;
|
|
8
|
+
function expectedError(message) {
|
|
9
|
+
return Object.assign(new Error(message), { expected: true });
|
|
10
|
+
}
|
|
11
|
+
function partFileSize(file) {
|
|
12
|
+
try {
|
|
13
|
+
return fs.statSync(file).size;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function contentLength(response) {
|
|
20
|
+
const raw = response.headers.get("content-length");
|
|
21
|
+
if (!raw)
|
|
22
|
+
return null;
|
|
23
|
+
const value = Number.parseInt(raw, 10);
|
|
24
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
25
|
+
}
|
|
26
|
+
function contentRangeTotal(response) {
|
|
27
|
+
const raw = response.headers.get("content-range");
|
|
28
|
+
const match = raw ? /\/(\d+)\s*$/.exec(raw) : null;
|
|
29
|
+
if (!match)
|
|
30
|
+
return null;
|
|
31
|
+
const value = Number.parseInt(match[1], 10);
|
|
32
|
+
return Number.isFinite(value) ? value : null;
|
|
33
|
+
}
|
|
34
|
+
async function pumpBody(response, fd, offset, name, total, stallMs, onProgress) {
|
|
35
|
+
if (!response.body) {
|
|
36
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
37
|
+
fs.writeSync(fd, bytes, 0, bytes.length, offset);
|
|
38
|
+
const received = offset + bytes.length;
|
|
39
|
+
onProgress?.({ name, received, total });
|
|
40
|
+
return received;
|
|
41
|
+
}
|
|
42
|
+
const reader = response.body.getReader();
|
|
43
|
+
let received = offset;
|
|
44
|
+
let lastReport = 0;
|
|
45
|
+
for (;;) {
|
|
46
|
+
let timer;
|
|
47
|
+
const stalled = new Promise((_resolve, reject) => {
|
|
48
|
+
timer = setTimeout(() => reject(expectedError(`stalled: no data for ${Math.round(stallMs / 1000)}s`)), stallMs);
|
|
49
|
+
});
|
|
50
|
+
let chunk;
|
|
51
|
+
try {
|
|
52
|
+
chunk = await Promise.race([reader.read(), stalled]);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
void reader.cancel().catch(() => undefined);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
if (timer)
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
}
|
|
62
|
+
if (chunk.done)
|
|
63
|
+
break;
|
|
64
|
+
fs.writeSync(fd, chunk.value, 0, chunk.value.length, received);
|
|
65
|
+
received += chunk.value.length;
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
if (now - lastReport >= 250 || (total !== null && received >= total)) {
|
|
68
|
+
lastReport = now;
|
|
69
|
+
onProgress?.({ name, received, total });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
onProgress?.({ name, received, total });
|
|
73
|
+
return received;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Download `url` into `destination`, writing through `<destination>.part` and
|
|
77
|
+
* resuming from the bytes already there. Every attempt that receives nothing
|
|
78
|
+
* for `stallMs` is retried from where it stopped, up to `maxResumes` times.
|
|
79
|
+
* The file is renamed into place only when the body ended cleanly and, when
|
|
80
|
+
* the server told us its size, the sizes match.
|
|
81
|
+
*/
|
|
82
|
+
export async function downloadFleetFile(url, destination, options = {}) {
|
|
83
|
+
const fetcher = options.fetch ?? fetch;
|
|
84
|
+
const stallMs = options.stallMs ?? FLEET_DOWNLOAD_STALL_MS;
|
|
85
|
+
const maxResumes = options.maxResumes ?? FLEET_DOWNLOAD_MAX_RESUMES;
|
|
86
|
+
const name = path.basename(destination);
|
|
87
|
+
const part = `${destination}.part`;
|
|
88
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true, mode: 0o700 });
|
|
89
|
+
let lastFailure = null;
|
|
90
|
+
for (let attempt = 0; attempt <= maxResumes; attempt += 1) {
|
|
91
|
+
const have = partFileSize(part);
|
|
92
|
+
const headers = { ...options.headers };
|
|
93
|
+
if (have > 0)
|
|
94
|
+
headers.Range = `bytes=${have}-`;
|
|
95
|
+
let response;
|
|
96
|
+
try {
|
|
97
|
+
response = await (Object.keys(headers).length > 0 ? fetcher(url, { headers }) : fetcher(url));
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
lastFailure = error;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
let offset = have;
|
|
104
|
+
let total;
|
|
105
|
+
if (response.status === 206 && have > 0) {
|
|
106
|
+
total = contentRangeTotal(response) ?? (contentLength(response) !== null ? have + contentLength(response) : null);
|
|
107
|
+
}
|
|
108
|
+
else if (response.status === 416 && have > 0) {
|
|
109
|
+
void response.body?.cancel().catch(() => undefined);
|
|
110
|
+
fs.renameSync(part, destination);
|
|
111
|
+
return destination;
|
|
112
|
+
}
|
|
113
|
+
else if (response.ok) {
|
|
114
|
+
offset = 0;
|
|
115
|
+
total = contentLength(response);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
void response.body?.cancel().catch(() => undefined);
|
|
119
|
+
throw expectedError(`Could not download ${name}: HQ storage returned HTTP ${response.status}.`);
|
|
120
|
+
}
|
|
121
|
+
const fd = fs.openSync(part, offset === 0 ? "w" : "r+", 0o600);
|
|
122
|
+
let received;
|
|
123
|
+
try {
|
|
124
|
+
received = await pumpBody(response, fd, offset, name, total, stallMs, options.onProgress);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
fs.closeSync(fd);
|
|
128
|
+
lastFailure = error;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
fs.closeSync(fd);
|
|
132
|
+
if (total !== null && received !== total) {
|
|
133
|
+
if (received > total)
|
|
134
|
+
fs.rmSync(part, { force: true });
|
|
135
|
+
lastFailure = expectedError(`ended early at ${received} of ${total} bytes`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
fs.renameSync(part, destination);
|
|
139
|
+
return destination;
|
|
140
|
+
}
|
|
141
|
+
const cause = lastFailure instanceof Error ? lastFailure.message : String(lastFailure ?? "unknown error");
|
|
142
|
+
throw expectedError(`Could not download ${name} after ${maxResumes + 1} attempts (${cause}). Check your internet connection and retry; the download continues where it stopped.`);
|
|
143
|
+
}
|
|
144
|
+
/** sha256 of a file on disk, streamed. */
|
|
145
|
+
export function fileSha256(file) {
|
|
146
|
+
const hash = crypto.createHash("sha256");
|
|
147
|
+
const fd = fs.openSync(file, "r");
|
|
148
|
+
try {
|
|
149
|
+
const chunk = Buffer.allocUnsafe(1 << 20);
|
|
150
|
+
for (;;) {
|
|
151
|
+
const read = fs.readSync(fd, chunk, 0, chunk.length, null);
|
|
152
|
+
if (read === 0)
|
|
153
|
+
break;
|
|
154
|
+
hash.update(chunk.subarray(0, read));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
fs.closeSync(fd);
|
|
159
|
+
}
|
|
160
|
+
return hash.digest("hex");
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=fleet-download.js.map
|
|
@@ -65,6 +65,9 @@ export interface FleetLocalInstallDeps {
|
|
|
65
65
|
now?: () => Date;
|
|
66
66
|
release?: FleetLocalReleasePin;
|
|
67
67
|
publicKey?: string | readonly string[];
|
|
68
|
+
onProgress?: FleetInstallProgressListener;
|
|
69
|
+
/** How long a download may go without bytes before it is resumed (tests). */
|
|
70
|
+
downloadStallMs?: number;
|
|
68
71
|
}
|
|
69
72
|
export interface FleetLocalProfileInput {
|
|
70
73
|
agentUid: string;
|
|
@@ -113,6 +116,7 @@ export interface FleetLocalBootstrapDeps {
|
|
|
113
116
|
spawn?: FleetLocalSpawn;
|
|
114
117
|
start?: boolean;
|
|
115
118
|
python?: FleetPython;
|
|
119
|
+
onProgress?: FleetInstallProgressListener;
|
|
116
120
|
}
|
|
117
121
|
interface SharedFleetRuntime {
|
|
118
122
|
id: string;
|
|
@@ -257,9 +261,35 @@ export declare function installTargetFleetLocalDistribution(deps?: Omit<FleetLoc
|
|
|
257
261
|
target?: FleetLocalTargetRelease;
|
|
258
262
|
now?: () => number;
|
|
259
263
|
}): Promise<FleetLocalInstallation>;
|
|
264
|
+
/** What an install is doing right now, for a progress bar or a log line. */
|
|
265
|
+
export type FleetInstallProgress = {
|
|
266
|
+
phase: "download";
|
|
267
|
+
file: string;
|
|
268
|
+
received: number;
|
|
269
|
+
total: number | null;
|
|
270
|
+
} | {
|
|
271
|
+
phase: "verify";
|
|
272
|
+
} | {
|
|
273
|
+
phase: "python";
|
|
274
|
+
} | {
|
|
275
|
+
phase: "extract";
|
|
276
|
+
} | {
|
|
277
|
+
phase: "environment";
|
|
278
|
+
} | {
|
|
279
|
+
phase: "packages";
|
|
280
|
+
} | {
|
|
281
|
+
phase: "ready";
|
|
282
|
+
step: "download" | "runtime";
|
|
283
|
+
cached: boolean;
|
|
284
|
+
};
|
|
285
|
+
export type FleetInstallProgressListener = (progress: FleetInstallProgress) => void;
|
|
260
286
|
export declare const FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS = 30000;
|
|
261
|
-
|
|
262
|
-
export declare const
|
|
287
|
+
/** One machine-wide lock: concurrent creates and adopts share one download instead of repeating it. */
|
|
288
|
+
export declare const FLEET_LOCAL_DISTRIBUTION_LOCK_NAME = "hq-fleet-local-distribution";
|
|
289
|
+
export declare const FLEET_LOCAL_DISTRIBUTION_LOCK_WAIT_MS: number;
|
|
290
|
+
export declare const FLEET_LOCAL_DISTRIBUTION_LOCK_STALE_MS: number;
|
|
291
|
+
/** Whether the pinned release is installed on this machine (no download needed). */
|
|
292
|
+
export declare function fleetLocalDistributionInstalled(release?: FleetLocalReleasePin, root?: string): boolean;
|
|
263
293
|
export declare function installPinnedFleetLocalDistribution(deps?: FleetLocalInstallDeps): Promise<FleetLocalInstallation>;
|
|
264
294
|
/** The process id the bot's gateway recorded, when its file reads; the caller checks whether it is alive. */
|
|
265
295
|
export declare function fleetLocalGatewayPid(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome">): number | null;
|
|
@@ -339,6 +369,25 @@ export declare function windowsExtractionPathProblem(destination: string, longes
|
|
|
339
369
|
export declare function importFleetLocalMachineCredential(agentUid: string, machineCredentials: string, deps?: FleetLocalControlDeps, opts?: {
|
|
340
370
|
keepSource?: boolean;
|
|
341
371
|
}): void;
|
|
372
|
+
export interface FleetRuntimeInstallResult {
|
|
373
|
+
release: FleetLocalReleasePin;
|
|
374
|
+
distributionDir: string;
|
|
375
|
+
runtimeDir: string;
|
|
376
|
+
runtimeId: string;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Everything a bot needs that is shared by every bot on this machine: the
|
|
380
|
+
* pinned distribution and runtime archive, the private Python, and the built
|
|
381
|
+
* runtime environment. Safe to run any number of times; a complete install
|
|
382
|
+
* returns at once.
|
|
383
|
+
*/
|
|
384
|
+
export declare function ensureFleetRuntimeInstalled(installation: FleetLocalInstallation, deps?: FleetLocalBootstrapDeps): Promise<FleetRuntimeInstallResult>;
|
|
385
|
+
/** Whether the shared runtime for this release is already built on this machine. */
|
|
386
|
+
export declare function fleetRuntimeReady(installation: Pick<FleetLocalInstallation, "release">, deps?: {
|
|
387
|
+
fleetHome?: string;
|
|
388
|
+
platform?: NodeJS.Platform;
|
|
389
|
+
python?: FleetPython;
|
|
390
|
+
}): boolean;
|
|
342
391
|
export declare function bootstrapFleetLocalBot(input: FleetLocalProfileInput & {
|
|
343
392
|
machineCredentials?: string;
|
|
344
393
|
}, deps?: FleetLocalBootstrapDeps): Promise<FleetLocalBootstrapResult>;
|