@indigoai-us/hq-cli 5.345.46 → 5.345.48
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 +12 -0
- package/assets/scaffold/core/scripts/hq-session.sh +113 -7
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +101 -0
- package/dist/command-catalog.generated.d.ts +4 -0
- package/dist/command-catalog.generated.js +5 -0
- package/dist/commands/core-hq-session-native.d.ts +2 -0
- package/dist/commands/core-hq-session-native.js +242 -52
- package/dist/commands/core-resolve-company-native.d.ts +1 -0
- package/dist/commands/core-resolve-company-native.js +41 -11
- package/dist/commands/files.js +4 -1
- package/dist/commands/install-global.d.ts +2 -0
- package/dist/commands/install-global.js +38 -4
- package/dist/commands/secrets.d.ts +5 -0
- package/dist/commands/secrets.js +181 -2
- package/dist/commands/workers.d.ts +4 -1
- package/dist/commands/workers.js +57 -15
- package/dist/lib/cli-flag-keys.d.ts +1 -0
- package/dist/lib/cli-flag-keys.js +1 -0
- package/dist/lib/core-utils/session-id.d.ts +6 -0
- package/dist/lib/core-utils/session-id.js +35 -0
- package/dist/lib/daemon/catalog.js +1 -1
- package/dist/lib/daemon/lanes-reconcile-guard.d.ts +6 -5
- package/dist/lib/daemon/lanes-reconcile-guard.js +26 -7
- package/dist/lib/daemon/run.d.ts +6 -0
- package/dist/lib/daemon/run.js +36 -9
- package/dist/lib/daemon/supervisor.d.ts +7 -1
- package/dist/lib/daemon/supervisor.js +50 -4
- package/dist/lib/install/provider-wrappers.d.ts +9 -0
- package/dist/lib/install/provider-wrappers.js +190 -0
- package/dist/lib/lanes/provider-rejection.d.ts +5 -5
- package/dist/lib/lanes/provider-rejection.js +15 -10
- package/dist/lib/lanes/spawn.js +9 -4
- package/dist/lib/lanes/watcher.d.ts +6 -2
- package/dist/lib/lanes/watcher.js +37 -17
- package/dist/utils/resident-package-preload.js +4 -0
- package/dist/utils/self-update.js +8 -1
- package/dist/utils/version-gate.d.ts +7 -0
- package/dist/utils/version-gate.js +37 -0
- package/package.json +1 -1
|
@@ -106,11 +106,19 @@ export function classifyLaneExit(input) {
|
|
|
106
106
|
throw new LanesError("unknown_envelope_decision", `Unknown envelope decision ${JSON.stringify(input.envelope.decision)}. Accepted: done, ask, blocked`, { accepted: ["done", "ask", "blocked"], value: input.envelope.decision });
|
|
107
107
|
}
|
|
108
108
|
if (input.providerRejection) {
|
|
109
|
+
const rejection = input.providerRejection;
|
|
110
|
+
const reason = isProviderLoginFailure(rejection)
|
|
111
|
+
? "account_not_logged_in"
|
|
112
|
+
: rejection.status === 402
|
|
113
|
+
? "provider_billing_required"
|
|
114
|
+
: rejection.status === 401 || rejection.status === 403
|
|
115
|
+
? "provider_auth_failed"
|
|
116
|
+
: rejection.status === 429 || /\b(?:rate limit|too many requests)\b/i.test(rejection.message)
|
|
117
|
+
? "provider_rate_limited"
|
|
118
|
+
: PROVIDER_REJECTED_REASON;
|
|
109
119
|
return {
|
|
110
|
-
state: "failed",
|
|
111
|
-
reason
|
|
112
|
-
? "account_not_logged_in"
|
|
113
|
-
: PROVIDER_REJECTED_REASON,
|
|
120
|
+
state: rejection.retryable && input.hasProviderSession === true ? "interrupted" : "failed",
|
|
121
|
+
reason,
|
|
114
122
|
};
|
|
115
123
|
}
|
|
116
124
|
if (input.invalidEnvelope) {
|
|
@@ -129,15 +137,14 @@ export function classifyLaneExit(input) {
|
|
|
129
137
|
}
|
|
130
138
|
/**
|
|
131
139
|
* One decision for an exited worker's output. An explicit provider status
|
|
132
|
-
* wins over the quota text heuristics: a 429
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* rejection even when its text sounds like a quota message (a 402
|
|
136
|
-
* usage credits" is a
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* carries no failure object does the quota reader's own read apply.
|
|
140
|
+
* wins over the quota text heuristics: a 429 with quota-specific text or a
|
|
141
|
+
* usage-limit error type becomes an account hold; a generic rate-limit
|
|
142
|
+
* response stays a retryable provider rejection. Any other explicit status
|
|
143
|
+
* is a rejection even when its text sounds like a quota message (a 402
|
|
144
|
+
* "requires usage credits" is a billing refusal, not a hold). A status-less
|
|
145
|
+
* usage-limit message can create an account hold, while a status-less
|
|
146
|
+
* rate-limit message remains a retryable provider rejection. Only when the
|
|
147
|
+
* stream carries no failure object does the quota reader's own read apply.
|
|
141
148
|
*/
|
|
142
149
|
function validFailureOutputTime(value, fallback) {
|
|
143
150
|
const parsed = new Date(value);
|
|
@@ -146,21 +153,31 @@ function validFailureOutputTime(value, fallback) {
|
|
|
146
153
|
: fallback;
|
|
147
154
|
}
|
|
148
155
|
export function routeProviderFailure(failure, readQuota, model, now) {
|
|
149
|
-
if (failure && failure.status === QUOTA_STATUS) {
|
|
156
|
+
if (failure && failure.status === QUOTA_STATUS && isAccountQuotaFailure(failure)) {
|
|
150
157
|
return {
|
|
151
158
|
quota: quotaSignalFromFields({ api_error_status: QUOTA_STATUS, is_error: true, result: failure.message }, model, failure.output_at ? validFailureOutputTime(failure.output_at, now) : now),
|
|
152
159
|
rejection: null,
|
|
153
160
|
};
|
|
154
161
|
}
|
|
155
|
-
if (failure && failure.status === null) {
|
|
162
|
+
if (failure && failure.status === null && isAccountQuotaFailure(failure)) {
|
|
156
163
|
const quota = quotaSignalFromFields({ api_error_status: undefined, is_error: true, result: failure.message }, model, failure.output_at ? validFailureOutputTime(failure.output_at, now) : now);
|
|
157
164
|
if (quota)
|
|
158
165
|
return { quota, rejection: null };
|
|
159
166
|
}
|
|
160
|
-
if (failure)
|
|
161
|
-
return {
|
|
167
|
+
if (failure) {
|
|
168
|
+
return {
|
|
169
|
+
quota: null,
|
|
170
|
+
rejection: failure.status === QUOTA_STATUS
|
|
171
|
+
? { ...failure, retryable: true }
|
|
172
|
+
: failure,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
162
175
|
return { quota: readQuota(), rejection: null };
|
|
163
176
|
}
|
|
177
|
+
function isAccountQuotaFailure(failure) {
|
|
178
|
+
return failure.error_type === "usage_limit_reached" ||
|
|
179
|
+
/weekly limit|usage limit|quota|usage credits|hit your .+ limit/i.test(failure.message);
|
|
180
|
+
}
|
|
164
181
|
export function tickWatcher(input) {
|
|
165
182
|
const silenceMs = input.silenceMs ?? SILENCE_MS;
|
|
166
183
|
const at = new Date(input.nowMs).toISOString();
|
|
@@ -171,6 +188,7 @@ export function tickWatcher(input) {
|
|
|
171
188
|
quota: input.quota,
|
|
172
189
|
invalidEnvelope: input.invalidEnvelope,
|
|
173
190
|
providerRejection: input.providerRejection,
|
|
191
|
+
hasProviderSession: input.hasProviderSession,
|
|
174
192
|
});
|
|
175
193
|
return {
|
|
176
194
|
emit: { kind: "terminal", at, classification },
|
|
@@ -1578,6 +1596,8 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1578
1596
|
quota,
|
|
1579
1597
|
invalidEnvelope,
|
|
1580
1598
|
providerRejection,
|
|
1599
|
+
hasProviderSession: typeof quotaLane?.provider_session_id === "string" &&
|
|
1600
|
+
quotaLane.provider_session_id.trim().length > 0,
|
|
1581
1601
|
});
|
|
1582
1602
|
if (processAlive) {
|
|
1583
1603
|
maybeHeartbeatLane(hqRoot, laneId, now());
|
|
@@ -83,6 +83,8 @@ export const RESIDENT_PACKAGE_MODULES = {
|
|
|
83
83
|
"../utils/cli-telemetry.js",
|
|
84
84
|
"../utils/cognito-session.js",
|
|
85
85
|
"../lib/first-sync-next-step.js",
|
|
86
|
+
"../lib/process-flag-company-context.js",
|
|
87
|
+
"../lib/flag-snapshot-file.js",
|
|
86
88
|
"@indigoai-us/hq-cloud",
|
|
87
89
|
],
|
|
88
90
|
};
|
|
@@ -352,6 +354,8 @@ const LOADERS = {
|
|
|
352
354
|
() => import("../utils/cli-telemetry.js"),
|
|
353
355
|
() => import("../utils/cognito-session.js"),
|
|
354
356
|
() => import("../lib/first-sync-next-step.js"),
|
|
357
|
+
() => import("../lib/process-flag-company-context.js"),
|
|
358
|
+
() => import("../lib/flag-snapshot-file.js"),
|
|
355
359
|
() => import("@indigoai-us/hq-cloud"),
|
|
356
360
|
],
|
|
357
361
|
};
|
|
@@ -65,7 +65,7 @@ import chalk from "chalk";
|
|
|
65
65
|
import { Sentry } from "../sentry.js";
|
|
66
66
|
import { isCliTelemetryDisabled } from "./cli-telemetry.js";
|
|
67
67
|
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
68
|
-
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, canWriteNpmInstall, captureNonWritableNpmPrefixNotice, claimNonWritableNpmPrefixNotice, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, pnpmInstalledVersion, performPnpmUpdate, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, UPDATE_RECURSION_GUARD_ENV, } from "./version-gate.js";
|
|
68
|
+
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, canWriteNpmInstall, captureNonWritableNpmPrefixNotice, claimNonWritableNpmPrefixNotice, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, nonWritablePrefixNote, openInstallOutput, pnpmPathConflict, pnpmUpdateEnv, pnpmInstalledVersion, performPnpmUpdate, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, UPDATE_RECURSION_GUARD_ENV, } from "./version-gate.js";
|
|
69
69
|
import { canStageInstall, stagedNpmInstall } from "./staged-install.js";
|
|
70
70
|
import { acquireUpdateLock as acquireSharedUpdateLock } from "./update-lock.js";
|
|
71
71
|
import { markLatestIneffective } from "./version-check.js";
|
|
@@ -931,6 +931,13 @@ async function attemptUpdateAndReexec(argv, flavor, known, deps, env) {
|
|
|
931
931
|
console.error(chalk.dim(`hq-cli ${latest} is available, but this copy is a local dependency (${install.packageRoot}) — update the project that owns it.`));
|
|
932
932
|
return { action: "skipped", latest };
|
|
933
933
|
}
|
|
934
|
+
if (install.manager === "pnpm") {
|
|
935
|
+
const conflict = pnpmPathConflict(install);
|
|
936
|
+
if (conflict) {
|
|
937
|
+
console.error(chalk.yellow(`⚠ hq self-update refused: ${conflict}`));
|
|
938
|
+
return { action: "skipped", latest };
|
|
939
|
+
}
|
|
940
|
+
}
|
|
934
941
|
const releaseLock = flavor.lock ? (deps.acquireLock ?? acquireUpdateLock)() : () => { };
|
|
935
942
|
if (!releaseLock)
|
|
936
943
|
return { action: "skipped", latest };
|
|
@@ -430,6 +430,13 @@ declare function resolveHqOnPath(): string | null;
|
|
|
430
430
|
export declare function parseReportedCliVersion(stdout: string): string | null;
|
|
431
431
|
/** `<bin> --version` output (trimmed), or null on any failure/timeout. */
|
|
432
432
|
declare function probeCliVersion(bin: string): string | null;
|
|
433
|
+
/**
|
|
434
|
+
* Detect a second pnpm global install winning PATH before the updater mutates
|
|
435
|
+
* the running install. pnpm homes have independent global package trees and
|
|
436
|
+
* shims, so writing one home cannot change a different home that appears first
|
|
437
|
+
* on PATH.
|
|
438
|
+
*/
|
|
439
|
+
export declare function pnpmPathConflict(install: RunningInstall): string | null;
|
|
433
440
|
/**
|
|
434
441
|
* Read-your-writes convergence check, run after an install reports success.
|
|
435
442
|
*
|
|
@@ -1395,6 +1395,43 @@ function probeCliVersion(bin) {
|
|
|
1395
1395
|
return null;
|
|
1396
1396
|
}
|
|
1397
1397
|
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Detect a second pnpm global install winning PATH before the updater mutates
|
|
1400
|
+
* the running install. pnpm homes have independent global package trees and
|
|
1401
|
+
* shims, so writing one home cannot change a different home that appears first
|
|
1402
|
+
* on PATH.
|
|
1403
|
+
*/
|
|
1404
|
+
export function pnpmPathConflict(install) {
|
|
1405
|
+
if (install.manager !== "pnpm" || !install.packageRoot)
|
|
1406
|
+
return null;
|
|
1407
|
+
const bin = resolveHqOnPath();
|
|
1408
|
+
if (!bin)
|
|
1409
|
+
return null;
|
|
1410
|
+
const pathPackageRoot = findPackageRootForExecutable(bin);
|
|
1411
|
+
if (!pathPackageRoot ||
|
|
1412
|
+
globalInstallManagerForPath(bin, pathPackageRoot) !== "pnpm") {
|
|
1413
|
+
return null;
|
|
1414
|
+
}
|
|
1415
|
+
// The fallback resolver scans every hash in a PNPM_HOME because generated
|
|
1416
|
+
// shims do not expose their target. Multiple installs in the SAME home can
|
|
1417
|
+
// therefore produce an arbitrary package root; only a different home means
|
|
1418
|
+
// that updating this install cannot change which global shim wins PATH.
|
|
1419
|
+
const runningHome = derivePnpmHome(install);
|
|
1420
|
+
const pathHome = derivePnpmHome({ manager: "pnpm", prefix: null, packageRoot: pathPackageRoot });
|
|
1421
|
+
if (!runningHome || !pathHome || samePath(runningHome, pathHome))
|
|
1422
|
+
return null;
|
|
1423
|
+
let resolvedBin = bin;
|
|
1424
|
+
try {
|
|
1425
|
+
resolvedBin = realpathSync(bin);
|
|
1426
|
+
}
|
|
1427
|
+
catch {
|
|
1428
|
+
// Keep the PATH entry when a platform shim cannot be canonicalized.
|
|
1429
|
+
}
|
|
1430
|
+
const version = probeCliVersion(bin) ?? "unknown";
|
|
1431
|
+
return (`PATH resolves ${resolvedBin} at version ${version} (manager: pnpm), ` +
|
|
1432
|
+
`but this process is running from a different pnpm install at ${install.packageRoot}. ` +
|
|
1433
|
+
"Refusing to update one pnpm home while another hq-cli install wins PATH resolution.");
|
|
1434
|
+
}
|
|
1398
1435
|
/**
|
|
1399
1436
|
* Read-your-writes convergence check, run after an install reports success.
|
|
1400
1437
|
*
|