@indigoai-us/hq-cli 5.342.15 → 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 CHANGED
@@ -2,6 +2,11 @@
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
+
5
10
  ## [5.342.15] — 2026-10-04
6
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).
7
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.
@@ -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;
@@ -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, waitForTerminalSession, } from "@indigoai-us/hq-cloud/outposts/node";
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
- let reply;
1311
- while (Date.now() < deadline) {
1312
- await sleep(3000);
1313
- const msgs = await readAgentThread(token, agentUid, 10);
1314
- reply = msgs.find((m) => m.direction === "in" && m.createdAt > lastBeforeAt);
1315
- if (reply)
1316
- break;
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 waitForTerminalSession(async () => {
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.`));
@@ -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
- let size = fs.statSync(file).size;
1015
- await new Promise(() => {
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
@@ -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
- let offset = fs.statSync(file).size;
415
- await new Promise(() => {
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 &&
@@ -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 {
@@ -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
- laneMessageApi = (deps.createLaneMessageApi ?? createMonitorLaneMessageApi)(hqRoot, meta.cwd, process.env);
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?: {
@@ -4,6 +4,104 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  export const FLAG_SNAPSHOT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
6
6
  const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
7
+ function markWriteFailure(error, context) {
8
+ if (!error || (typeof error !== "object" && typeof error !== "function"))
9
+ return;
10
+ const failure = error;
11
+ let dirOwnerMatchesUid = "unknown";
12
+ if (typeof process.getuid === "function") {
13
+ try {
14
+ dirOwnerMatchesUid = fs.statSync(context.directory).uid === process.getuid()
15
+ ? "true"
16
+ : "false";
17
+ }
18
+ catch {
19
+ // A missing or inaccessible directory has no safely knowable owner.
20
+ }
21
+ }
22
+ try {
23
+ Object.defineProperties(failure, {
24
+ flagSnapshotStep: { value: context.step, configurable: true },
25
+ cacheRootKind: { value: context.cacheRootKind, configurable: true },
26
+ dirOwnerMatchesUid: { value: dirOwnerMatchesUid, configurable: true },
27
+ });
28
+ }
29
+ catch {
30
+ // Preserve the original filesystem error if it cannot carry diagnostics.
31
+ }
32
+ }
33
+ function atWriteStep(context, operation) {
34
+ try {
35
+ return operation();
36
+ }
37
+ catch (error) {
38
+ markWriteFailure(error, context);
39
+ throw error;
40
+ }
41
+ }
42
+ export function flagSnapshotWriteFailureTags(error) {
43
+ const failure = error;
44
+ const code = failure && typeof failure.code === "string" ? failure.code : "";
45
+ const allowedCodes = new Set(["EACCES", "EPERM", "EROFS", "ENOSPC"]);
46
+ const step = failure?.flagSnapshotStep;
47
+ const rootKind = failure?.cacheRootKind;
48
+ const owner = failure?.dirOwnerMatchesUid;
49
+ const tags = {
50
+ flag_snapshot_step: step && ["mkdir", "chmod_dir", "open_tmp", "write_tmp", "rename", "chmod_file"].includes(step)
51
+ ? step
52
+ : "other",
53
+ errno_code: allowedCodes.has(code) ? code : "other",
54
+ cache_root_kind: rootKind === "xdg" || rootKind === "home" ? rootKind : "unknown",
55
+ dir_owner_matches_uid: owner === "true" || owner === "false" ? owner : "unknown",
56
+ };
57
+ if (fallbackCacheUsed)
58
+ tags.cache_root = "fallback";
59
+ if (failure?.fallbackFailed)
60
+ tags.fallback = "failed";
61
+ return tags;
62
+ }
63
+ let fallbackCacheUsed = false;
64
+ export function _resetFlagSnapshotCacheForTests() {
65
+ fallbackCacheUsed = false;
66
+ }
67
+ function markFallbackFailed(error) {
68
+ if (!error || (typeof error !== "object" && typeof error !== "function"))
69
+ return;
70
+ try {
71
+ Object.defineProperty(error, "fallbackFailed", { value: true, configurable: true });
72
+ }
73
+ catch {
74
+ // Keep the original filesystem error intact if it cannot carry diagnostics.
75
+ }
76
+ }
77
+ function isPermissionFailure(error) {
78
+ const code = error?.code;
79
+ return code === "EACCES" || code === "EPERM";
80
+ }
81
+ function fallbackSnapshotPath(companyUid, create) {
82
+ if (typeof process.getuid !== "function")
83
+ return undefined;
84
+ const uid = process.getuid();
85
+ const root = path.join(os.tmpdir(), `hq-cli-flag-snapshots-${uid}`);
86
+ const directories = [root, path.join(root, "hq"), path.join(root, "hq", "flags")];
87
+ for (const directory of directories) {
88
+ try {
89
+ if (create)
90
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
91
+ const stat = fs.lstatSync(directory);
92
+ if (!stat.isDirectory() || stat.isSymbolicLink() || stat.uid !== uid)
93
+ return undefined;
94
+ if (create)
95
+ fs.chmodSync(directory, 0o700);
96
+ else if ((stat.mode & 0o077) !== 0)
97
+ return undefined;
98
+ }
99
+ catch {
100
+ return undefined;
101
+ }
102
+ }
103
+ return path.join(directories[2], `snapshot-${companyUid}.json`);
104
+ }
7
105
  export function flagSnapshotPath(companyUid, env = process.env) {
8
106
  if (!COMPANY_UID_RE.test(companyUid))
9
107
  return undefined;
@@ -12,7 +110,8 @@ export function flagSnapshotPath(companyUid, env = process.env) {
12
110
  }
13
111
  /** Write just the validated flag decisions and company scope, never auth data. */
14
112
  export function writeFlagSnapshotFile(companyUid, flags, options = {}) {
15
- const file = flagSnapshotPath(companyUid, options.env);
113
+ const env = options.env ?? process.env;
114
+ const file = flagSnapshotPath(companyUid, env);
16
115
  if (!file)
17
116
  throw new Error("Cannot persist a flag snapshot without a valid company uid");
18
117
  const normalizedFlags = Object.fromEntries(Object.entries(flags).filter((entry) => Boolean(entry[0].trim()) && typeof entry[1] === "boolean"));
@@ -24,39 +123,78 @@ export function writeFlagSnapshotFile(companyUid, flags, options = {}) {
24
123
  if (!Number.isFinite(Date.parse(snapshot.fetched_at))) {
25
124
  throw new Error("Cannot persist a flag snapshot with an invalid fetched_at timestamp");
26
125
  }
27
- const directory = path.dirname(file);
28
- fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
29
- fs.chmodSync(directory, 0o700);
30
- const temporary = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
31
- let fd;
32
- try {
33
- fd = fs.openSync(temporary, "wx", 0o600);
34
- fs.writeFileSync(fd, `${JSON.stringify(snapshot)}\n`, "utf8");
35
- fs.fsyncSync(fd);
36
- fs.closeSync(fd);
37
- fd = undefined;
38
- fs.renameSync(temporary, file);
39
- fs.chmodSync(file, 0o600);
40
- }
41
- catch (error) {
42
- if (fd !== undefined)
43
- fs.closeSync(fd);
126
+ const cacheRootKind = env.XDG_CACHE_HOME?.trim() ? "xdg" : "home";
127
+ const writeAt = (destination) => {
128
+ const directory = path.dirname(destination);
129
+ const context = (step) => ({
130
+ step,
131
+ cacheRootKind,
132
+ directory,
133
+ });
134
+ atWriteStep(context("mkdir"), () => fs.mkdirSync(directory, { recursive: true, mode: 0o700 }));
135
+ const temporary = `${destination}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
136
+ let fd;
44
137
  try {
45
- fs.unlinkSync(temporary);
138
+ fd = atWriteStep(context("open_tmp"), () => fs.openSync(temporary, "wx", 0o600));
139
+ atWriteStep(context("write_tmp"), () => {
140
+ fs.writeFileSync(fd, `${JSON.stringify(snapshot)}\n`, "utf8");
141
+ fs.fsyncSync(fd);
142
+ fs.closeSync(fd);
143
+ fd = undefined;
144
+ });
145
+ atWriteStep(context("rename"), () => fs.renameSync(temporary, destination));
46
146
  }
47
- catch (cleanupError) {
48
- if (cleanupError.code !== "ENOENT")
49
- throw cleanupError;
147
+ catch (error) {
148
+ if (fd !== undefined)
149
+ fs.closeSync(fd);
150
+ try {
151
+ fs.unlinkSync(temporary);
152
+ }
153
+ catch (cleanupError) {
154
+ if (cleanupError.code !== "ENOENT")
155
+ throw cleanupError;
156
+ }
157
+ throw error;
50
158
  }
51
- throw error;
159
+ return destination;
160
+ };
161
+ const writeFallback = (originalError) => {
162
+ const fallbackFile = fallbackSnapshotPath(companyUid, true);
163
+ if (!fallbackFile) {
164
+ if (originalError) {
165
+ markFallbackFailed(originalError);
166
+ throw originalError;
167
+ }
168
+ const error = Object.assign(new Error("Flag snapshot fallback directory is unavailable"), { code: "EACCES" });
169
+ markFallbackFailed(error);
170
+ throw error;
171
+ }
172
+ try {
173
+ const written = writeAt(fallbackFile);
174
+ fallbackCacheUsed = true;
175
+ return written;
176
+ }
177
+ catch (fallbackError) {
178
+ if (originalError) {
179
+ markFallbackFailed(originalError);
180
+ throw originalError;
181
+ }
182
+ markFallbackFailed(fallbackError);
183
+ throw fallbackError;
184
+ }
185
+ };
186
+ if (fallbackCacheUsed)
187
+ return writeFallback();
188
+ try {
189
+ return writeAt(file);
190
+ }
191
+ catch (error) {
192
+ if (!isPermissionFailure(error))
193
+ throw error;
194
+ return writeFallback(error);
52
195
  }
53
- return file;
54
196
  }
55
- /** Return null for every miss, malformed file, company mismatch, or stale file. */
56
- export function readFlagSnapshotFile(companyUid, options = {}) {
57
- const file = flagSnapshotPath(companyUid, options.env);
58
- if (!file)
59
- return null;
197
+ function parseSnapshotFile(companyUid, file, options) {
60
198
  let parsed;
61
199
  try {
62
200
  parsed = JSON.parse(fs.readFileSync(file, "utf8"));
@@ -71,7 +209,7 @@ export function readFlagSnapshotFile(companyUid, options = {}) {
71
209
  value.company_uid !== companyUid ||
72
210
  typeof value.fetched_at !== "string" ||
73
211
  !value.flags || typeof value.flags !== "object" || Array.isArray(value.flags))
74
- return null; // Expected on missing, malformed, unreadable, or cross-company cache data.
212
+ return null;
75
213
  const fetchedAt = Date.parse(value.fetched_at);
76
214
  const age = (options.now ?? Date.now()) - fetchedAt;
77
215
  if (!Number.isFinite(fetchedAt) || age < 0 || age > (options.maxAgeMs ?? FLAG_SNAPSHOT_MAX_AGE_MS)) {
@@ -88,4 +226,26 @@ export function readFlagSnapshotFile(companyUid, options = {}) {
88
226
  flags: flags,
89
227
  };
90
228
  }
229
+ /** Return null for every miss, malformed file, company mismatch, or stale file. */
230
+ export function readFlagSnapshotFile(companyUid, options = {}) {
231
+ const file = flagSnapshotPath(companyUid, options.env);
232
+ if (!file)
233
+ return null;
234
+ if (fallbackCacheUsed) {
235
+ const fallback = fallbackSnapshotPath(companyUid, false);
236
+ const snapshot = fallback ? parseSnapshotFile(companyUid, fallback, options) : null;
237
+ if (snapshot)
238
+ return snapshot;
239
+ }
240
+ const primary = parseSnapshotFile(companyUid, file, options);
241
+ if (primary)
242
+ return primary;
243
+ const fallback = fallbackSnapshotPath(companyUid, false);
244
+ if (!fallback)
245
+ return null;
246
+ const snapshot = parseSnapshotFile(companyUid, fallback, options);
247
+ if (snapshot)
248
+ fallbackCacheUsed = true;
249
+ return snapshot;
250
+ }
91
251
  //# sourceMappingURL=flag-snapshot-file.js.map
@@ -0,0 +1,3 @@
1
+ /** Follow append-only log bytes without importing package code after startup. */
2
+ export declare function followLogFile(file: string, initialOffset: number, write: (chunk: string) => void): Promise<void>;
3
+ //# sourceMappingURL=log-follow.d.ts.map
@@ -0,0 +1,20 @@
1
+ import * as fs from "node:fs";
2
+ /** Follow append-only log bytes without importing package code after startup. */
3
+ export async function followLogFile(file, initialOffset, write) {
4
+ let offset = initialOffset;
5
+ await new Promise(() => {
6
+ fs.watchFile(file, { interval: 500 }, (curr) => {
7
+ if (curr.size < offset)
8
+ offset = 0;
9
+ if (curr.size === offset)
10
+ return;
11
+ const fd = fs.openSync(file, "r");
12
+ const buf = Buffer.alloc(curr.size - offset);
13
+ fs.readSync(fd, buf, 0, buf.length, offset);
14
+ fs.closeSync(fd);
15
+ offset = curr.size;
16
+ write(buf.toString("utf8"));
17
+ });
18
+ });
19
+ }
20
+ //# sourceMappingURL=log-follow.js.map
@@ -1,5 +1,5 @@
1
- export type ResidentPackagePath = "agent-kit" | "agent-mcp" | "auth-login" | "bot" | "childWrapper" | "coreJobRun" | "login" | "mcp" | "daemon" | "mesh-daemon" | "lanes-watch" | "monitor-wait" | "monitor-run";
2
- /** Package imports reachable lazily from each resident command's runtime graph. */
1
+ export type ResidentPackagePath = "agent-kit" | "agent-mcp" | "auth-login" | "agents-terminal" | "bot" | "bot-logs-follow" | "childWrapper" | "coreJobRun" | "login" | "mcp" | "daemon" | "daemon-logs-follow" | "mesh-daemon" | "lanes-watch" | "monitor-wait" | "monitor-run" | "agents-message-wait" | "integrations-oauth-wait";
2
+ /** Runtime modules that resident commands may need after a wait starts. */
3
3
  export declare const RESIDENT_PACKAGE_MODULES: Record<ResidentPackagePath, readonly string[]>;
4
4
  export declare class ResidentPackagePreloadError extends Error {
5
5
  readonly moduleSpecifier: string;
@@ -1,8 +1,8 @@
1
1
  import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload, } from "./package-use-lease.js";
2
- /** Package imports reachable lazily from each resident command's runtime graph. */
2
+ /** Runtime modules that resident commands may need after a wait starts. */
3
3
  export const RESIDENT_PACKAGE_MODULES = {
4
- "agent-kit": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open"],
5
- "agent-mcp": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../commands/access.js", "open"],
4
+ "agent-kit": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
5
+ "agent-mcp": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../commands/access.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "../utils/local-files-overview.js", "@indigoai-us/hq-cloud"],
6
6
  login: [
7
7
  "../lib/mesh/api.js",
8
8
  "../lib/work-context/config.js",
@@ -12,6 +12,9 @@ export const RESIDENT_PACKAGE_MODULES = {
12
12
  "../utils/plan-gate-error.js",
13
13
  "../sentry.js",
14
14
  "open",
15
+ "../utils/cognito-session.js",
16
+ "../lib/first-sync-next-step.js",
17
+ "@indigoai-us/hq-cloud",
15
18
  ],
16
19
  "auth-login": [
17
20
  "../lib/auth-login-provider-state.js",
@@ -20,7 +23,10 @@ export const RESIDENT_PACKAGE_MODULES = {
20
23
  "../utils/plan-gate-error.js",
21
24
  "../sentry.js",
22
25
  "open",
26
+ "../utils/cognito-session.js",
27
+ "@indigoai-us/hq-cloud",
23
28
  ],
29
+ "agents-terminal": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
24
30
  bot: [
25
31
  "../lib/flag-registry.js",
26
32
  "../commands/access.js",
@@ -28,7 +34,12 @@ export const RESIDENT_PACKAGE_MODULES = {
28
34
  "../lib/plan-limit-nag.js",
29
35
  "../utils/plan-gate-error.js",
30
36
  "open",
37
+ "../utils/cognito-session.js",
38
+ "../lib/first-sync-next-step.js",
39
+ "../utils/local-files-overview.js",
40
+ "@indigoai-us/hq-cloud",
31
41
  ],
42
+ "bot-logs-follow": [],
32
43
  mcp: ["../lib/mcp/tools.js", "../lib/mcp/prompts.js"],
33
44
  childWrapper: ["../main.js"],
34
45
  coreJobRun: ["../lib/flag-registry.js"],
@@ -42,11 +53,16 @@ export const RESIDENT_PACKAGE_MODULES = {
42
53
  "@modelcontextprotocol/sdk/client/streamableHttp.js",
43
54
  "../utils/staged-npm-install.js",
44
55
  "open",
56
+ "../lib/first-sync-next-step.js",
57
+ "@indigoai-us/hq-cloud",
45
58
  ],
46
- "mesh-daemon": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open"],
47
- "lanes-watch": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open"],
48
- "monitor-wait": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open"],
49
- "monitor-run": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "open"],
59
+ "daemon-logs-follow": [],
60
+ "mesh-daemon": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
61
+ "lanes-watch": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
62
+ "monitor-wait": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
63
+ "monitor-run": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
64
+ "agents-message-wait": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
65
+ "integrations-oauth-wait": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
50
66
  };
51
67
  const LOADERS = {
52
68
  "agent-kit": [
@@ -57,6 +73,9 @@ const LOADERS = {
57
73
  () => import("../lib/plan-limit-nag.js"),
58
74
  () => import("../utils/plan-gate-error.js"),
59
75
  () => import("open"),
76
+ () => import("../utils/cognito-session.js"),
77
+ () => import("../lib/first-sync-next-step.js"),
78
+ () => import("@indigoai-us/hq-cloud"),
60
79
  ],
61
80
  "agent-mcp": [
62
81
  async () => {
@@ -67,6 +86,10 @@ const LOADERS = {
67
86
  () => import("../utils/plan-gate-error.js"),
68
87
  () => import("../commands/access.js"),
69
88
  () => import("open"),
89
+ () => import("../utils/cognito-session.js"),
90
+ () => import("../lib/first-sync-next-step.js"),
91
+ () => import("../utils/local-files-overview.js"),
92
+ () => import("@indigoai-us/hq-cloud"),
70
93
  ],
71
94
  login: [
72
95
  () => import("../lib/mesh/api.js"),
@@ -80,6 +103,9 @@ const LOADERS = {
80
103
  preloadSentrySdk();
81
104
  },
82
105
  () => import("open"),
106
+ () => import("../utils/cognito-session.js"),
107
+ () => import("../lib/first-sync-next-step.js"),
108
+ () => import("@indigoai-us/hq-cloud"),
83
109
  ],
84
110
  "auth-login": [
85
111
  () => import("../lib/auth-login-provider-state.js"),
@@ -91,6 +117,20 @@ const LOADERS = {
91
117
  preloadSentrySdk();
92
118
  },
93
119
  () => import("open"),
120
+ () => import("../utils/cognito-session.js"),
121
+ () => import("@indigoai-us/hq-cloud"),
122
+ ],
123
+ "agents-terminal": [
124
+ async () => {
125
+ const { preloadSentrySdk } = await import("../sentry.js");
126
+ preloadSentrySdk();
127
+ },
128
+ () => import("../lib/plan-limit-nag.js"),
129
+ () => import("../utils/plan-gate-error.js"),
130
+ () => import("open"),
131
+ () => import("../utils/cognito-session.js"),
132
+ () => import("../lib/first-sync-next-step.js"),
133
+ () => import("@indigoai-us/hq-cloud"),
94
134
  ],
95
135
  bot: [
96
136
  () => import("../lib/flag-registry.js"),
@@ -102,7 +142,12 @@ const LOADERS = {
102
142
  () => import("../lib/plan-limit-nag.js"),
103
143
  () => import("../utils/plan-gate-error.js"),
104
144
  () => import("open"),
145
+ () => import("../utils/cognito-session.js"),
146
+ () => import("../lib/first-sync-next-step.js"),
147
+ () => import("../utils/local-files-overview.js"),
148
+ () => import("@indigoai-us/hq-cloud"),
105
149
  ],
150
+ "bot-logs-follow": [],
106
151
  mcp: [
107
152
  () => import("../lib/mcp/tools.js"),
108
153
  () => import("../lib/mcp/prompts.js"),
@@ -126,7 +171,10 @@ const LOADERS = {
126
171
  () => import("@modelcontextprotocol/sdk/client/streamableHttp.js"),
127
172
  () => import("../utils/staged-npm-install.js"),
128
173
  () => import("open"),
174
+ () => import("../lib/first-sync-next-step.js"),
175
+ () => import("@indigoai-us/hq-cloud"),
129
176
  ],
177
+ "daemon-logs-follow": [],
130
178
  "mesh-daemon": [
131
179
  async () => {
132
180
  const { preloadSentrySdk } = await import("../sentry.js");
@@ -135,6 +183,9 @@ const LOADERS = {
135
183
  () => import("../lib/plan-limit-nag.js"),
136
184
  () => import("../utils/plan-gate-error.js"),
137
185
  () => import("open"),
186
+ () => import("../utils/cognito-session.js"),
187
+ () => import("../lib/first-sync-next-step.js"),
188
+ () => import("@indigoai-us/hq-cloud"),
138
189
  ],
139
190
  "lanes-watch": [
140
191
  async () => {
@@ -144,6 +195,9 @@ const LOADERS = {
144
195
  () => import("../lib/plan-limit-nag.js"),
145
196
  () => import("../utils/plan-gate-error.js"),
146
197
  () => import("open"),
198
+ () => import("../utils/cognito-session.js"),
199
+ () => import("../lib/first-sync-next-step.js"),
200
+ () => import("@indigoai-us/hq-cloud"),
147
201
  ],
148
202
  "monitor-wait": [
149
203
  async () => {
@@ -153,6 +207,9 @@ const LOADERS = {
153
207
  () => import("../lib/plan-limit-nag.js"),
154
208
  () => import("../utils/plan-gate-error.js"),
155
209
  () => import("open"),
210
+ () => import("../utils/cognito-session.js"),
211
+ () => import("../lib/first-sync-next-step.js"),
212
+ () => import("@indigoai-us/hq-cloud"),
156
213
  ],
157
214
  "monitor-run": [
158
215
  () => import("../commands/lanes.js"),
@@ -163,6 +220,33 @@ const LOADERS = {
163
220
  preloadSentrySdk();
164
221
  },
165
222
  () => import("open"),
223
+ () => import("../utils/cognito-session.js"),
224
+ () => import("../lib/first-sync-next-step.js"),
225
+ () => import("@indigoai-us/hq-cloud"),
226
+ ],
227
+ "agents-message-wait": [
228
+ async () => {
229
+ const { preloadSentrySdk } = await import("../sentry.js");
230
+ preloadSentrySdk();
231
+ },
232
+ () => import("../lib/plan-limit-nag.js"),
233
+ () => import("../utils/plan-gate-error.js"),
234
+ () => import("open"),
235
+ () => import("../utils/cognito-session.js"),
236
+ () => import("../lib/first-sync-next-step.js"),
237
+ () => import("@indigoai-us/hq-cloud"),
238
+ ],
239
+ "integrations-oauth-wait": [
240
+ async () => {
241
+ const { preloadSentrySdk } = await import("../sentry.js");
242
+ preloadSentrySdk();
243
+ },
244
+ () => import("../lib/plan-limit-nag.js"),
245
+ () => import("../utils/plan-gate-error.js"),
246
+ () => import("open"),
247
+ () => import("../utils/cognito-session.js"),
248
+ () => import("../lib/first-sync-next-step.js"),
249
+ () => import("@indigoai-us/hq-cloud"),
166
250
  ],
167
251
  };
168
252
  export class ResidentPackagePreloadError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.342.15",
3
+ "version": "5.342.16",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {