@indigoai-us/hq-cli 5.345.6 → 5.345.8
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 +14 -0
- package/dist/commands/__fixtures__/make-tar.d.ts +2 -10
- package/dist/commands/__fixtures__/make-tar.js +2 -2
- package/dist/commands/agent-enroll.d.ts +2 -3
- package/dist/commands/agent-enroll.js +2 -2
- package/dist/commands/mcp-status.d.ts +4 -3
- package/dist/commands/reindex.d.ts +0 -6
- package/dist/commands/reindex.js +1 -1
- package/dist/commands/safe-extract.d.ts +2 -17
- package/dist/commands/safe-extract.js +2 -2
- package/dist/commands/search.d.ts +2 -1
- package/dist/lazy-commands.d.ts +0 -6
- package/dist/lazy-commands.js +1 -1
- package/dist/lib/agent-kit/run/inbox.js +29 -3
- package/dist/lib/daemon/supervisor.d.ts +1 -0
- package/dist/lib/daemon/supervisor.js +97 -12
- package/dist/lib/doctor/__testing__/fake-hq-tree.d.ts +8 -9
- package/dist/lib/doctor/__testing__/fake-hq-tree.js +1 -1
- package/dist/lib/doctor/checks/runtime-probe.d.ts +0 -21
- package/dist/lib/doctor/checks/runtime-probe.js +4 -4
- package/dist/lib/doctor/checks/sync-health.d.ts +2 -3
- package/dist/lib/doctor/checks/sync-health.js +1 -1
- package/dist/lib/doctor/compat.d.ts +2 -1
- package/dist/lib/doctor/deep/classify.d.ts +2 -1
- package/dist/lib/doctor/fix/backup.d.ts +0 -8
- package/dist/lib/doctor/fix/backup.js +2 -2
- package/dist/lib/doctor/fixtures/discover.d.ts +2 -10
- package/dist/lib/doctor/fixtures/discover.js +1 -1
- package/dist/lib/lanes/autopilot.d.ts +2 -17
- package/dist/lib/lanes/autopilot.js +8 -8
- package/dist/lib/lanes/flag-keys.d.ts +2 -0
- package/dist/lib/lanes/flag-keys.js +2 -0
- package/dist/lib/lanes/keep-alive.d.ts +2 -0
- package/dist/lib/lanes/keep-alive.js +9 -0
- package/dist/lib/lanes/reconcile.js +44 -27
- package/dist/lib/lanes/spawn.js +2 -0
- package/dist/lib/lanes/tracker-report.d.ts +16 -15
- package/dist/lib/lanes/wait.d.ts +2 -5
- package/dist/lib/lanes/wait.js +3 -3
- package/dist/lib/local-tree-diff.d.ts +3 -2
- package/dist/lib/search-index/background.d.ts +0 -2
- package/dist/lib/search-index/background.js +1 -1
- package/dist/lib/search-index/index.d.ts +0 -19
- package/dist/lib/search-index/index.js +2 -2
- package/dist/main.d.ts +3 -0
- package/dist/main.js +126 -12
- package/dist/types.d.ts +3 -3
- package/dist/utils/client-health-contract.d.ts +14 -17
- package/dist/utils/client-health-contract.js +11 -11
- package/dist/utils/self-update.d.ts +2 -0
- package/dist/utils/self-update.js +37 -7
- package/package.json +1 -1
package/dist/lib/lanes/wait.d.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { LaneRecord, QuestionRecord } from "./types.js";
|
|
2
|
-
export declare const LANE_WAIT_POLL_INTERVAL_MS = 2000;
|
|
3
|
-
export declare const LANE_WAIT_MAX_LANES = 64;
|
|
4
2
|
export declare const LANE_WAIT_TIMEOUT_EXIT_CODE = 4;
|
|
5
3
|
export declare const LANE_WAIT_INVALID_INPUT_EXIT_CODE = 2;
|
|
6
|
-
|
|
7
|
-
export type LaneWaitChangeType = (typeof LANE_WAIT_CHANGE_TYPES)[number];
|
|
8
|
-
export type LaneWaitChange = {
|
|
4
|
+
type LaneWaitChange = {
|
|
9
5
|
type: "envelope";
|
|
10
6
|
ref: string;
|
|
11
7
|
decision: "done" | "ask" | "blocked";
|
|
@@ -36,4 +32,5 @@ export interface LaneWaitDeps {
|
|
|
36
32
|
* lock. Question records are scanned in one pass when question changes matter.
|
|
37
33
|
*/
|
|
38
34
|
export declare function runLaneWait(hqRoot: string, laneIds: readonly string[], changeTypes: readonly string[], timeoutMs: number, deps?: LaneWaitDeps): Promise<LaneWaitResult | null>;
|
|
35
|
+
export {};
|
|
39
36
|
//# sourceMappingURL=wait.d.ts.map
|
package/dist/lib/lanes/wait.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { listQuestionIds, readLane, readQuestion } from "./store.js";
|
|
2
2
|
import { LanesError } from "./errors.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const LANE_WAIT_POLL_INTERVAL_MS = 2_000;
|
|
4
|
+
const LANE_WAIT_MAX_LANES = 64;
|
|
5
5
|
export const LANE_WAIT_TIMEOUT_EXIT_CODE = 4;
|
|
6
6
|
export const LANE_WAIT_INVALID_INPUT_EXIT_CODE = 2;
|
|
7
|
-
|
|
7
|
+
const LANE_WAIT_CHANGE_TYPES = ["envelope", "question", "state"];
|
|
8
8
|
function defaultSleep(ms) {
|
|
9
9
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
10
|
}
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
* for the destructive side effects (delete, tombstone, PUT sync-config).
|
|
25
25
|
*/
|
|
26
26
|
import { type SyncJournal } from "@indigoai-us/hq-cloud";
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
type DirtyReason = "modified-after-sync" | "hash-mismatch" | "not-in-journal" | "stat-error";
|
|
28
|
+
interface NarrowFile {
|
|
29
29
|
/**
|
|
30
30
|
* COMPANY-RELATIVE path (e.g. `meetings/a.md`) — the canonical namespace
|
|
31
31
|
* shared by the per-company journal keys, the vault S3 keys, and the
|
|
@@ -117,4 +117,5 @@ export declare function toPosixKey(p: string): string;
|
|
|
117
117
|
export declare function formatNarrowPlanSummary(plan: NarrowPlan): string;
|
|
118
118
|
/** Human-readable byte counts with a fixed unit ladder. */
|
|
119
119
|
export declare function formatBytes(n: number): string;
|
|
120
|
+
export {};
|
|
120
121
|
//# sourceMappingURL=local-tree-diff.d.ts.map
|
|
@@ -93,8 +93,6 @@ export declare function defaultSpawnWorker({ logPath, fallbackLogPath }: {
|
|
|
93
93
|
}, io?: SpawnWorkerIo): number;
|
|
94
94
|
/** Defaults used by the CLI; tests supply every nondeterministic dependency. */
|
|
95
95
|
export declare function defaultBackgroundDependencies(hqRoot: string): BackgroundDependencies;
|
|
96
|
-
/** Match the shell forwarder's hosted-agent markers before looking up qmd. */
|
|
97
|
-
export declare function isHostedAgent(env?: NodeJS.ProcessEnv): boolean;
|
|
98
96
|
export declare function installWorkerCleanup(cleanup: () => void, processEvents?: Pick<NodeJS.Process, 'once'>, exit?: (code: number) => void): void;
|
|
99
97
|
/** Start a detached worker; this public entry never owns the qmd pipeline. */
|
|
100
98
|
export declare function runBackgroundLauncher(dependencies: BackgroundDependencies): BackgroundResult;
|
|
@@ -129,7 +129,7 @@ export function defaultBackgroundDependencies(hqRoot) {
|
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
/** Match the shell forwarder's hosted-agent markers before looking up qmd. */
|
|
132
|
-
|
|
132
|
+
function isHostedAgent(env = process.env) {
|
|
133
133
|
if (env.HQ_QMD_REINDEX_MODE === 'skip-agent' || env.HQ_QMD_REINDEX_MODE === 'skip')
|
|
134
134
|
return true;
|
|
135
135
|
if (env.HQ_AGENT_BOX && env.HQ_AGENT_BOX !== '0')
|
|
@@ -327,19 +327,6 @@ export type ResolveQmdBinOptions = {
|
|
|
327
327
|
* independent of the dependency's own exports map.
|
|
328
328
|
*/
|
|
329
329
|
export declare function packageLocalBin(): string | undefined;
|
|
330
|
-
/**
|
|
331
|
-
* Locate the bundled qmd's Node LAUNCHER — `@tobilu/qmd`'s `bin.qmd` entry
|
|
332
|
-
* (`bin/qmd`, a `#!/usr/bin/env node` script) — by the same upward walk
|
|
333
|
-
* {@link resolveQmdVersion} uses to find the package. Returns the absolute
|
|
334
|
-
* launcher path only when both the manifest and the launcher file exist.
|
|
335
|
-
*
|
|
336
|
-
* This is the shell-free preferred resolution: invoking `node <launcher>`
|
|
337
|
-
* bypasses the npm-generated `.cmd` shim entirely, so the Windows CVE-2024-27980
|
|
338
|
-
* EINVAL never arises and no user-supplied search text reaches cmd.exe. A
|
|
339
|
-
* missing/partial layout returns undefined so the caller falls back to the
|
|
340
|
-
* `.bin` shim (which the shared spawn plan now makes spawnable on Windows too).
|
|
341
|
-
*/
|
|
342
|
-
export declare function packageLocalNodeEntry(): string | undefined;
|
|
343
330
|
/**
|
|
344
331
|
* Return the pinned package version when qmd is supplied by this CLI.
|
|
345
332
|
*
|
|
@@ -412,12 +399,6 @@ export type EnsureQmdStoreDirOptions = {
|
|
|
412
399
|
export declare function ensureQmdStoreDir(env?: NodeJS.ProcessEnv, options?: EnsureQmdStoreDirOptions): void;
|
|
413
400
|
/** Reset per-process probe/repair memoisation. Test-only. */
|
|
414
401
|
export declare function __resetQmdProbeStateForTests(): void;
|
|
415
|
-
/** Probe a resolved invocation, memoised per invocation; records the failure
|
|
416
|
-
* detail under the qmd bin identity so repair can read it. */
|
|
417
|
-
export declare function isUsableInvocation(invocation: QmdInvocation, options?: {
|
|
418
|
-
platform?: NodeJS.Platform;
|
|
419
|
-
spawn?: QmdSpawn;
|
|
420
|
-
}): boolean;
|
|
421
402
|
/** Backwards-compatible string form: probe a bare bin path shell-free (or via
|
|
422
403
|
* the shell plan when it is a Windows `.cmd`/`.bat`). */
|
|
423
404
|
export declare function isUsableQmd(bin: string): boolean;
|
|
@@ -332,7 +332,7 @@ export function packageLocalBin() {
|
|
|
332
332
|
* missing/partial layout returns undefined so the caller falls back to the
|
|
333
333
|
* `.bin` shim (which the shared spawn plan now makes spawnable on Windows too).
|
|
334
334
|
*/
|
|
335
|
-
|
|
335
|
+
function packageLocalNodeEntry() {
|
|
336
336
|
let directory = path.dirname(fileURLToPath(import.meta.url));
|
|
337
337
|
for (let depth = 0; depth < 10; depth++) {
|
|
338
338
|
const manifest = path.join(directory, 'node_modules', '@tobilu', 'qmd', 'package.json');
|
|
@@ -644,7 +644,7 @@ function probeQmd(invocation, options = {}) {
|
|
|
644
644
|
}
|
|
645
645
|
/** Probe a resolved invocation, memoised per invocation; records the failure
|
|
646
646
|
* detail under the qmd bin identity so repair can read it. */
|
|
647
|
-
|
|
647
|
+
function isUsableInvocation(invocation, options = {}) {
|
|
648
648
|
const key = invocationKey(invocation);
|
|
649
649
|
const cached = usableQmdCache.get(key);
|
|
650
650
|
if (cached !== undefined)
|
package/dist/main.d.ts
CHANGED
|
@@ -44,6 +44,9 @@ export type TopLevelErrorDependencies = {
|
|
|
44
44
|
openUrl?: (url: string) => Promise<void>;
|
|
45
45
|
upgradeToTeam?: typeof upgradeToTeam;
|
|
46
46
|
};
|
|
47
|
+
export declare const MAX_OWN_DIST_RELATIVE_PATH_BYTES = 256;
|
|
48
|
+
/** Return a bounded path only when targetPath is beneath packageRoot/dist. */
|
|
49
|
+
export declare function relativeOwnDistModulePath(packageRootPath: string, targetPath: string): string | undefined;
|
|
47
50
|
/** Classify a top-level failure without making the CLI process boundary opaque to tests. */
|
|
48
51
|
export declare function handleTopLevelError(err: unknown, deps?: TopLevelErrorDependencies): Promise<void>;
|
|
49
52
|
//# sourceMappingURL=main.d.ts.map
|
package/dist/main.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// Node 20+ API (e.g. util.styleText) or a newer native ABI is evaluated.
|
|
7
7
|
import "./node-preflight.js";
|
|
8
8
|
import "./node-network-compat.js";
|
|
9
|
+
import fs from "node:fs";
|
|
9
10
|
import path from "node:path";
|
|
10
11
|
import { fileURLToPath } from "node:url";
|
|
11
12
|
import { Command } from "commander";
|
|
@@ -52,10 +53,11 @@ import { settleWithin } from "./utils/settle-with-timeout.js";
|
|
|
52
53
|
import { isVersionRequest } from "./version-request.js";
|
|
53
54
|
import { emitPlanLimitNag } from "./lib/plan-limit-nag.js";
|
|
54
55
|
import { kickFlagRegistryReadiness, waitForFlagRegistryRefresh, } from "./lib/flag-registry.js";
|
|
55
|
-
import { isPackageRootResolutionError, packageRootCaptureContext, } from "./utils/package-root-diagnostics.js";
|
|
56
|
+
import { boundedDiagnosticValue, isPackageRootResolutionError, packageRootCaptureContext, } from "./utils/package-root-diagnostics.js";
|
|
56
57
|
import { registerCommandsWithRecovery } from "./startup-registration.js";
|
|
57
58
|
import { releasePackageUseLeaseAfterCommandLoad } from "./utils/package-use-lease.js";
|
|
58
|
-
import {
|
|
59
|
+
import { packageRoot } from "./utils/hq-roots.js";
|
|
60
|
+
import { classifyModuleNotFound, installTreeTornCaptureContext, installTreeTornStderrLine, isInstallTreeTornError, } from "./utils/install-tree-torn.js";
|
|
59
61
|
import { isVaultAccessDeniedError, vaultAccessDeniedMessage, } from "./utils/vault-access-denied-error.js";
|
|
60
62
|
import { fallbackOperatorMessage, unexpectedCliErrorMessage } from "./utils/unexpected-cli-error.js";
|
|
61
63
|
import { runGlobalNetworkLifecycle } from "./utils/cli-network-lifecycle.js";
|
|
@@ -410,6 +412,110 @@ function integrationsGatewayCaptureContext(err) {
|
|
|
410
412
|
},
|
|
411
413
|
};
|
|
412
414
|
}
|
|
415
|
+
export const MAX_OWN_DIST_RELATIVE_PATH_BYTES = 256;
|
|
416
|
+
function resolveThroughExistingAncestors(pathApi, inputPath) {
|
|
417
|
+
let cursor = pathApi.resolve(inputPath);
|
|
418
|
+
const missingSegments = [];
|
|
419
|
+
while (true) {
|
|
420
|
+
try {
|
|
421
|
+
const realPath = fs.realpathSync.native(cursor);
|
|
422
|
+
if (missingSegments.length > 0 && !fs.statSync(realPath).isDirectory()) {
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
return pathApi.join(realPath, ...missingSegments);
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
const code = error !== null && typeof error === "object" && "code" in error
|
|
429
|
+
? error.code
|
|
430
|
+
: undefined;
|
|
431
|
+
// A missing final module is expected here. Walk to its nearest existing
|
|
432
|
+
// parent so symlinked package roots and junctions still compare by their
|
|
433
|
+
// actual location. Other filesystem errors leave enrichment unavailable;
|
|
434
|
+
// the original error remains on the generic capture path.
|
|
435
|
+
if (code !== "ENOENT" && code !== "ENOTDIR")
|
|
436
|
+
return undefined;
|
|
437
|
+
const parent = pathApi.dirname(cursor);
|
|
438
|
+
if (parent === cursor)
|
|
439
|
+
return undefined;
|
|
440
|
+
missingSegments.unshift(pathApi.basename(cursor));
|
|
441
|
+
cursor = parent;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/** Return a bounded path only when targetPath is beneath packageRoot/dist. */
|
|
446
|
+
export function relativeOwnDistModulePath(packageRootPath, targetPath) {
|
|
447
|
+
const hasWindowsAbsolutePath = (value) => /^[A-Za-z]:[\\/]/.test(value) || /^\\\\/.test(value);
|
|
448
|
+
const pathApi = hasWindowsAbsolutePath(packageRootPath) || hasWindowsAbsolutePath(targetPath)
|
|
449
|
+
? path.win32
|
|
450
|
+
: path;
|
|
451
|
+
let resolvedRoot;
|
|
452
|
+
let resolvedTarget;
|
|
453
|
+
if (pathApi === path.win32) {
|
|
454
|
+
// Use Windows lexical semantics for drive paths and UNC paths even when
|
|
455
|
+
// this helper is exercised on a non-Windows host.
|
|
456
|
+
resolvedRoot = pathApi.resolve(packageRootPath);
|
|
457
|
+
resolvedTarget = pathApi.resolve(targetPath);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
const canonicalRoot = resolveThroughExistingAncestors(pathApi, packageRootPath);
|
|
461
|
+
const canonicalTarget = resolveThroughExistingAncestors(pathApi, targetPath);
|
|
462
|
+
if (canonicalRoot === undefined || canonicalTarget === undefined)
|
|
463
|
+
return undefined;
|
|
464
|
+
resolvedRoot = canonicalRoot;
|
|
465
|
+
resolvedTarget = canonicalTarget;
|
|
466
|
+
}
|
|
467
|
+
const distRoot = pathApi.resolve(resolvedRoot, "dist");
|
|
468
|
+
const relativePath = pathApi.relative(distRoot, resolvedTarget);
|
|
469
|
+
if (relativePath.length === 0 ||
|
|
470
|
+
relativePath === ".." ||
|
|
471
|
+
relativePath.startsWith(`..${pathApi.sep}`) ||
|
|
472
|
+
pathApi.isAbsolute(relativePath)) {
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
475
|
+
return boundedDiagnosticValue(relativePath, MAX_OWN_DIST_RELATIVE_PATH_BYTES);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Add bounded evidence when an ESM resolver failure names a file in hq-cli's
|
|
479
|
+
* own dist tree. This only enriches an event that is already captured; it does
|
|
480
|
+
* not infer that the package tree was torn or change the recovery path.
|
|
481
|
+
*/
|
|
482
|
+
function ownDistModuleMissingCaptureContext(err) {
|
|
483
|
+
if (err === null || typeof err !== "object")
|
|
484
|
+
return undefined;
|
|
485
|
+
const record = err;
|
|
486
|
+
if (record.code !== "ERR_MODULE_NOT_FOUND")
|
|
487
|
+
return undefined;
|
|
488
|
+
const classified = classifyModuleNotFound(err);
|
|
489
|
+
if (!classified ||
|
|
490
|
+
classified.code !== "ERR_MODULE_NOT_FOUND" ||
|
|
491
|
+
classified.dialect !== "esm-path" ||
|
|
492
|
+
classified.target.kind !== "path") {
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
let root;
|
|
496
|
+
try {
|
|
497
|
+
root = packageRoot();
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
// Enrichment is optional. If root discovery itself is unavailable, retain
|
|
501
|
+
// the original generic capture without changing its recovery or output.
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
const relativePath = relativeOwnDistModulePath(root, classified.target.path);
|
|
505
|
+
if (relativePath === undefined)
|
|
506
|
+
return undefined;
|
|
507
|
+
return {
|
|
508
|
+
tags: {
|
|
509
|
+
module_missing_scope: "own_dist",
|
|
510
|
+
cli_version: CLI_VERSION,
|
|
511
|
+
},
|
|
512
|
+
contexts: {
|
|
513
|
+
module_missing_target: {
|
|
514
|
+
relative_path: relativePath,
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
}
|
|
413
519
|
/** Classify a top-level failure without making the CLI process boundary opaque to tests. */
|
|
414
520
|
export async function handleTopLevelError(err, deps = defaultTopLevelErrorDependencies) {
|
|
415
521
|
// A typed plan gate, or an hq-cloud vault error carrying a plan-limit
|
|
@@ -920,22 +1026,30 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
920
1026
|
else {
|
|
921
1027
|
// A genuinely unclassified fault is still captured exactly once. Two
|
|
922
1028
|
// shapes attach bounded, hq-derived context so the next occurrence
|
|
923
|
-
// carries
|
|
924
|
-
//
|
|
925
|
-
//
|
|
926
|
-
//
|
|
1029
|
+
// carries evidence otherwise missing: qmd spawn failures (HQ-CLI-1A),
|
|
1030
|
+
// loader/resolver ENOENTs with no usable path (HQ-CLI-1M/1S/20), and an
|
|
1031
|
+
// ESM module miss under hq-cli's own dist tree. The latter remains a
|
|
1032
|
+
// captured fault without guessing whether the artifact or install tore.
|
|
927
1033
|
// Every other error captures bare, exactly as before.
|
|
928
1034
|
const spawnContext = qmdSpawnFailureCaptureContext(err);
|
|
929
1035
|
const installContext = incompleteInstallCaptureContext(err);
|
|
930
1036
|
const qmdContext = qmdExitCaptureContext(err);
|
|
931
1037
|
const gatewayContext = integrationsGatewayCaptureContext(err);
|
|
932
|
-
|
|
1038
|
+
const moduleContext = ownDistModuleMissingCaptureContext(err);
|
|
1039
|
+
const tags = {
|
|
1040
|
+
...qmdContext?.tags,
|
|
1041
|
+
...gatewayContext?.tags,
|
|
1042
|
+
...moduleContext?.tags,
|
|
1043
|
+
};
|
|
1044
|
+
const contexts = {
|
|
1045
|
+
...spawnContext?.contexts,
|
|
1046
|
+
...installContext,
|
|
1047
|
+
...moduleContext?.contexts,
|
|
1048
|
+
};
|
|
1049
|
+
if (Object.keys(tags).length > 0 || Object.keys(contexts).length > 0) {
|
|
933
1050
|
deps.sentry.captureException(err, {
|
|
934
|
-
...
|
|
935
|
-
...(
|
|
936
|
-
...((spawnContext || installContext)
|
|
937
|
-
? { contexts: { ...spawnContext?.contexts, ...installContext } }
|
|
938
|
-
: {}),
|
|
1051
|
+
...(Object.keys(tags).length > 0 ? { tags } : {}),
|
|
1052
|
+
...(Object.keys(contexts).length > 0 ? { contexts } : {}),
|
|
939
1053
|
});
|
|
940
1054
|
}
|
|
941
1055
|
else {
|
package/dist/types.d.ts
CHANGED
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ContributionKey } from './utils/contribution-table.js';
|
|
11
11
|
export type LegacyStrategy = 'link' | 'merge' | 'copy';
|
|
12
|
-
export type SyncStrategy = LegacyStrategy | 'package';
|
|
13
12
|
export type AccessLevel = 'public' | 'team' | `role:${string}`;
|
|
14
|
-
|
|
13
|
+
interface PathMapping {
|
|
15
14
|
src: string;
|
|
16
15
|
dest: string;
|
|
17
16
|
}
|
|
@@ -83,7 +82,7 @@ export interface PackAuthor {
|
|
|
83
82
|
* tracks installed packs by FILESYSTEM PRESENCE — not `modules.yaml`). `version`
|
|
84
83
|
* is an optional semver RANGE the installed dependency must satisfy.
|
|
85
84
|
*/
|
|
86
|
-
|
|
85
|
+
interface PackDependency {
|
|
87
86
|
name: string;
|
|
88
87
|
version?: string;
|
|
89
88
|
}
|
|
@@ -129,4 +128,5 @@ export interface PackManifest {
|
|
|
129
128
|
prompt?: string;
|
|
130
129
|
};
|
|
131
130
|
}
|
|
131
|
+
export {};
|
|
132
132
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -32,19 +32,19 @@
|
|
|
32
32
|
* ({@link shouldApplyHeartbeat}).
|
|
33
33
|
*/
|
|
34
34
|
export declare const CLIENT_HEALTH_CONTRACT_VERSION = 1;
|
|
35
|
-
|
|
35
|
+
declare const CLIENT_HEALTH_PLATFORMS: readonly ["macos", "windows", "linux"];
|
|
36
36
|
export type ClientHealthPlatform = (typeof CLIENT_HEALTH_PLATFORMS)[number];
|
|
37
|
-
|
|
37
|
+
declare const CLIENT_HEALTH_ARCHS: readonly ["x64", "arm64"];
|
|
38
38
|
export type ClientHealthArch = (typeof CLIENT_HEALTH_ARCHS)[number];
|
|
39
39
|
/** Which client produced the heartbeat. Desktop is always-on; CLI contributes per invocation. */
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
declare const CLIENT_HEALTH_SOURCES: readonly ["desktop", "cli"];
|
|
41
|
+
type ClientHealthSource = (typeof CLIENT_HEALTH_SOURCES)[number];
|
|
42
42
|
/**
|
|
43
43
|
* Current sync state of the installation. `paused` and `conflict_blocked` are
|
|
44
44
|
* first-class states (not failures folded into `error`) because support treats
|
|
45
45
|
* them differently: pause may be intentional and conflicts are user-owned.
|
|
46
46
|
*/
|
|
47
|
-
|
|
47
|
+
declare const CLIENT_HEALTH_SYNC_STATES: readonly ["idle", "syncing", "paused", "conflict_blocked", "error", "never_synced"];
|
|
48
48
|
export type ClientHealthSyncState = (typeof CLIENT_HEALTH_SYNC_STATES)[number];
|
|
49
49
|
/**
|
|
50
50
|
* Updater state. ABSENCE of the field means the client is too old to report
|
|
@@ -62,19 +62,15 @@ export type ClientHealthFailureReason = (typeof CLIENT_HEALTH_FAILURE_REASONS)[n
|
|
|
62
62
|
* fails closed.
|
|
63
63
|
*/
|
|
64
64
|
export declare const CLIENT_HEALTH_REPAIR_KINDS: readonly ["CHECK_NOW", "RETRY_SYNC", "RESUME_SYNC", "REPAIR_CLI", "UPDATE_CORE", "APPLY_DESKTOP_UPDATE", "RESTART_APP"];
|
|
65
|
-
|
|
65
|
+
type ClientHealthRepairKind = (typeof CLIENT_HEALTH_REPAIR_KINDS)[number];
|
|
66
66
|
/** Command/receipt lifecycle states (queued → acknowledged → running → terminal). */
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
declare const CLIENT_HEALTH_COMMAND_STATES: readonly ["queued", "acknowledged", "running", "succeeded", "failed", "expired"];
|
|
68
|
+
type ClientHealthCommandState = (typeof CLIENT_HEALTH_COMMAND_STATES)[number];
|
|
69
69
|
/** Closed diagnostic probe identifiers (US-007). */
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
export declare const CLIENT_HEALTH_MAX_STRING_LENGTH = 64;
|
|
75
|
-
export declare const CLIENT_HEALTH_MAX_CONSECUTIVE_FAILURES = 100000;
|
|
76
|
-
export declare const CLIENT_HEALTH_MAX_CONFLICT_COUNT = 100000;
|
|
77
|
-
export declare const CLIENT_HEALTH_MAX_CHECKS = 16;
|
|
70
|
+
declare const CLIENT_HEALTH_DIAGNOSTIC_CHECKS: readonly ["auth", "runner", "cli", "core", "updater", "sync", "conflicts", "storage", "permissions"];
|
|
71
|
+
type ClientHealthDiagnosticCheck = (typeof CLIENT_HEALTH_DIAGNOSTIC_CHECKS)[number];
|
|
72
|
+
declare const CLIENT_HEALTH_CHECK_STATUSES: readonly ["pass", "fail", "skip"];
|
|
73
|
+
type ClientHealthCheckStatus = (typeof CLIENT_HEALTH_CHECK_STATUSES)[number];
|
|
78
74
|
/**
|
|
79
75
|
* Local-files-overview bounds (client-sync-health-control-plane hq-cli
|
|
80
76
|
* addition, US-016). These cap the closed set of bounded facts support sees
|
|
@@ -146,7 +142,7 @@ export interface ClientHealthHeartbeat {
|
|
|
146
142
|
/** Absent on older clients / non-sync-outcome events. See {@link ClientHealthLocalFilesOverview}. */
|
|
147
143
|
localFilesOverview?: ClientHealthLocalFilesOverview;
|
|
148
144
|
}
|
|
149
|
-
|
|
145
|
+
interface ClientHealthCheckResult {
|
|
150
146
|
check: ClientHealthDiagnosticCheck;
|
|
151
147
|
status: ClientHealthCheckStatus;
|
|
152
148
|
/** Present only on `fail` — a closed reason code, never prose. */
|
|
@@ -187,4 +183,5 @@ export declare function parseClientHealthCommandReceipt(input: unknown): ClientH
|
|
|
187
183
|
* answers idempotently and the current snapshot remains unchanged).
|
|
188
184
|
*/
|
|
189
185
|
export declare function shouldApplyHeartbeat(storedSequence: number | undefined, incomingSequence: number): boolean;
|
|
186
|
+
export {};
|
|
190
187
|
//# sourceMappingURL=client-health-contract.d.ts.map
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
// ─── Contract version ────────────────────────────────────────────────────────
|
|
35
35
|
export const CLIENT_HEALTH_CONTRACT_VERSION = 1;
|
|
36
36
|
// ─── Closed enums ────────────────────────────────────────────────────────────
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const CLIENT_HEALTH_PLATFORMS = ["macos", "windows", "linux"];
|
|
38
|
+
const CLIENT_HEALTH_ARCHS = ["x64", "arm64"];
|
|
39
39
|
/** Which client produced the heartbeat. Desktop is always-on; CLI contributes per invocation. */
|
|
40
|
-
|
|
40
|
+
const CLIENT_HEALTH_SOURCES = ["desktop", "cli"];
|
|
41
41
|
/**
|
|
42
42
|
* Current sync state of the installation. `paused` and `conflict_blocked` are
|
|
43
43
|
* first-class states (not failures folded into `error`) because support treats
|
|
44
44
|
* them differently: pause may be intentional and conflicts are user-owned.
|
|
45
45
|
*/
|
|
46
|
-
|
|
46
|
+
const CLIENT_HEALTH_SYNC_STATES = [
|
|
47
47
|
"idle",
|
|
48
48
|
"syncing",
|
|
49
49
|
"paused",
|
|
@@ -94,7 +94,7 @@ export const CLIENT_HEALTH_REPAIR_KINDS = [
|
|
|
94
94
|
"RESTART_APP",
|
|
95
95
|
];
|
|
96
96
|
/** Command/receipt lifecycle states (queued → acknowledged → running → terminal). */
|
|
97
|
-
|
|
97
|
+
const CLIENT_HEALTH_COMMAND_STATES = [
|
|
98
98
|
"queued",
|
|
99
99
|
"acknowledged",
|
|
100
100
|
"running",
|
|
@@ -103,7 +103,7 @@ export const CLIENT_HEALTH_COMMAND_STATES = [
|
|
|
103
103
|
"expired",
|
|
104
104
|
];
|
|
105
105
|
/** Closed diagnostic probe identifiers (US-007). */
|
|
106
|
-
|
|
106
|
+
const CLIENT_HEALTH_DIAGNOSTIC_CHECKS = [
|
|
107
107
|
"auth",
|
|
108
108
|
"runner",
|
|
109
109
|
"cli",
|
|
@@ -114,12 +114,12 @@ export const CLIENT_HEALTH_DIAGNOSTIC_CHECKS = [
|
|
|
114
114
|
"storage",
|
|
115
115
|
"permissions",
|
|
116
116
|
];
|
|
117
|
-
|
|
117
|
+
const CLIENT_HEALTH_CHECK_STATUSES = ["pass", "fail", "skip"];
|
|
118
118
|
// ─── Bounds ──────────────────────────────────────────────────────────────────
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
const CLIENT_HEALTH_MAX_STRING_LENGTH = 64;
|
|
120
|
+
const CLIENT_HEALTH_MAX_CONSECUTIVE_FAILURES = 100_000;
|
|
121
|
+
const CLIENT_HEALTH_MAX_CONFLICT_COUNT = 100_000;
|
|
122
|
+
const CLIENT_HEALTH_MAX_CHECKS = 16;
|
|
123
123
|
/**
|
|
124
124
|
* Local-files-overview bounds (client-sync-health-control-plane hq-cli
|
|
125
125
|
* addition, US-016). These cap the closed set of bounded facts support sees
|
|
@@ -145,6 +145,8 @@ export interface SelfUpdateDeps {
|
|
|
145
145
|
/** Same-filesystem install rename, injectable for rollback tests. */
|
|
146
146
|
renamePath?: typeof fs.renameSync;
|
|
147
147
|
runner?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => UpdateResult;
|
|
148
|
+
/** Delay between bounded npm retries when the just-published target is not visible yet. */
|
|
149
|
+
sleep?: (ms: number) => Promise<void>;
|
|
148
150
|
reexec?: (argv: string[], env: NodeJS.ProcessEnv) => number | null;
|
|
149
151
|
/** Async process-spawn seam for lease handoff tests. */
|
|
150
152
|
spawnReexec?: typeof spawn;
|
|
@@ -693,6 +693,18 @@ function reexecErrorClass(error) {
|
|
|
693
693
|
}
|
|
694
694
|
return "unknown";
|
|
695
695
|
}
|
|
696
|
+
const NPM_NOTARGET_RETRY_DELAYS_MS = [30_000, 60_000, 90_000];
|
|
697
|
+
const NPM_NOTARGET_MAX_ATTEMPTS = NPM_NOTARGET_RETRY_DELAYS_MS.length + 1;
|
|
698
|
+
function isNpmNotargetFailure(result) {
|
|
699
|
+
const code = (result.diagnosticCode ?? result.code)?.toUpperCase();
|
|
700
|
+
if (code === "ETARGET" || code === "NOTARGET")
|
|
701
|
+
return true;
|
|
702
|
+
return result.failureKind === "package_manager_exit" &&
|
|
703
|
+
/\bETARGET\b|\bnpm\s+(?:ERR!|error)\s+notarget\b/i.test(result.detail ?? "");
|
|
704
|
+
}
|
|
705
|
+
function sleepForRetry(ms) {
|
|
706
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
707
|
+
}
|
|
696
708
|
async function updateAndReexec(argv, flavor, known, deps) {
|
|
697
709
|
const env = deps.env ?? process.env;
|
|
698
710
|
if (env[REEXEC_GUARD_ENV] === "1" || env.HQ_NO_UPDATE_CHECK === "1") {
|
|
@@ -795,14 +807,32 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
795
807
|
// Forward env only when PNPM_HOME had to be injected, so the common path
|
|
796
808
|
// spawns with the inherited environment and keeps the two-argument runner
|
|
797
809
|
// call it has always made.
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
810
|
+
const runInstall = () => {
|
|
811
|
+
if (staged) {
|
|
812
|
+
return stagedNpmInstall(install, latestValid, { run: runner, env, rename: renamePath });
|
|
813
|
+
}
|
|
814
|
+
return updateEnv === undefined
|
|
815
|
+
? runner(plan.cmd, plan.args)
|
|
816
|
+
: runner(plan.cmd, plan.args, updateEnv);
|
|
817
|
+
};
|
|
818
|
+
let attempt = 1;
|
|
819
|
+
result = runInstall();
|
|
820
|
+
while (install.manager === "npm" &&
|
|
821
|
+
!result.ok &&
|
|
822
|
+
isNpmNotargetFailure(result) &&
|
|
823
|
+
attempt < NPM_NOTARGET_MAX_ATTEMPTS) {
|
|
824
|
+
const delay = NPM_NOTARGET_RETRY_DELAYS_MS[attempt - 1];
|
|
825
|
+
const nextAttempt = attempt + 1;
|
|
826
|
+
console.error(chalk.dim(` npm has not published hq-cli ${latestValid} yet; retrying install (attempt ${nextAttempt}/${NPM_NOTARGET_MAX_ATTEMPTS}) after ${delay} ms.`));
|
|
827
|
+
await (deps.sleep ?? sleepForRetry)(delay);
|
|
828
|
+
attempt = nextAttempt;
|
|
829
|
+
result = runInstall();
|
|
803
830
|
}
|
|
804
|
-
|
|
805
|
-
result =
|
|
831
|
+
if (!result.ok && attempt > 1) {
|
|
832
|
+
result = {
|
|
833
|
+
...result,
|
|
834
|
+
detail: `${result.detail ?? "npm install failed"} (attempts: ${attempt})`,
|
|
835
|
+
};
|
|
806
836
|
}
|
|
807
837
|
if (result.ok && !staged && install.manager === "npm" && install.prefix && install.packageRoot) {
|
|
808
838
|
const bins = verifyOrRepairNpmBins(install.prefix, install.packageRoot);
|