@indigoai-us/hq-cli 5.345.189 → 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 +34 -0
- package/dist/command-catalog.generated.d.ts +29 -0
- package/dist/command-catalog.generated.js +36 -0
- package/dist/commands/cloud.js +16 -2
- package/dist/commands/files.d.ts +18 -0
- package/dist/commands/files.js +73 -0
- package/dist/lib/bot/fleet-local.d.ts +40 -0
- package/dist/lib/bot/fleet-local.js +195 -38
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.190] — 2026-11-01
|
|
6
|
+
- `hq files purge <path> --version-id <id>` permanently deletes one old version of a vault file, after a confirmation prompt.
|
|
7
|
+
- `hq bot create` / `hq bot start` on Windows: the shared HQ Fleet Runtime now
|
|
8
|
+
installs under `%USERPROFILE%\.hq-fleet\runtimes\<16 hex>\{v,s\r,s\d}` instead
|
|
9
|
+
of the 64-character identity plus `sources\runtime`, so the runtime's deepest
|
|
10
|
+
files stay under the 260-character Windows path limit when long paths are
|
|
11
|
+
off. Before, extraction stopped partway with "HQ Fleet Local runtime
|
|
12
|
+
extraction failed." Runtimes already installed under the old layout keep
|
|
13
|
+
working, and a half-extracted old one is removed. macOS and Linux paths are
|
|
14
|
+
unchanged.
|
|
15
|
+
- HQ Fleet Local install steps now include the last lines of the failing
|
|
16
|
+
command's stderr in their error, and on Windows the CLI checks the runtime
|
|
17
|
+
archive's longest path before extracting and names the path limit and
|
|
18
|
+
`LongPathsEnabled` setting when it would not fit.
|
|
19
|
+
- The CLI now warns on stderr when the HQ Fleet Local release manifest's
|
|
20
|
+
`compatibility.minimum_hq_cli_version` is newer than the running CLI. It does
|
|
21
|
+
not block the install.
|
|
22
|
+
- `hq-hook`: the `block-credential-file-read` port follows the fixed bash guard
|
|
23
|
+
(hq-core-staging #1753, sha256 7c6a8cfa) and its pin moves to that version.
|
|
24
|
+
Zero-padded brace ranges are read in base 10 and long or stepped ranges
|
|
25
|
+
block; letter ranges expand exactly; the mktemp scan replacement is literal
|
|
26
|
+
(no `patsub_replacement`) with capped loops; `function name { ... }` bodies
|
|
27
|
+
are analysed; extglob groups are an unknown word part; and `ls`, `stat` and
|
|
28
|
+
the other metadata commands are not trusted after an alias, hash, enable,
|
|
29
|
+
PATH change, a write into a bin directory, or when named by a path outside
|
|
30
|
+
/bin, /usr/bin, /sbin and /usr/sbin. The port also picks up the guard's
|
|
31
|
+
cygpath and `upstream-pin.env` changes. The vendored fixture and corpus are
|
|
32
|
+
refreshed. The port flag stays off.
|
|
33
|
+
- `hq sync now`: when the push leg holds files after an account sign-in change
|
|
34
|
+
on this computer, the push line now shows the held count, prints how to
|
|
35
|
+
release them (`hq sync held`, `hq sync release-held`), skips the "Sync now
|
|
36
|
+
complete" line and exits 1. Before, it printed "0 uploaded, 0 skipped" and a
|
|
37
|
+
success line (feedback_2356e54f).
|
|
38
|
+
|
|
5
39
|
## [5.345.189] — 2026-11-01
|
|
6
40
|
- A local bot that cannot reach HQ when it starts now retries the identity check up to five times
|
|
7
41
|
(waits of 5 s, 15 s, 45 s and 2 min) on a network error (connection refused, reset, DNS failure,
|
|
@@ -3243,6 +3243,35 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3243
3243
|
readonly hidden: false;
|
|
3244
3244
|
}];
|
|
3245
3245
|
readonly subcommands: readonly [];
|
|
3246
|
+
}, {
|
|
3247
|
+
readonly name: "purge";
|
|
3248
|
+
readonly description: "Permanently delete ONE retained (noncurrent) version of an exact vault key, for example a version that held data which must not live in HQ. Owner only; audited; cannot be undone. Delete the file first if the version is current. Use --personal for your personal vault.";
|
|
3249
|
+
readonly aliases: readonly [];
|
|
3250
|
+
readonly hidden: false;
|
|
3251
|
+
readonly helpOption: true;
|
|
3252
|
+
readonly helpCommand: false;
|
|
3253
|
+
readonly usage: "[options] <path>";
|
|
3254
|
+
readonly helpText: readonly [];
|
|
3255
|
+
readonly arguments: readonly [{
|
|
3256
|
+
readonly name: "path";
|
|
3257
|
+
readonly required: true;
|
|
3258
|
+
readonly variadic: false;
|
|
3259
|
+
readonly description: "";
|
|
3260
|
+
}];
|
|
3261
|
+
readonly options: readonly [{
|
|
3262
|
+
readonly flags: "--personal";
|
|
3263
|
+
readonly description: "Target your own personal vault instead of a company vault (mutually exclusive with --company)";
|
|
3264
|
+
readonly hidden: false;
|
|
3265
|
+
}, {
|
|
3266
|
+
readonly flags: "--version-id <id>";
|
|
3267
|
+
readonly description: "Exact version ID to purge (see `hq files versions`)";
|
|
3268
|
+
readonly hidden: false;
|
|
3269
|
+
}, {
|
|
3270
|
+
readonly flags: "-y, --yes";
|
|
3271
|
+
readonly description: "Skip the confirmation prompt (for scripts)";
|
|
3272
|
+
readonly hidden: false;
|
|
3273
|
+
}];
|
|
3274
|
+
readonly subcommands: readonly [];
|
|
3246
3275
|
}, {
|
|
3247
3276
|
readonly name: "trash";
|
|
3248
3277
|
readonly description: "List deleted vault keys retained as tombstones. Use --personal for your personal vault.";
|
|
@@ -3912,6 +3912,42 @@ export const COMMAND_CATALOG = [
|
|
|
3912
3912
|
],
|
|
3913
3913
|
"subcommands": []
|
|
3914
3914
|
},
|
|
3915
|
+
{
|
|
3916
|
+
"name": "purge",
|
|
3917
|
+
"description": "Permanently delete ONE retained (noncurrent) version of an exact vault key, for example a version that held data which must not live in HQ. Owner only; audited; cannot be undone. Delete the file first if the version is current. Use --personal for your personal vault.",
|
|
3918
|
+
"aliases": [],
|
|
3919
|
+
"hidden": false,
|
|
3920
|
+
"helpOption": true,
|
|
3921
|
+
"helpCommand": false,
|
|
3922
|
+
"usage": "[options] <path>",
|
|
3923
|
+
"helpText": [],
|
|
3924
|
+
"arguments": [
|
|
3925
|
+
{
|
|
3926
|
+
"name": "path",
|
|
3927
|
+
"required": true,
|
|
3928
|
+
"variadic": false,
|
|
3929
|
+
"description": ""
|
|
3930
|
+
}
|
|
3931
|
+
],
|
|
3932
|
+
"options": [
|
|
3933
|
+
{
|
|
3934
|
+
"flags": "--personal",
|
|
3935
|
+
"description": "Target your own personal vault instead of a company vault (mutually exclusive with --company)",
|
|
3936
|
+
"hidden": false
|
|
3937
|
+
},
|
|
3938
|
+
{
|
|
3939
|
+
"flags": "--version-id <id>",
|
|
3940
|
+
"description": "Exact version ID to purge (see `hq files versions`)",
|
|
3941
|
+
"hidden": false
|
|
3942
|
+
},
|
|
3943
|
+
{
|
|
3944
|
+
"flags": "-y, --yes",
|
|
3945
|
+
"description": "Skip the confirmation prompt (for scripts)",
|
|
3946
|
+
"hidden": false
|
|
3947
|
+
}
|
|
3948
|
+
],
|
|
3949
|
+
"subcommands": []
|
|
3950
|
+
},
|
|
3915
3951
|
{
|
|
3916
3952
|
"name": "trash",
|
|
3917
3953
|
"description": "List deleted vault keys retained as tombstones. Use --personal for your personal vault.",
|
package/dist/commands/cloud.js
CHANGED
|
@@ -2351,12 +2351,19 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
2351
2351
|
}
|
|
2352
2352
|
const pushProgressCompany = personalMode ? null : (pushSlug ?? null);
|
|
2353
2353
|
const planLimitTracker = createPlanLimitRefusalTracker(companySlugForBilling(pushProgressCompany));
|
|
2354
|
+
// feedback_2356e54f: the push leg held files after an account switch and
|
|
2355
|
+
// still printed "0 uploaded, 0 skipped" and "Sync now complete".
|
|
2356
|
+
const pushIdentityHeld = createIdentityHeldTracker();
|
|
2357
|
+
const pushRecord = trackingProgressRecorder(planLimitTracker, pushProgressCompany);
|
|
2354
2358
|
const pushResult = await share({
|
|
2355
2359
|
paths: pushPaths,
|
|
2356
2360
|
company: targetCompany,
|
|
2357
2361
|
vaultConfig,
|
|
2358
2362
|
hqRoot,
|
|
2359
|
-
progressRecorder:
|
|
2363
|
+
progressRecorder: (event) => {
|
|
2364
|
+
pushIdentityHeld.observe(event);
|
|
2365
|
+
pushRecord(event);
|
|
2366
|
+
},
|
|
2360
2367
|
skipUnchanged: true,
|
|
2361
2368
|
serverOwnedPathPolicy: "skip",
|
|
2362
2369
|
propagateDeletes: true,
|
|
@@ -2370,7 +2377,9 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
2370
2377
|
});
|
|
2371
2378
|
const pushRefused = planLimitTracker.summary();
|
|
2372
2379
|
const pushUploadFailed = readUploadFailures(pushResult);
|
|
2380
|
+
const pushHeldCount = pushIdentityHeld.count();
|
|
2373
2381
|
const pushStatus = pushResult.aborted ||
|
|
2382
|
+
pushHeldCount > 0 ||
|
|
2374
2383
|
pushResult.filesExcludedByScope > 0 ||
|
|
2375
2384
|
(pushResult.filesRefusedStale ?? 0) > 0 ||
|
|
2376
2385
|
pushUploadFailed.count > 0 ||
|
|
@@ -2380,6 +2389,7 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
2380
2389
|
console.log(` ${pushStatus} ` +
|
|
2381
2390
|
`${pushResult.filesUploaded} uploaded, ${pushResult.filesSkipped} skipped, ` +
|
|
2382
2391
|
`${pushResult.filesDeleted} deleted, ${pushResult.filesExcludedByScope} scope-excluded` +
|
|
2392
|
+
(pushHeldCount > 0 ? `, ${pushHeldCount} held` : "") +
|
|
2383
2393
|
(pushResult.aborted ? " — aborted" : "") +
|
|
2384
2394
|
(pushRefused
|
|
2385
2395
|
? ` — ${describeRefusedCount(pushRefused)} refused (new files paused)`
|
|
@@ -2397,6 +2407,9 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
2397
2407
|
for (const line of uploadFailedLines(pushUploadFailed.count, pushUploadFailed.paths)) {
|
|
2398
2408
|
console.log(chalk.yellow(line));
|
|
2399
2409
|
}
|
|
2410
|
+
for (const line of identityHeldLines(pushHeldCount)) {
|
|
2411
|
+
console.log(chalk.yellow(line));
|
|
2412
|
+
}
|
|
2400
2413
|
if (pushResult.aborted) {
|
|
2401
2414
|
console.log(chalk.yellow("\n⚠ Sync now aborted on push leg; pull skipped."));
|
|
2402
2415
|
throw new SyncExitError(1);
|
|
@@ -2519,7 +2532,8 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
2519
2532
|
}
|
|
2520
2533
|
if (pushResult.filesExcludedByScope > 0 ||
|
|
2521
2534
|
(pushResult.filesRefusedStale ?? 0) > 0 ||
|
|
2522
|
-
pushUploadFailed.count > 0
|
|
2535
|
+
pushUploadFailed.count > 0 ||
|
|
2536
|
+
pushHeldCount > 0) {
|
|
2523
2537
|
console.log(chalk.yellow("\n⚠ Sync now finished, but one or more requested uploads were refused."));
|
|
2524
2538
|
throw new SyncExitError(1, { health: "succeeded" });
|
|
2525
2539
|
}
|
package/dist/commands/files.d.ts
CHANGED
|
@@ -242,6 +242,24 @@ export declare function runFilesRestore(params: {
|
|
|
242
242
|
}, deps?: {
|
|
243
243
|
confirm?: ConfirmFn;
|
|
244
244
|
}): Promise<FilesRestoreResponse | undefined>;
|
|
245
|
+
export interface FilesPurgeResponse {
|
|
246
|
+
key: string;
|
|
247
|
+
purgedVersionId: string;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* feedback_fc711fc3: permanently delete one noncurrent version. The server
|
|
251
|
+
* (hq-pro POST /v1/files/restore, action "purge") enforces owner-only access,
|
|
252
|
+
* refuses the current version and delete markers, and audits before deleting.
|
|
253
|
+
*/
|
|
254
|
+
export declare function runFilesPurge(params: {
|
|
255
|
+
key: string;
|
|
256
|
+
versionId: string;
|
|
257
|
+
yes: boolean;
|
|
258
|
+
personal: boolean;
|
|
259
|
+
companySlug: string | undefined;
|
|
260
|
+
}, deps?: {
|
|
261
|
+
confirm?: ConfirmFn;
|
|
262
|
+
}): Promise<FilesPurgeResponse | undefined>;
|
|
245
263
|
export declare function runFilesTrash(params: {
|
|
246
264
|
prefix: string;
|
|
247
265
|
cursor?: string;
|
package/dist/commands/files.js
CHANGED
|
@@ -731,6 +731,30 @@ export function registerFilesCommand(program) {
|
|
|
731
731
|
process.exit(1);
|
|
732
732
|
}
|
|
733
733
|
});
|
|
734
|
+
files
|
|
735
|
+
.command("purge <path>")
|
|
736
|
+
.description("Permanently delete ONE retained (noncurrent) version of an exact vault key, for example a version that held data which must not live in HQ. Owner only; audited; cannot be undone. Delete the file first if the version is current. Use --personal for your personal vault.")
|
|
737
|
+
.option("--personal", "Target your own personal vault instead of a company vault (mutually exclusive with --company)")
|
|
738
|
+
.requiredOption("--version-id <id>", "Exact version ID to purge (see `hq files versions`)")
|
|
739
|
+
.option("-y, --yes", "Skip the confirmation prompt (for scripts)")
|
|
740
|
+
.action(async (path, opts) => {
|
|
741
|
+
try {
|
|
742
|
+
const companySlug = files.opts().company;
|
|
743
|
+
const personal = opts.personal === true;
|
|
744
|
+
assertRecoveryScope(personal, companySlug);
|
|
745
|
+
await runFilesPurge({
|
|
746
|
+
key: path,
|
|
747
|
+
versionId: opts.versionId,
|
|
748
|
+
yes: opts.yes === true,
|
|
749
|
+
personal,
|
|
750
|
+
companySlug,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
catch (err) {
|
|
754
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
734
758
|
files
|
|
735
759
|
.command("trash")
|
|
736
760
|
.description("List deleted vault keys retained as tombstones. Use --personal for your personal vault.")
|
|
@@ -1767,6 +1791,55 @@ export async function runFilesRestore(params, deps = {}) {
|
|
|
1767
1791
|
console.log(chalk.dim("New version: " + data.newVersionId));
|
|
1768
1792
|
return data;
|
|
1769
1793
|
}
|
|
1794
|
+
/**
|
|
1795
|
+
* feedback_fc711fc3: permanently delete one noncurrent version. The server
|
|
1796
|
+
* (hq-pro POST /v1/files/restore, action "purge") enforces owner-only access,
|
|
1797
|
+
* refuses the current version and delete markers, and audits before deleting.
|
|
1798
|
+
*/
|
|
1799
|
+
export async function runFilesPurge(params, deps = {}) {
|
|
1800
|
+
assertExactRecoveryKey(params.key);
|
|
1801
|
+
if (typeof params.versionId !== "string" || params.versionId.trim() === "") {
|
|
1802
|
+
throw new Error("--version-id is required and must not be empty.");
|
|
1803
|
+
}
|
|
1804
|
+
const confirm = deps.confirm ?? realConfirm;
|
|
1805
|
+
if (!params.yes) {
|
|
1806
|
+
const ok = await confirm("Permanently delete version '" + params.versionId + "' of '" + params.key + "'" +
|
|
1807
|
+
(params.personal ? " in your personal vault" : "") +
|
|
1808
|
+
"? This cannot be undone.");
|
|
1809
|
+
if (!ok) {
|
|
1810
|
+
console.log(chalk.dim("Aborted — nothing was purged."));
|
|
1811
|
+
return undefined;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
const scope = await resolveRecoveryScope(params);
|
|
1815
|
+
const body = scope.personal
|
|
1816
|
+
? { personal: true, key: params.key }
|
|
1817
|
+
: { company: scope.companyUid, key: params.key };
|
|
1818
|
+
body.action = "purge";
|
|
1819
|
+
body.purgeVersionId = params.versionId;
|
|
1820
|
+
// Tripwire: a server without purge support rejects `versionId: null` with
|
|
1821
|
+
// 400 instead of ignoring `action` and restoring (re-publishing) the version.
|
|
1822
|
+
body.versionId = null;
|
|
1823
|
+
const res = await vaultApiFetch({
|
|
1824
|
+
token: scope.token,
|
|
1825
|
+
path: "/v1/files/restore",
|
|
1826
|
+
method: "POST",
|
|
1827
|
+
body,
|
|
1828
|
+
});
|
|
1829
|
+
if (!res.ok) {
|
|
1830
|
+
const errBody = (await res.json().catch(() => ({})));
|
|
1831
|
+
const err = new FilesRecoveryHttpError(res.status, errBody.message ?? errBody.error ?? res.statusText, errBody.code);
|
|
1832
|
+
throw new Error(formatFilesRecoveryError(err, params.key));
|
|
1833
|
+
}
|
|
1834
|
+
const data = (await res.json());
|
|
1835
|
+
// Belt and braces with the tripwire above: only a server that echoes the
|
|
1836
|
+
// purged version id has purged it.
|
|
1837
|
+
if (data.purgedVersionId !== params.versionId) {
|
|
1838
|
+
throw new Error("The server did not confirm the purge (it may not support purge yet). Check `hq files versions` before retrying.");
|
|
1839
|
+
}
|
|
1840
|
+
console.log(chalk.green("Purged version '" + data.purgedVersionId + "' of '" + params.key + "'."));
|
|
1841
|
+
return { key: data.key ?? params.key, purgedVersionId: data.purgedVersionId };
|
|
1842
|
+
}
|
|
1770
1843
|
export async function runFilesTrash(params) {
|
|
1771
1844
|
if (params.prefix.includes("*")) {
|
|
1772
1845
|
throw new Error("Trash prefix must be literal; wildcard expansion is not supported.");
|
|
@@ -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,6 +165,12 @@ 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;
|
|
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;
|
|
142
174
|
/** `gh auth token` fallback for the release download token. */
|
|
143
175
|
export declare const FLEET_RELEASE_TOKEN_TIMEOUT_MS = 15000;
|
|
144
176
|
export declare function resolveFleetReleaseToken(env?: NodeJS.ProcessEnv, run?: FleetLocalExec): string;
|
|
@@ -172,6 +204,14 @@ export declare function writeFleetLocalSoul(agentUid: string, systemPrompt: stri
|
|
|
172
204
|
export declare function scanFleetLocalInstructions(agentUid: string, text: string, deps?: FleetLocalControlDeps): string[];
|
|
173
205
|
/** Checks the instructions with the runtime's scanner, then makes them the bot's persona. */
|
|
174
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;
|
|
175
215
|
/** Moves a fresh machine credential into the bot's secure store and deletes the file. */
|
|
176
216
|
export declare function importFleetLocalMachineCredential(agentUid: string, machineCredentials: string, deps?: FleetLocalControlDeps): void;
|
|
177
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,6 +147,29 @@ 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
|
}
|
|
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
|
+
}
|
|
108
173
|
/** `gh auth token` fallback for the release download token. */
|
|
109
174
|
export const FLEET_RELEASE_TOKEN_TIMEOUT_MS = 15_000;
|
|
110
175
|
export function resolveFleetReleaseToken(env = process.env, run = execFileSync) {
|
|
@@ -305,8 +370,14 @@ export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
|
305
370
|
const root = deps.fleetHome ?? fleetHome();
|
|
306
371
|
const dir = path.join(root, "distributions", "hq-fleet-local", release.version);
|
|
307
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
|
+
};
|
|
308
378
|
if (current) {
|
|
309
379
|
verifyFleetLocalRelease(current, release, publicKey);
|
|
380
|
+
warnCompatibility(current.manifest);
|
|
310
381
|
}
|
|
311
382
|
else {
|
|
312
383
|
const fetcher = deps.fetch ?? fetch;
|
|
@@ -316,6 +387,7 @@ export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
|
316
387
|
? await downloadFleetLocalReleaseFromHq(release, fetcher, links, cachedRuntime)
|
|
317
388
|
: await downloadFleetLocalRelease(release, fetcher, (deps.token ?? resolveFleetReleaseToken)(), cachedRuntime);
|
|
318
389
|
verifyFleetLocalRelease(files, release, publicKey);
|
|
390
|
+
warnCompatibility(files.manifest);
|
|
319
391
|
installFiles(dir, files, release, publicKey);
|
|
320
392
|
}
|
|
321
393
|
return {
|
|
@@ -377,18 +449,19 @@ function venvExecutable(runtimeDir, name, platform) {
|
|
|
377
449
|
? path.join(runtimeDir, "Scripts", `${name}.exe`)
|
|
378
450
|
: path.join(runtimeDir, "bin", name);
|
|
379
451
|
}
|
|
380
|
-
function sharedRuntimeFromMarker(profileDir, root) {
|
|
452
|
+
function sharedRuntimeFromMarker(profileDir, root, platform = process.platform) {
|
|
381
453
|
try {
|
|
382
454
|
const marker = JSON.parse(fs.readFileSync(path.join(profileDir, "bootstrap.json"), "utf8"));
|
|
383
455
|
if (marker.schemaVersion !== 2 || typeof marker.runtimeId !== "string" || !/^[a-f0-9]{64}$/.test(marker.runtimeId))
|
|
384
456
|
return null;
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
root
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
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;
|
|
392
465
|
}
|
|
393
466
|
catch {
|
|
394
467
|
return null;
|
|
@@ -426,7 +499,7 @@ function fleetLocalExecutables(agentUid, deps) {
|
|
|
426
499
|
const root = deps.fleetHome ?? fleetHome();
|
|
427
500
|
const platform = deps.platform ?? process.platform;
|
|
428
501
|
const profileDir = profileRoot(agentUid, root);
|
|
429
|
-
const shared = sharedRuntimeFromMarker(profileDir, root);
|
|
502
|
+
const shared = sharedRuntimeFromMarker(profileDir, root, platform);
|
|
430
503
|
const runtimeDir = shared?.runtimeDir ?? path.join(profileDir, "runtime");
|
|
431
504
|
const sourcesDir = shared?.sourcesDir ?? path.join(profileDir, "sources");
|
|
432
505
|
const profile = path.join(profileDir, "profile.json");
|
|
@@ -484,12 +557,12 @@ export function fleetLocalProfileNeedsAdoption(agentUid, deps = {}) {
|
|
|
484
557
|
if (!entries.includes(canonicalName))
|
|
485
558
|
return true;
|
|
486
559
|
const profileDir = path.join(profiles, canonicalName);
|
|
487
|
-
const
|
|
560
|
+
const platform = deps.platform ?? process.platform;
|
|
561
|
+
const shared = sharedRuntimeFromMarker(profileDir, root, platform);
|
|
488
562
|
if (!shared)
|
|
489
563
|
return true;
|
|
490
564
|
if (!profileEngineMatchesRelease(profileDir, deps.release ?? HQ_FLEET_LOCAL_RELEASE))
|
|
491
565
|
return true;
|
|
492
|
-
const platform = deps.platform ?? process.platform;
|
|
493
566
|
if (!["hq-fleet", "hq-fleet-local", "python"].every((name) => fs.existsSync(venvExecutable(shared.runtimeDir, name, platform))))
|
|
494
567
|
return true;
|
|
495
568
|
if (hasEmbeddedRuntime(profileDir, platform) || fs.existsSync(path.join(profileDir, "sources")))
|
|
@@ -639,7 +712,7 @@ export function removeFleetLocalBot(agentUid, deps = {}) {
|
|
|
639
712
|
}
|
|
640
713
|
const platform = deps.platform ?? process.platform;
|
|
641
714
|
const legacyRuntime = path.join(profileDir, "runtime");
|
|
642
|
-
const shared = sharedRuntimeFromMarker(profileDir, deps.fleetHome ?? fleetHome());
|
|
715
|
+
const shared = sharedRuntimeFromMarker(profileDir, deps.fleetHome ?? fleetHome(), platform);
|
|
643
716
|
const installedRuntime = shared?.runtimeDir ?? legacyRuntime;
|
|
644
717
|
if (!["hq-fleet", "hq-fleet-local"].every((name) => fs.existsSync(venvExecutable(installedRuntime, name, platform)))) {
|
|
645
718
|
fs.rmSync(profileDir, { recursive: true, force: true });
|
|
@@ -757,9 +830,11 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
757
830
|
}
|
|
758
831
|
const root = deps.fleetHome ?? fleetHome();
|
|
759
832
|
const dir = profileRoot(input.agentUid, root);
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
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");
|
|
763
838
|
const destination = path.join(dir, "profile.json");
|
|
764
839
|
const temporary = path.join(dir, `.profile-${process.pid}-${crypto.randomUUID()}`);
|
|
765
840
|
const profile = {
|
|
@@ -772,7 +847,7 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
772
847
|
profileDir: path.resolve(dir),
|
|
773
848
|
runtime: { commit: input.installation.release.runtimeCommit, path: path.resolve(runtimeDir) },
|
|
774
849
|
paths: {
|
|
775
|
-
pluginSource: path.resolve(path.join(
|
|
850
|
+
pluginSource: path.resolve(path.join(distributionSource, "src", "hq_fleet_local", "plugins", "hqdm")),
|
|
776
851
|
statusFile: path.resolve(path.join(input.botDir, "status.json")),
|
|
777
852
|
logsDir: path.resolve(path.join(input.botDir, "logs")),
|
|
778
853
|
},
|
|
@@ -833,11 +908,11 @@ export function scanFleetLocalInstructions(agentUid, text, deps = {}) {
|
|
|
833
908
|
const run = deps.run ?? execFileSync;
|
|
834
909
|
const root = deps.fleetHome ?? fleetHome();
|
|
835
910
|
const profileDir = profileRoot(agentUid, root);
|
|
836
|
-
const
|
|
911
|
+
const platform = deps.platform ?? process.platform;
|
|
912
|
+
const shared = sharedRuntimeFromMarker(profileDir, root, platform);
|
|
837
913
|
const runtimeDir = shared?.runtimeDir ?? path.join(profileDir, "runtime");
|
|
838
|
-
const
|
|
839
|
-
const
|
|
840
|
-
const source = path.join(sourcesDir, "runtime");
|
|
914
|
+
const python = venvExecutable(runtimeDir, "python", platform);
|
|
915
|
+
const source = shared?.runtimeSource ?? path.join(profileDir, "sources", "runtime");
|
|
841
916
|
if (!fs.existsSync(python) || !fs.existsSync(source)) {
|
|
842
917
|
throw expectedError("HQ Fleet Local is not installed for this bot, so its instructions could not be checked.");
|
|
843
918
|
}
|
|
@@ -867,14 +942,82 @@ export function refreshFleetLocalInstructions(agentUid, systemPrompt, deps = {})
|
|
|
867
942
|
}
|
|
868
943
|
return writeFleetLocalSoul(agentUid, systemPrompt, deps);
|
|
869
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
|
+
}
|
|
870
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;
|
|
871
959
|
try {
|
|
872
|
-
run(executable, args, {
|
|
960
|
+
run(executable, args, {
|
|
961
|
+
windowsHide: true,
|
|
962
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
963
|
+
...options,
|
|
964
|
+
...(stdio === undefined ? {} : { stdio }),
|
|
965
|
+
});
|
|
873
966
|
}
|
|
874
|
-
catch {
|
|
875
|
-
|
|
967
|
+
catch (error) {
|
|
968
|
+
const tail = stderrTail(error);
|
|
969
|
+
throw expectedError(`HQ Fleet Local ${label} failed.${tail ? `\n${tail}` : ""}`);
|
|
876
970
|
}
|
|
877
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());
|
|
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);
|
|
1020
|
+
}
|
|
878
1021
|
/** Moves a fresh machine credential into the bot's secure store and deletes the file. */
|
|
879
1022
|
export function importFleetLocalMachineCredential(agentUid, machineCredentials, deps = {}) {
|
|
880
1023
|
const run = deps.run ?? execFileSync;
|
|
@@ -902,25 +1045,37 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
902
1045
|
platform,
|
|
903
1046
|
extras: ["mcp", "anthropic"],
|
|
904
1047
|
})));
|
|
905
|
-
const
|
|
906
|
-
const
|
|
907
|
-
id: identity,
|
|
908
|
-
root: shared,
|
|
909
|
-
runtimeDir: path.join(shared, "runtime"),
|
|
910
|
-
sourcesDir: path.join(shared, "sources"),
|
|
911
|
-
});
|
|
1048
|
+
const layout = sharedRuntimeLayout(root, identity, platform);
|
|
1049
|
+
const shared = layout.root;
|
|
912
1050
|
return withBotLock(identity, async () => {
|
|
913
1051
|
const marker = path.join(shared, "ready.json");
|
|
914
|
-
const
|
|
915
|
-
const distributionSource = path.join(sources, "distribution");
|
|
916
|
-
const runtimeSource = path.join(sources, "runtime");
|
|
917
|
-
const runtimeDir = path.join(shared, "runtime");
|
|
1052
|
+
const { distributionSource, runtimeSource, runtimeDir } = layout;
|
|
918
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
|
+
}
|
|
919
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
|
+
}
|
|
920
1075
|
if (!executables.every((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)))) {
|
|
921
1076
|
throw expectedError("The shared HQ Fleet Runtime is incomplete. Reinstall it before creating another bot.");
|
|
922
1077
|
}
|
|
923
|
-
return
|
|
1078
|
+
return layout;
|
|
924
1079
|
}
|
|
925
1080
|
fs.rmSync(shared, { recursive: true, force: true });
|
|
926
1081
|
fs.mkdirSync(distributionSource, { recursive: true, mode: 0o700 });
|
|
@@ -933,6 +1088,8 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
933
1088
|
installation.archive,
|
|
934
1089
|
distributionSource,
|
|
935
1090
|
], { stdio: "ignore" }, "distribution extraction");
|
|
1091
|
+
if (platform === "win32")
|
|
1092
|
+
assertWindowsExtractionFits(run, python.python, installation.runtimeArchive, runtimeSource);
|
|
936
1093
|
checkedRun(run, python.python, [
|
|
937
1094
|
"-m",
|
|
938
1095
|
"tarfile",
|
|
@@ -961,8 +1118,8 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
961
1118
|
if (!executables.every((name) => fs.existsSync(venvExecutable(runtimeDir, name, platform)))) {
|
|
962
1119
|
throw expectedError("HQ Fleet Local installed without its runtime executables.");
|
|
963
1120
|
}
|
|
964
|
-
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: installation.engine })}\n`, { mode: 0o600 });
|
|
965
|
-
return
|
|
1121
|
+
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, runtimeId: identity, engine: installation.engine })}\n`, { mode: 0o600 });
|
|
1122
|
+
return layout;
|
|
966
1123
|
}, { root: path.join(root, "runtimes"), command: "HQ Fleet Runtime installation", waitMs: 20 * 60_000, staleMs: 2 * 60 * 60_000 });
|
|
967
1124
|
}
|
|
968
1125
|
function writeNativeProfileMarker(profileDir, shared, engine) {
|
|
@@ -988,7 +1145,7 @@ export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
|
988
1145
|
const { systemPrompt, machineCredentials, ...profileInput } = input;
|
|
989
1146
|
if (systemPrompt !== undefined)
|
|
990
1147
|
refreshFleetLocalInstructions(input.agentUid, systemPrompt, { fleetHome: root, platform, run });
|
|
991
|
-
const profile = writeFleetLocalProfile(profileInput, { fleetHome: root, platform,
|
|
1148
|
+
const profile = writeFleetLocalProfile(profileInput, { fleetHome: root, platform, runtime: shared });
|
|
992
1149
|
const helper = venvExecutable(shared.runtimeDir, "hq-fleet-local", platform);
|
|
993
1150
|
const runtime = venvExecutable(shared.runtimeDir, "hq-fleet", platform);
|
|
994
1151
|
if (!fs.existsSync(helper) || !fs.existsSync(runtime)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.345.
|
|
3
|
+
"version": "5.345.190",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -114,11 +114,11 @@
|
|
|
114
114
|
"*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}": "eslint --cache --no-error-on-unmatched-pattern"
|
|
115
115
|
},
|
|
116
116
|
"optionalDependencies": {
|
|
117
|
-
"@indigoai-us/hq-hook-win32-x64": "5.345.
|
|
118
|
-
"@indigoai-us/hq-hook-win32-arm64": "5.345.
|
|
119
|
-
"@indigoai-us/hq-hook-darwin-x64": "5.345.
|
|
120
|
-
"@indigoai-us/hq-hook-darwin-arm64": "5.345.
|
|
121
|
-
"@indigoai-us/hq-hook-linux-x64": "5.345.
|
|
122
|
-
"@indigoai-us/hq-hook-linux-arm64": "5.345.
|
|
117
|
+
"@indigoai-us/hq-hook-win32-x64": "5.345.190",
|
|
118
|
+
"@indigoai-us/hq-hook-win32-arm64": "5.345.190",
|
|
119
|
+
"@indigoai-us/hq-hook-darwin-x64": "5.345.190",
|
|
120
|
+
"@indigoai-us/hq-hook-darwin-arm64": "5.345.190",
|
|
121
|
+
"@indigoai-us/hq-hook-linux-x64": "5.345.190",
|
|
122
|
+
"@indigoai-us/hq-hook-linux-arm64": "5.345.190"
|
|
123
123
|
}
|
|
124
124
|
}
|