@indigoai-us/hq-cli 5.342.14 → 5.342.16
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 +9 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +19 -11
- package/dist/commands/bot.js +4 -16
- package/dist/commands/daemon.d.ts +4 -0
- package/dist/commands/daemon.js +5 -18
- package/dist/commands/integrations-connect.d.ts +12 -0
- package/dist/commands/integrations-connect.js +5 -1
- package/dist/commands/integrations-core.d.ts +5 -4
- package/dist/commands/integrations-core.js +64 -10
- package/dist/commands/lanes-accounts.js +8 -0
- package/dist/commands/monitor.d.ts +1 -0
- package/dist/commands/monitor.js +4 -3
- package/dist/lib/agents/terminal-wait.d.ts +4 -0
- package/dist/lib/agents/terminal-wait.js +6 -0
- package/dist/lib/flag-registry.js +3 -1
- package/dist/lib/flag-snapshot-file.d.ts +3 -0
- package/dist/lib/flag-snapshot-file.js +190 -30
- package/dist/lib/lanes/admission.d.ts +7 -1
- package/dist/lib/lanes/admission.js +34 -0
- package/dist/lib/lanes/config.js +11 -0
- package/dist/lib/lanes/flag-keys.d.ts +2 -0
- package/dist/lib/lanes/flag-keys.js +2 -0
- package/dist/lib/lanes/reconcile.js +52 -6
- package/dist/lib/lanes/types.d.ts +3 -1
- package/dist/lib/lanes/types.js +1 -0
- package/dist/lib/log-follow.d.ts +3 -0
- package/dist/lib/log-follow.js +20 -0
- package/dist/main.js +16 -0
- package/dist/utils/resident-package-preload.d.ts +2 -2
- package/dist/utils/resident-package-preload.js +91 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.342.16] — 2026-10-04
|
|
6
|
+
- Keep resident command leases until monitor, agent, OAuth, bot, daemon and lane-watch runtime modules and their transitive Cognito, first-sync, hq-cloud and local-file dependencies are preloaded.
|
|
7
|
+
- CI now runs the core utility parity assertions on Ubuntu, macOS, and Windows.
|
|
8
|
+
- Retry permission-denied flag snapshot cache writes and reads in a uid-owned private fallback, while preserving captures with bounded fallback tags when recovery is unsafe or fails.
|
|
9
|
+
|
|
10
|
+
## [5.342.15] — 2026-10-04
|
|
11
|
+
- Preserve bounded integration subcommand and RPC error context on asynchronous gateway failures, and retain diagnostics for malformed HTTP 200 gateway errors (HQ-CLI-C8/C9).
|
|
12
|
+
- Claude admission can prioritize lanes that are seniors of live lanes and keep configurable per-host capacity reserved for them when `lanes.lead-admission-priority` is enabled.
|
|
13
|
+
|
|
5
14
|
## [5.342.14] — 2026-10-04
|
|
6
15
|
- Fixed native HQ subprocess launching and job-lock command resolution on Windows.
|
|
7
16
|
- Fixed Windows delegate/job shell parity while preserving embedded carriage returns and POSIX command/path bytes.
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* the caller's single active membership (same as `members.ts`).
|
|
29
29
|
*/
|
|
30
30
|
import { Command } from "commander";
|
|
31
|
+
import { type ResidentCommandLeaseOptions } from "../utils/resident-package-preload.js";
|
|
31
32
|
import { type TerminalSessionPayload } from "@indigoai-us/hq-cloud/outposts/node";
|
|
32
33
|
import { type AgentModelProvider, type AgentModelCatalog } from "../lib/agents/model-catalog.js";
|
|
33
34
|
import { type BillingErrorPayload } from "../utils/billing-gate.js";
|
|
@@ -403,6 +404,7 @@ export declare function sendAgentDm(token: string, agentUid: string, message: st
|
|
|
403
404
|
export declare function readAgentThread(token: string, agentUid: string, limit?: number): Promise<DmThreadMessage[]>;
|
|
404
405
|
/** Render one thread message as a labeled line for the terminal. */
|
|
405
406
|
export declare function formatThreadMessage(m: DmThreadMessage, agentLabel: string): string;
|
|
407
|
+
export declare function waitForAgentMessageWithPackageLease<T>(waitForReply: () => Promise<T>, leaseOptions?: ResidentCommandLeaseOptions): Promise<T>;
|
|
406
408
|
export interface EnrollmentIssue {
|
|
407
409
|
code: string;
|
|
408
410
|
expiresAt: string;
|
package/dist/commands/agents.js
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
* (`hq agents list --company acme`); when omitted, resolution falls back to
|
|
28
28
|
* the caller's single active membership (same as `members.ts`).
|
|
29
29
|
*/
|
|
30
|
+
import { runResidentCommandWithPackageLease, } from "../utils/resident-package-preload.js";
|
|
31
|
+
import { waitForAgentTerminalSession } from "../lib/agents/terminal-wait.js";
|
|
30
32
|
import chalk from "chalk";
|
|
31
33
|
import { randomUUID } from "node:crypto";
|
|
32
34
|
import * as readline from "node:readline";
|
|
@@ -37,7 +39,7 @@ import { gateApiKeyCapabilities } from "../utils/api-key-command-gate.js";
|
|
|
37
39
|
import { queueCliLimitNoticeEngagement, queueCliLimitNoticeExposure, } from "../utils/cli-telemetry.js";
|
|
38
40
|
import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
|
|
39
41
|
import { consoleOrigin } from "./integrations-connect.js";
|
|
40
|
-
import { launchSessionManagerPlugin, SessionManagerPluginLaunchError, TerminalSessionTimeoutError,
|
|
42
|
+
import { launchSessionManagerPlugin, SessionManagerPluginLaunchError, TerminalSessionTimeoutError, } from "@indigoai-us/hq-cloud/outposts/node";
|
|
41
43
|
import { peekIdToken } from "../utils/id-token.js";
|
|
42
44
|
import { canOfferTeamUpgrade, formatUpgradeRefusal, isPlanGateError, offerUpgradeLink, } from "../utils/plan-gate-error.js";
|
|
43
45
|
import { AGENT_UID_PATTERN, isAgentUid } from "../lib/notify-recipient.js";
|
|
@@ -1114,6 +1116,9 @@ function shortTime(iso) {
|
|
|
1114
1116
|
const m = iso.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}:\d{2})/);
|
|
1115
1117
|
return m ? `${m[2]}-${m[3]} ${m[4]}` : iso;
|
|
1116
1118
|
}
|
|
1119
|
+
export function waitForAgentMessageWithPackageLease(waitForReply, leaseOptions = {}) {
|
|
1120
|
+
return runResidentCommandWithPackageLease("agents-message-wait", waitForReply, leaseOptions);
|
|
1121
|
+
}
|
|
1117
1122
|
function sleep(ms) {
|
|
1118
1123
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1119
1124
|
}
|
|
@@ -1307,14 +1312,17 @@ export function registerAgentsCommand(program) {
|
|
|
1307
1312
|
const deadline = Date.now() + timeoutMs;
|
|
1308
1313
|
if (!opts.json)
|
|
1309
1314
|
console.log(chalk.dim(`Sent — waiting for ${agent} to reply…`));
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1315
|
+
const reply = await waitForAgentMessageWithPackageLease(async () => {
|
|
1316
|
+
let received;
|
|
1317
|
+
while (Date.now() < deadline) {
|
|
1318
|
+
await sleep(3000);
|
|
1319
|
+
const msgs = await readAgentThread(token, agentUid, 10);
|
|
1320
|
+
received = msgs.find((m) => m.direction === "in" && m.createdAt > lastBeforeAt);
|
|
1321
|
+
if (received)
|
|
1322
|
+
break;
|
|
1323
|
+
}
|
|
1324
|
+
return received;
|
|
1325
|
+
});
|
|
1318
1326
|
if (opts.json) {
|
|
1319
1327
|
process.stdout.write(JSON.stringify({ agentUid, sent: message, reply: reply ?? null }, null, 2) + "\n");
|
|
1320
1328
|
return;
|
|
@@ -1391,7 +1399,7 @@ export function registerAgentsCommand(program) {
|
|
|
1391
1399
|
// registration after a cold start takes ~30-90s. Status lines go to
|
|
1392
1400
|
// stderr (once per state) so stdout stays the session's.
|
|
1393
1401
|
let lastState;
|
|
1394
|
-
const session = await
|
|
1402
|
+
const session = await runResidentCommandWithPackageLease("agents-terminal", () => waitForAgentTerminalSession(async () => {
|
|
1395
1403
|
const result = await openAgentTerminal(token, agentUid, body);
|
|
1396
1404
|
if (result.ok)
|
|
1397
1405
|
return { kind: "ready", session: result };
|
|
@@ -1411,7 +1419,7 @@ export function registerAgentsCommand(program) {
|
|
|
1411
1419
|
? `${agent}'s box is starting — waiting for it to come online (Ctrl-C to cancel)…`
|
|
1412
1420
|
: `${agent}'s box is up — waiting for its agent to register (Ctrl-C to cancel)…`));
|
|
1413
1421
|
},
|
|
1414
|
-
});
|
|
1422
|
+
}));
|
|
1415
1423
|
console.error(chalk.dim(forwardPort !== undefined
|
|
1416
1424
|
? `Forwarding box port ${forwardPort} to localhost:${localPort ?? forwardPort} — Ctrl-C to end.`
|
|
1417
1425
|
: `Connected to ${agent}'s box — type 'exit' or Ctrl-D to leave.`));
|
package/dist/commands/bot.js
CHANGED
|
@@ -27,7 +27,8 @@ import { hasPromotionHold, promotionHoldSummary } from "../lib/bot/promotion-hol
|
|
|
27
27
|
*/
|
|
28
28
|
import { promoteLocalBot } from "../lib/bot/promote.js";
|
|
29
29
|
import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload } from "../utils/package-use-lease.js";
|
|
30
|
-
import { preloadResidentPackageModules, reportResidentPackagePreloadFailure } from "../utils/resident-package-preload.js";
|
|
30
|
+
import { preloadResidentPackageModules, reportResidentPackagePreloadFailure, runResidentCommandWithPackageLease, } from "../utils/resident-package-preload.js";
|
|
31
|
+
import { followLogFile } from "../lib/log-follow.js";
|
|
31
32
|
import { CLI_UPDATE_DISABLE_ENV, createCliUpdateWatcher, handOffToUpdatedCli } from "../lib/bot/cli-update.js";
|
|
32
33
|
import { createVaultObjectDownloader } from "../lib/agent-kit/attachment-download.js";
|
|
33
34
|
import { addBotToCompanies, describeCompanyFailures, removeBotFromCompanies } from "./bot-companies.js";
|
|
@@ -1011,21 +1012,8 @@ async function runBotLogs(nameArg, opts) {
|
|
|
1011
1012
|
console.log(line);
|
|
1012
1013
|
if (!opts.follow)
|
|
1013
1014
|
return;
|
|
1014
|
-
|
|
1015
|
-
await
|
|
1016
|
-
fs.watchFile(file, { interval: 500 }, (curr) => {
|
|
1017
|
-
if (curr.size < size)
|
|
1018
|
-
size = 0;
|
|
1019
|
-
if (curr.size > size) {
|
|
1020
|
-
const fd = fs.openSync(file, "r");
|
|
1021
|
-
const buf = Buffer.alloc(curr.size - size);
|
|
1022
|
-
fs.readSync(fd, buf, 0, buf.length, size);
|
|
1023
|
-
fs.closeSync(fd);
|
|
1024
|
-
process.stdout.write(buf.toString());
|
|
1025
|
-
size = curr.size;
|
|
1026
|
-
}
|
|
1027
|
-
});
|
|
1028
|
-
});
|
|
1015
|
+
const size = fs.statSync(file).size;
|
|
1016
|
+
await runResidentCommandWithPackageLease("bot-logs-follow", () => followLogFile(file, size, (chunk) => process.stdout.write(chunk)));
|
|
1029
1017
|
}
|
|
1030
1018
|
// ── rm ───────────────────────────────────────────────────────────────────────
|
|
1031
1019
|
async function confirm(question) {
|
|
@@ -64,6 +64,10 @@ export declare function resolveDaemonHqRoot(flag: string | undefined, where: {
|
|
|
64
64
|
cwd: string;
|
|
65
65
|
home: string;
|
|
66
66
|
}): string | undefined;
|
|
67
|
+
export declare function runLogs(d: DaemonCommandDeps, unit: string | undefined, opts: {
|
|
68
|
+
follow?: boolean;
|
|
69
|
+
lines?: string;
|
|
70
|
+
}): Promise<void>;
|
|
67
71
|
export declare function registerDaemonCommand(program: Command, over?: Partial<DaemonCommandDeps>): Command;
|
|
68
72
|
export {};
|
|
69
73
|
//# sourceMappingURL=daemon.d.ts.map
|
package/dist/commands/daemon.js
CHANGED
|
@@ -36,7 +36,8 @@ import { hqdRequest, hqdSocketPath, HqdClientError } from "../lib/daemon/protoco
|
|
|
36
36
|
import { parseStdinMachineCredentials } from "../lib/daemon/machine-login.js";
|
|
37
37
|
import { resolveDefaultHqRoot } from "../utils/cognito-session.js";
|
|
38
38
|
import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload } from "../utils/package-use-lease.js";
|
|
39
|
-
import { preloadResidentPackageModules, reportResidentPackagePreloadFailure } from "../utils/resident-package-preload.js";
|
|
39
|
+
import { preloadResidentPackageModules, reportResidentPackagePreloadFailure, runResidentCommandWithPackageLease, } from "../utils/resident-package-preload.js";
|
|
40
|
+
import { followLogFile } from "../lib/log-follow.js";
|
|
40
41
|
/** Load every lazy daemon runtime module before releasing its package-use lease. */
|
|
41
42
|
export async function loadDaemonRunner(release = releaseCurrentPackageUseLease, load = () => import("../lib/daemon/run.js"), preload = () => preloadResidentPackageModules("daemon"), reportPreloadFailure = reportResidentPackagePreloadFailure) {
|
|
42
43
|
const { runDaemon } = await load();
|
|
@@ -401,7 +402,7 @@ function tailLines(file, lines) {
|
|
|
401
402
|
const text = fs.readFileSync(file, "utf8");
|
|
402
403
|
return text.split("\n").filter((l, i, all) => i < all.length - 1 || l !== "").slice(-lines);
|
|
403
404
|
}
|
|
404
|
-
async function runLogs(d, unit, opts) {
|
|
405
|
+
export async function runLogs(d, unit, opts) {
|
|
405
406
|
const { paths } = currentHost(d);
|
|
406
407
|
const file = unit ? paths.unitLogPath(unit) : paths.supervisorLogPath;
|
|
407
408
|
if (!fs.existsSync(file))
|
|
@@ -411,22 +412,8 @@ async function runLogs(d, unit, opts) {
|
|
|
411
412
|
d.out(line);
|
|
412
413
|
if (!opts.follow)
|
|
413
414
|
return;
|
|
414
|
-
|
|
415
|
-
await
|
|
416
|
-
setInterval(() => {
|
|
417
|
-
const size = fs.statSync(file).size;
|
|
418
|
-
if (size < offset)
|
|
419
|
-
offset = 0;
|
|
420
|
-
if (size === offset)
|
|
421
|
-
return;
|
|
422
|
-
const fd = fs.openSync(file, "r");
|
|
423
|
-
const buf = Buffer.alloc(size - offset);
|
|
424
|
-
fs.readSync(fd, buf, 0, buf.length, offset);
|
|
425
|
-
fs.closeSync(fd);
|
|
426
|
-
offset = size;
|
|
427
|
-
process.stdout.write(buf.toString("utf8"));
|
|
428
|
-
}, 500);
|
|
429
|
-
});
|
|
415
|
+
const offset = fs.statSync(file).size;
|
|
416
|
+
await runResidentCommandWithPackageLease("daemon-logs-follow", () => followLogFile(file, offset, (chunk) => process.stdout.write(chunk)));
|
|
430
417
|
}
|
|
431
418
|
function queueRequest(d, request, what) {
|
|
432
419
|
const host = currentHost(d);
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* "key" but is really OAuth-protected still connects on the first try.
|
|
18
18
|
*/
|
|
19
19
|
import { Command } from "commander";
|
|
20
|
+
import { type ResidentCommandLeaseOptions } from "../utils/resident-package-preload.js";
|
|
20
21
|
import { consoleOrigin } from "../utils/console-origin.js";
|
|
21
22
|
export { consoleOrigin };
|
|
22
23
|
import type { AdminConnection } from "./integrations-core.js";
|
|
@@ -62,6 +63,17 @@ interface ConnectTarget {
|
|
|
62
63
|
/** Human label for prompts and success lines. */
|
|
63
64
|
label: string;
|
|
64
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Run the browser sign-in and finish the install.
|
|
68
|
+
*
|
|
69
|
+
* The loopback listener is bound BEFORE `/oauth/start` because its port is
|
|
70
|
+
* part of the redirect URI hq-pro registers with the remote authorization
|
|
71
|
+
* server. If hq-pro refuses that URI (its loopback flag is off, or it predates
|
|
72
|
+
* the feature), the flow degrades to the console handoff rather than failing:
|
|
73
|
+
* `/oauth/start` is retried with no `redirectUri`, and the caller is told to
|
|
74
|
+
* finish in the browser where the console callback completes it.
|
|
75
|
+
*/
|
|
76
|
+
export declare function waitForIntegrationOAuthWithPackageLease<T>(waitForCode: () => Promise<T>, leaseOptions?: ResidentCommandLeaseOptions): Promise<T>;
|
|
65
77
|
/**
|
|
66
78
|
* The console's Integrations page for a company, the console root when the
|
|
67
79
|
* caller never named one (a single-membership session resolves the company
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import chalk from "chalk";
|
|
20
20
|
import open from "open";
|
|
21
21
|
import { ensureCognitoIdToken } from "../utils/cognito-session.js";
|
|
22
|
+
import { runResidentCommandWithPackageLease, } from "../utils/resident-package-preload.js";
|
|
22
23
|
import { consoleOrigin } from "../utils/console-origin.js";
|
|
23
24
|
export { consoleOrigin };
|
|
24
25
|
import { getCompanyUid } from "../utils/vault-api.js";
|
|
@@ -458,6 +459,9 @@ async function assertTargetResolvable(token, companyUid, target) {
|
|
|
458
459
|
* `/oauth/start` is retried with no `redirectUri`, and the caller is told to
|
|
459
460
|
* finish in the browser where the console callback completes it.
|
|
460
461
|
*/
|
|
462
|
+
export function waitForIntegrationOAuthWithPackageLease(waitForCode, leaseOptions = {}) {
|
|
463
|
+
return runResidentCommandWithPackageLease("integrations-oauth-wait", waitForCode, leaseOptions);
|
|
464
|
+
}
|
|
461
465
|
async function connectViaOAuth(token, companyUid, target, opts) {
|
|
462
466
|
const timeoutMs = opts.timeout ? Number(opts.timeout) * 1000 : undefined;
|
|
463
467
|
if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
|
|
@@ -529,7 +533,7 @@ async function connectViaOAuth(token, companyUid, target, opts) {
|
|
|
529
533
|
}
|
|
530
534
|
let code;
|
|
531
535
|
try {
|
|
532
|
-
code = await pendingCode;
|
|
536
|
+
code = await waitForIntegrationOAuthWithPackageLease(() => pendingCode);
|
|
533
537
|
}
|
|
534
538
|
catch (err) {
|
|
535
539
|
if (err instanceof IntegrationsCliError &&
|
|
@@ -110,11 +110,9 @@ export interface AdminSurface {
|
|
|
110
110
|
}
|
|
111
111
|
interface GatewayMessage {
|
|
112
112
|
result?: unknown;
|
|
113
|
-
error?:
|
|
114
|
-
code?: number;
|
|
115
|
-
message?: string;
|
|
116
|
-
};
|
|
113
|
+
error?: unknown;
|
|
117
114
|
}
|
|
115
|
+
export type GatewayErrorShape = "string_error" | "object_no_code" | "object_code" | "string_code" | "nested_code" | "empty" | "redacted_empty";
|
|
118
116
|
export declare class IntegrationsCliError extends Error {
|
|
119
117
|
/**
|
|
120
118
|
* True when the error is the caller's request/state/permission (a client 4xx
|
|
@@ -152,6 +150,8 @@ export declare class IntegrationsCliError extends Error {
|
|
|
152
150
|
readonly rpcCode?: number;
|
|
153
151
|
/** Fixed vocabulary for the integrations subcommand that called the gateway. */
|
|
154
152
|
readonly gatewaySubcommand?: "tools_list" | "tools_call" | "other";
|
|
153
|
+
/** Fixed vocabulary describing the shape of an upstream JSON-RPC error. */
|
|
154
|
+
readonly gatewayErrorShape?: GatewayErrorShape;
|
|
155
155
|
/**
|
|
156
156
|
* HQ's own OAuth callback URL, carried on the errors that say a provider
|
|
157
157
|
* refused to register HQ automatically. The provider must register the exact
|
|
@@ -167,6 +167,7 @@ export declare class IntegrationsCliError extends Error {
|
|
|
167
167
|
oauthProtected?: boolean;
|
|
168
168
|
rpcCode?: number;
|
|
169
169
|
gatewaySubcommand?: "tools_list" | "tools_call" | "other";
|
|
170
|
+
gatewayErrorShape?: GatewayErrorShape;
|
|
170
171
|
redirectUri?: string;
|
|
171
172
|
});
|
|
172
173
|
}
|
|
@@ -57,6 +57,8 @@ export class IntegrationsCliError extends Error {
|
|
|
57
57
|
rpcCode;
|
|
58
58
|
/** Fixed vocabulary for the integrations subcommand that called the gateway. */
|
|
59
59
|
gatewaySubcommand;
|
|
60
|
+
/** Fixed vocabulary describing the shape of an upstream JSON-RPC error. */
|
|
61
|
+
gatewayErrorShape;
|
|
60
62
|
/**
|
|
61
63
|
* HQ's own OAuth callback URL, carried on the errors that say a provider
|
|
62
64
|
* refused to register HQ automatically. The provider must register the exact
|
|
@@ -79,6 +81,8 @@ export class IntegrationsCliError extends Error {
|
|
|
79
81
|
this.rpcCode = opts.rpcCode;
|
|
80
82
|
if (opts.gatewaySubcommand !== undefined)
|
|
81
83
|
this.gatewaySubcommand = opts.gatewaySubcommand;
|
|
84
|
+
if (opts.gatewayErrorShape !== undefined)
|
|
85
|
+
this.gatewayErrorShape = opts.gatewayErrorShape;
|
|
82
86
|
if (opts.redirectUri !== undefined) {
|
|
83
87
|
Object.defineProperty(this, "redirectUri", {
|
|
84
88
|
value: opts.redirectUri,
|
|
@@ -191,6 +195,54 @@ function expectedGatewayErrorFallback(code) {
|
|
|
191
195
|
return undefined;
|
|
192
196
|
}
|
|
193
197
|
}
|
|
198
|
+
function recordValue(value) {
|
|
199
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
200
|
+
? value
|
|
201
|
+
: undefined;
|
|
202
|
+
}
|
|
203
|
+
function numericGatewayCode(value) {
|
|
204
|
+
if (typeof value === "number" && Number.isSafeInteger(value))
|
|
205
|
+
return value;
|
|
206
|
+
if (typeof value === "string" && /^-?\d+$/.test(value.trim())) {
|
|
207
|
+
const parsed = Number(value.trim());
|
|
208
|
+
if (Number.isSafeInteger(parsed))
|
|
209
|
+
return parsed;
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
/** Read the known JSON-RPC error fields without stringifying arbitrary payload data. */
|
|
214
|
+
function gatewayErrorDetails(error) {
|
|
215
|
+
if (typeof error === "string") {
|
|
216
|
+
const message = redactErrorText(error);
|
|
217
|
+
return {
|
|
218
|
+
message,
|
|
219
|
+
shape: message ? "string_error" : "redacted_empty",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const record = recordValue(error);
|
|
223
|
+
if (!record)
|
|
224
|
+
return { message: "", shape: "empty" };
|
|
225
|
+
const data = recordValue(record.data);
|
|
226
|
+
const rawMessage = typeof record.message === "string" ? record.message : undefined;
|
|
227
|
+
const message = rawMessage === undefined ? "" : redactErrorText(rawMessage);
|
|
228
|
+
const directCode = record.code;
|
|
229
|
+
const nestedCode = data?.code;
|
|
230
|
+
const directRpcCode = numericGatewayCode(directCode);
|
|
231
|
+
const nestedRpcCode = numericGatewayCode(nestedCode);
|
|
232
|
+
const rpcCode = directRpcCode ?? nestedRpcCode;
|
|
233
|
+
const hasStringCode = typeof directCode === "string" || typeof nestedCode === "string";
|
|
234
|
+
const redactedEmptyMessage = rawMessage !== undefined && rawMessage.length > 0 && !message;
|
|
235
|
+
const shape = redactedEmptyMessage
|
|
236
|
+
? "redacted_empty"
|
|
237
|
+
: nestedCode !== undefined
|
|
238
|
+
? "nested_code"
|
|
239
|
+
: hasStringCode
|
|
240
|
+
? "string_code"
|
|
241
|
+
: directCode !== undefined
|
|
242
|
+
? "object_code"
|
|
243
|
+
: message ? "object_no_code" : "empty";
|
|
244
|
+
return { message, ...(rpcCode === undefined ? {} : { rpcCode }), shape };
|
|
245
|
+
}
|
|
194
246
|
// A 401 from ANY integration-gateway vault call means the caller's HQ session
|
|
195
247
|
// is expired or missing — an expected auth state fixed by `hq login`, not an
|
|
196
248
|
// hq-cli defect. Raise the same typed AuthError the vault company-resolution
|
|
@@ -446,16 +498,16 @@ export async function callGateway(token, params, idempotencyKey) {
|
|
|
446
498
|
const accepted = message;
|
|
447
499
|
const jobId = accepted && typeof accepted.jobId === "string" ? accepted.jobId : "";
|
|
448
500
|
if (accepted?.status !== "pending" || !/^imj_[A-Za-z0-9_-]+$/.test(jobId)) {
|
|
449
|
-
throw new IntegrationsCliError(`Integration gateway returned an invalid async job response (job id: ${jobId || "missing"}).`, { expected: false, status: 202 });
|
|
501
|
+
throw new IntegrationsCliError(`Integration gateway returned an invalid async job response (job id: ${jobId || "missing"}).`, { expected: false, status: 202, gatewaySubcommand });
|
|
450
502
|
}
|
|
451
503
|
const companyUid = typeof params.companyUid === "string" ? params.companyUid : "";
|
|
452
504
|
if (!companyUid) {
|
|
453
505
|
throw new IntegrationsCliError(`Cannot poll integration job ${jobId}: the company id is missing.`, { expected: true });
|
|
454
506
|
}
|
|
455
507
|
if (!idempotencyKey) {
|
|
456
|
-
throw new IntegrationsCliError(`Integration gateway accepted async job ${jobId} without an idempotency key.`, { expected: false, status: 202 });
|
|
508
|
+
throw new IntegrationsCliError(`Integration gateway accepted async job ${jobId} without an idempotency key.`, { expected: false, status: 202, gatewaySubcommand });
|
|
457
509
|
}
|
|
458
|
-
const terminal = await pollAsyncGatewayJob(token, companyUid, jobId, idempotencyKey, deadline);
|
|
510
|
+
const terminal = await pollAsyncGatewayJob(token, companyUid, jobId, idempotencyKey, deadline, gatewaySubcommand);
|
|
459
511
|
terminalMessage = terminal.message;
|
|
460
512
|
terminalStatus = terminal.status;
|
|
461
513
|
}
|
|
@@ -470,6 +522,7 @@ export async function callGateway(token, params, idempotencyKey) {
|
|
|
470
522
|
throw new IntegrationsCliError(`Integration gateway request failed (HTTP ${res.status}).`, { expected: isClientError(res.status), status: res.status, gatewaySubcommand });
|
|
471
523
|
}
|
|
472
524
|
if (terminalMessage?.error) {
|
|
525
|
+
const gatewayError = gatewayErrorDetails(terminalMessage.error);
|
|
473
526
|
// The gateway's error text is minted UPSTREAM (hq-pro, the integration
|
|
474
527
|
// factory, and beyond it the third-party provider), so it is untrusted:
|
|
475
528
|
// scrub credentials and bound the length here, at the throw site, because
|
|
@@ -478,21 +531,22 @@ export async function callGateway(token, params, idempotencyKey) {
|
|
|
478
531
|
// The chain is idempotent, so the unexpected path scrubbing again is a
|
|
479
532
|
// no-op. This preserves PR #298's user-visible diagnostic; it only makes
|
|
480
533
|
// it safe on the newly-expected path.
|
|
481
|
-
throw new IntegrationsCliError(
|
|
482
|
-
expectedGatewayErrorFallback(
|
|
534
|
+
throw new IntegrationsCliError(gatewayError.message ||
|
|
535
|
+
expectedGatewayErrorFallback(gatewayError.rpcCode) ||
|
|
483
536
|
"Integration gateway returned an error.", {
|
|
484
|
-
expected: isExpectedGatewayError(
|
|
537
|
+
expected: isExpectedGatewayError(gatewayError.rpcCode),
|
|
485
538
|
status: terminalStatus,
|
|
486
539
|
gatewaySubcommand,
|
|
540
|
+
gatewayErrorShape: gatewayError.shape,
|
|
487
541
|
// Carry the protocol code to the capture site as a bounded grouping
|
|
488
542
|
// discriminator (HQ-CLI collision, Sentry 7642756130). This does not
|
|
489
543
|
// alter the `expected` decision above; it only labels the error.
|
|
490
|
-
...(
|
|
544
|
+
...(gatewayError.rpcCode === undefined ? {} : { rpcCode: gatewayError.rpcCode }),
|
|
491
545
|
});
|
|
492
546
|
}
|
|
493
547
|
return terminalMessage;
|
|
494
548
|
}
|
|
495
|
-
async function pollAsyncGatewayJob(token, companyUid, jobId, idempotencyKey, deadline) {
|
|
549
|
+
async function pollAsyncGatewayJob(token, companyUid, jobId, idempotencyKey, deadline, gatewaySubcommand) {
|
|
496
550
|
const jobPath = `/v1/integrations/mcp/jobs/${encodeURIComponent(jobId)}`;
|
|
497
551
|
while (Date.now() < deadline) {
|
|
498
552
|
const requestRemainingMs = deadline - Date.now();
|
|
@@ -526,7 +580,7 @@ async function pollAsyncGatewayJob(token, companyUid, jobId, idempotencyKey, dea
|
|
|
526
580
|
if (res.status === 404) {
|
|
527
581
|
throw new IntegrationsCliError(`Integration job ${jobId} was not found or is no longer available.`, { expected: true, status: 404 });
|
|
528
582
|
}
|
|
529
|
-
throw new IntegrationsCliError(`Integration job ${jobId} polling failed (HTTP ${res.status}).`, { expected: isClientError(res.status), status: res.status });
|
|
583
|
+
throw new IntegrationsCliError(`Integration job ${jobId} polling failed (HTTP ${res.status}).`, { expected: isClientError(res.status), status: res.status, gatewaySubcommand });
|
|
530
584
|
}
|
|
531
585
|
if (!poll || typeof poll !== "object") {
|
|
532
586
|
throw new IntegrationsCliError(`Integration job ${jobId} returned an invalid poll response.`, { expected: true });
|
|
@@ -549,7 +603,7 @@ async function pollAsyncGatewayJob(token, companyUid, jobId, idempotencyKey, dea
|
|
|
549
603
|
hasResult !== hasError &&
|
|
550
604
|
(!hasError || validError);
|
|
551
605
|
if (!hasJsonRpcOutcome) {
|
|
552
|
-
throw new IntegrationsCliError(`Integration job ${jobId} returned an invalid JSON-RPC response.`, { expected: false });
|
|
606
|
+
throw new IntegrationsCliError(`Integration job ${jobId} returned an invalid JSON-RPC response.`, { expected: false, gatewaySubcommand });
|
|
553
607
|
}
|
|
554
608
|
return { message: response, status: res.status };
|
|
555
609
|
}
|
|
@@ -10,6 +10,7 @@ import { resolveLaneEffort } from "../lib/lanes/effort.js";
|
|
|
10
10
|
import { LanesError } from "../lib/lanes/errors.js";
|
|
11
11
|
import { accountQuotaView, clearAccountQuota, loadQuotaState } from "../lib/lanes/quota.js";
|
|
12
12
|
import { resolveLaneModel } from "../lib/lanes/spawn.js";
|
|
13
|
+
import { leadLaneIds } from "../lib/lanes/admission.js";
|
|
13
14
|
export function createLaneAccountsHandlers(deps) {
|
|
14
15
|
const { automaticBalanceMoveHistoryReason, ensureDir, laneWithRecoveredProviderSession, loadClaudeBalanceHostSnapshots, parseRemoteJson, prepareAccountMove, printJson, reconcileHostFromCtx, resolveCallerPath } = deps;
|
|
15
16
|
const VENDOR_LOGIN = {
|
|
@@ -269,6 +270,13 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
269
270
|
balance_gate_enabled: balanceGateEnabled,
|
|
270
271
|
accounts,
|
|
271
272
|
selections,
|
|
273
|
+
lead_reserve_per_host: config.lead_reserve_per_host,
|
|
274
|
+
queued_leads: loaded.snapshots.flatMap((host) => {
|
|
275
|
+
const leads = leadLaneIds(host.lanes);
|
|
276
|
+
return host.lanes
|
|
277
|
+
.filter((lane) => lane.provider === "claude" && lane.state === "queued" && leads.has(lane.lane_id))
|
|
278
|
+
.map((lane) => ({ lane_id: lane.lane_id, host: host.capacity.host, queued_reason: lane.queued_reason ?? null }));
|
|
279
|
+
}),
|
|
272
280
|
unavailable_hosts: loaded.unavailableHosts,
|
|
273
281
|
suggestions,
|
|
274
282
|
...(opts.apply ? { moved, skipped } : {}),
|
|
@@ -40,6 +40,7 @@ export interface RunSupervisorDeps {
|
|
|
40
40
|
reportPreloadFailure?: (error: unknown) => void;
|
|
41
41
|
createLaneMessageApi?: typeof createMonitorLaneMessageApi;
|
|
42
42
|
}
|
|
43
|
+
/** Preload every monitor runtime; only lane targets need the in-process message API. */
|
|
43
44
|
export declare function preloadMonitorLaneMessageApi(hqRoot: string, meta: MonitorMetadata, deps?: Pick<RunSupervisorDeps, "preloadResidentModules" | "releasePackageUseLease" | "reportPreloadFailure" | "createLaneMessageApi">): Promise<MonitorLaneMessageApi | undefined>;
|
|
44
45
|
export declare function runSupervisor(id: string, deps?: RunSupervisorDeps): Promise<void>;
|
|
45
46
|
interface MonitorHookResult {
|
package/dist/commands/monitor.js
CHANGED
|
@@ -575,13 +575,14 @@ function activeTargetMeta(hqRoot, targetKey) {
|
|
|
575
575
|
return meta?.target_key === targetKey && state?.status === "running" && state.pid !== null && readMonitorPidLiveness(state.pid, state.starttime) ? [meta] : [];
|
|
576
576
|
});
|
|
577
577
|
}
|
|
578
|
+
/** Preload every monitor runtime; only lane targets need the in-process message API. */
|
|
578
579
|
export async function preloadMonitorLaneMessageApi(hqRoot, meta, deps = {}) {
|
|
579
|
-
if (meta.target.kind !== "lane")
|
|
580
|
-
return undefined;
|
|
581
580
|
let laneMessageApi;
|
|
582
581
|
await releasePackageUseLeaseAfterResidentPreload(async () => {
|
|
583
582
|
await (deps.preloadResidentModules ?? (() => preloadResidentPackageModules("monitor-run")))();
|
|
584
|
-
|
|
583
|
+
if (meta.target.kind === "lane") {
|
|
584
|
+
laneMessageApi = (deps.createLaneMessageApi ?? createMonitorLaneMessageApi)(hqRoot, meta.cwd, process.env);
|
|
585
|
+
}
|
|
585
586
|
}, deps.reportPreloadFailure ?? reportResidentPackagePreloadFailure, deps.releasePackageUseLease ?? releaseCurrentPackageUseLease);
|
|
586
587
|
return laneMessageApi;
|
|
587
588
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TerminalSessionAttempt, TerminalSessionPayload, WaitForTerminalSessionOptions } from "@indigoai-us/hq-cloud/outposts/node";
|
|
2
|
+
/** Keep the terminal poll loop behind a small, auditable resident preload root. */
|
|
3
|
+
export declare function waitForAgentTerminalSession<T extends TerminalSessionPayload>(attempt: () => Promise<TerminalSessionAttempt<T>>, options?: WaitForTerminalSessionOptions): Promise<T>;
|
|
4
|
+
//# sourceMappingURL=terminal-wait.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { waitForTerminalSession } from "@indigoai-us/hq-cloud/outposts/node";
|
|
2
|
+
/** Keep the terminal poll loop behind a small, auditable resident preload root. */
|
|
3
|
+
export function waitForAgentTerminalSession(attempt, options) {
|
|
4
|
+
return waitForTerminalSession(attempt, options);
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=terminal-wait.js.map
|
|
@@ -11,7 +11,7 @@ import { resolveFlagRegistryEndpoint, } from "./flag-registry-endpoint.js";
|
|
|
11
11
|
import { FLAG_REGISTRY_WORKER_SOURCE } from "./flag-registry-worker-source.generated.js";
|
|
12
12
|
import { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
13
13
|
import { registerFlagRegistryRefreshWaiter } from "./flag-registry-refresh-wait.js";
|
|
14
|
-
import { writeFlagSnapshotFile } from "./flag-snapshot-file.js";
|
|
14
|
+
import { flagSnapshotWriteFailureTags, writeFlagSnapshotFile } from "./flag-snapshot-file.js";
|
|
15
15
|
function captureFlagRegistryException(error, tags, sentry) {
|
|
16
16
|
if (!sentry)
|
|
17
17
|
return;
|
|
@@ -63,6 +63,7 @@ export async function waitForFlagRegistryRefresh(timeoutMs = 500, companyUid) {
|
|
|
63
63
|
captureFlagRegistryException(error instanceof Error ? error : new Error(String(error)), {
|
|
64
64
|
component: "flag-snapshot-cache-write",
|
|
65
65
|
company_uid_present: "true",
|
|
66
|
+
...flagSnapshotWriteFailureTags(error),
|
|
66
67
|
}, refreshSentry);
|
|
67
68
|
}
|
|
68
69
|
refreshPendingSnapshot = null;
|
|
@@ -215,6 +216,7 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
|
|
|
215
216
|
captureFlagRegistryException(error instanceof Error ? error : new Error(String(error)), {
|
|
216
217
|
component: "flag-snapshot-cache-write",
|
|
217
218
|
company_uid_present: "true",
|
|
219
|
+
...flagSnapshotWriteFailureTags(error),
|
|
218
220
|
}, sentry);
|
|
219
221
|
}
|
|
220
222
|
refreshPendingSnapshot = null;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/** Private, synchronous, offline snapshot used by the native CLI fast route. */
|
|
2
2
|
export declare const FLAG_SNAPSHOT_MAX_AGE_MS: number;
|
|
3
|
+
export type FlagSnapshotWriteStep = "mkdir" | "chmod_dir" | "open_tmp" | "write_tmp" | "rename" | "chmod_file";
|
|
4
|
+
export declare function flagSnapshotWriteFailureTags(error: unknown): Record<string, string>;
|
|
3
5
|
export interface PersistedFlagSnapshot {
|
|
4
6
|
company_uid: string;
|
|
5
7
|
fetched_at: string;
|
|
6
8
|
flags: Record<string, boolean>;
|
|
7
9
|
}
|
|
10
|
+
export declare function _resetFlagSnapshotCacheForTests(): void;
|
|
8
11
|
export declare function flagSnapshotPath(companyUid: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
9
12
|
/** Write just the validated flag decisions and company scope, never auth data. */
|
|
10
13
|
export declare function writeFlagSnapshotFile(companyUid: string, flags: Readonly<Record<string, unknown>>, options?: {
|