@indigoai-us/hq-cli 5.319.0 → 5.321.0

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/assets/scaffold/core/scripts/repo-run-registry.sh +3 -1
  3. package/assets/scaffold/core/scripts/session-journal.sh +40 -2
  4. package/dist/bin/hq-auth-refresh.d.ts +3 -1
  5. package/dist/bin/hq-auth-refresh.js +41 -6
  6. package/dist/command-catalog.generated.d.ts +24 -0
  7. package/dist/command-catalog.generated.js +30 -0
  8. package/dist/commands/core-hook-batch-command.d.ts +3 -0
  9. package/dist/commands/core-hook-batch-command.js +27 -0
  10. package/dist/commands/core-hook-batch-flag.d.ts +4 -0
  11. package/dist/commands/core-hook-batch-flag.js +7 -0
  12. package/dist/commands/core-hook-batch-native.d.ts +10 -0
  13. package/dist/commands/core-hook-batch-native.js +85 -0
  14. package/dist/commands/core-repo-run-registry-native.js +6 -1
  15. package/dist/commands/core-session-journal-native.d.ts +8 -1
  16. package/dist/commands/core-session-journal-native.js +77 -18
  17. package/dist/commands/core.js +2 -0
  18. package/dist/commands/lanes.js +28 -5
  19. package/dist/fast-core.js +13 -0
  20. package/dist/fast-scaffold.d.ts +2 -0
  21. package/dist/fast-scaffold.js +10 -0
  22. package/dist/index.d.ts +9 -0
  23. package/dist/index.js +135 -63
  24. package/dist/lib/daemon/run.d.ts +2 -0
  25. package/dist/lib/daemon/run.js +10 -1
  26. package/dist/lib/lanes/flag-keys.d.ts +3 -0
  27. package/dist/lib/lanes/flag-keys.js +3 -0
  28. package/dist/lib/work-context/bind-best.d.ts +1 -1
  29. package/dist/lib/work-context/bind-best.js +3 -3
  30. package/dist/utils/package-use-lease.d.ts +25 -0
  31. package/dist/utils/package-use-lease.js +213 -0
  32. package/package.json +1 -1
@@ -35,7 +35,7 @@ import { withCliFeatureUsed } from "../utils/cli-telemetry.js";
35
35
  import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
36
36
  import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
37
37
  import { readLanesFlagLocalOverride, readLanesRegistryFlag, readLanesRegistryFlagFailOff } from "../lib/lanes/registry-flag.js";
38
- import { LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG, LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_FLAG } from "../lib/lanes/flag-keys.js";
38
+ import { LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_LIST_SKIP_REMOTE_REFRESH_FLAG, LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG, LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_FLAG, } from "../lib/lanes/flag-keys.js";
39
39
  import { CODEX_ROLLOUT_RETENTION_FLAG, configuredCodexRolloutHomes, lowDiskGuardEnabledForLane, retainCodexRollouts } from "../lib/lanes/disk-guard.js";
40
40
  import { isControllerAliasOutpostId } from "../lib/lanes/admission-host.js";
41
41
  import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
@@ -1337,6 +1337,7 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
1337
1337
  updated: new Map(),
1338
1338
  unverifiedRemote: new Map(),
1339
1339
  missingOnOwner: new Map(),
1340
+ skippedRemoteRefresh: new Set(),
1340
1341
  };
1341
1342
  const now = ctx.now();
1342
1343
  const nowMs = now.getTime();
@@ -1378,6 +1379,7 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
1378
1379
  if (candidates.length === 0)
1379
1380
  return result;
1380
1381
  const enabledByCompany = new Map();
1382
+ const skipRefreshByCompany = new Map();
1381
1383
  const enabledCandidates = [];
1382
1384
  for (const lane of candidates) {
1383
1385
  const company = resolveLaneCompany(hqRoot, lane).company;
@@ -1392,8 +1394,20 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
1392
1394
  });
1393
1395
  enabledByCompany.set(companyKey, enabled);
1394
1396
  }
1395
- if (enabled)
1396
- enabledCandidates.push(lane);
1397
+ if (!enabled)
1398
+ continue;
1399
+ if (options.skipRemoteRefreshForCompany) {
1400
+ let skipRefresh = skipRefreshByCompany.get(companyKey);
1401
+ if (skipRefresh === undefined) {
1402
+ skipRefresh = await options.skipRemoteRefreshForCompany(company);
1403
+ skipRefreshByCompany.set(companyKey, skipRefresh);
1404
+ }
1405
+ if (skipRefresh) {
1406
+ result.skippedRemoteRefresh.add(lane.lane_id);
1407
+ continue;
1408
+ }
1409
+ }
1410
+ enabledCandidates.push(lane);
1397
1411
  }
1398
1412
  if (enabledCandidates.length === 0)
1399
1413
  return result;
@@ -4950,7 +4964,12 @@ async function runList(ctx, opts) {
4950
4964
  }
4951
4965
  const remoteRead = ctx.env[REMOTE_LANE_READ_ENV] === "1";
4952
4966
  const remoteRefresh = remoteRead
4953
- ? { updated: new Map(), unverifiedRemote: new Map(), missingOnOwner: new Map() }
4967
+ ? {
4968
+ updated: new Map(),
4969
+ unverifiedRemote: new Map(),
4970
+ missingOnOwner: new Map(),
4971
+ skippedRemoteRefresh: new Set(),
4972
+ }
4954
4973
  : await refreshRemoteLaneMirrors(ctx, hqRoot, laneSource, new Set(laneSource
4955
4974
  .filter((lane) => matchesLaneFilters(lane, false))
4956
4975
  .map((lane) => lane.lane_id)), {
@@ -4958,6 +4977,7 @@ async function runList(ctx, opts) {
4958
4977
  laneIds: new Set(laneSource
4959
4978
  .filter((lane) => matchesLaneFilters(lane, false))
4960
4979
  .map((lane) => lane.lane_id)),
4980
+ skipRemoteRefreshForCompany: (company) => readLanesRegistryFlagFailOff(LANE_LIST_SKIP_REMOTE_REFRESH_FLAG, { company, env: ctx.env }),
4961
4981
  });
4962
4982
  for (const [laneId, updated] of remoteRefresh.updated) {
4963
4983
  const index = laneSource.findIndex((lane) => lane.lane_id === laneId);
@@ -4995,6 +5015,7 @@ async function runList(ctx, opts) {
4995
5015
  const ownerEligibleLaneIds = new Set();
4996
5016
  for (const lane of selectedLanes) {
4997
5017
  if (remoteRead ||
5018
+ remoteRefresh.skippedRemoteRefresh.has(lane.lane_id) ||
4998
5019
  lane.host.kind !== "outpost" ||
4999
5020
  isTerminalLaneState(lane.state) ||
5000
5021
  laneRunsHere(lane, host)) {
@@ -5034,6 +5055,7 @@ async function runList(ctx, opts) {
5034
5055
  const livenessBySession = new Map();
5035
5056
  let livenessDeps;
5036
5057
  const lanes = selectedLanes.map((lane) => {
5058
+ const overdue = Boolean(lane.overdue) || isOverdue(hqRoot, lane.lane_id, now);
5037
5059
  const remoteQueued = lane.state === "queued" &&
5038
5060
  lane.host.kind === "outpost" &&
5039
5061
  !laneRunsHere(lane, host);
@@ -5052,7 +5074,7 @@ async function runList(ctx, opts) {
5052
5074
  queued_details: lane.queued_reason === "low_disk" ? lane.queued_details : undefined,
5053
5075
  }
5054
5076
  : {}),
5055
- overdue: Boolean(lane.overdue) || isOverdue(hqRoot, lane.lane_id, now),
5077
+ overdue,
5056
5078
  stalled: Boolean(lane.stalled),
5057
5079
  // Listing is observational. Reconcile owns durable liveness transitions.
5058
5080
  senior_lost: lane.state === "running" && lane.senior.kind === "session" && lane.senior.id
@@ -7873,6 +7895,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
7873
7895
  updated: new Map(),
7874
7896
  unverifiedRemote: new Map(),
7875
7897
  missingOnOwner: new Map(),
7898
+ skippedRemoteRefresh: new Set(),
7876
7899
  }
7877
7900
  : await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot));
7878
7901
  const remoteLost = [];
package/dist/fast-core.js CHANGED
@@ -35,6 +35,11 @@ export async function runFastCore(argv) {
35
35
  const envOverride = localBooleanFlagOverride(entry.familyFlagKey, process.env);
36
36
  familyEnabled = envOverride ?? await (await import("./native-fast-route.js")).fastFlagEnabled(entry.familyFlagKey, process.env, { numericEnvironmentOverride: false });
37
37
  }
38
+ if (entry.kind === "native" && entry.fallbackToCommandGraph && !familyEnabled) {
39
+ const { runCli } = await import("./main.js");
40
+ await runCli();
41
+ return;
42
+ }
38
43
  const useShell = entry.kind === "native" && Boolean(entry.familyFlagKey) && !familyEnabled;
39
44
  const worktreeScopeFixEnabled = entry.name === "repo-run-registry"
40
45
  ? await (await import("./native-fast-route.js")).fastFlagEnabled(CORE_RRR_WORKTREE_SCOPE_FIX_FLAG, process.env, { numericEnvironmentOverride: false })
@@ -74,6 +79,14 @@ export async function runFastCore(argv) {
74
79
  const readsStdin = entry.readsStdinFor?.(parsed.args) ?? entry.readsStdin ?? false;
75
80
  const stdin = readsStdin ? await readStdinIfPiped(process.stdin) : "";
76
81
  const code = await handler(parsed.args, stdin, process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg, worktreeScopeFixEnabled);
82
+ if (code === undefined) {
83
+ if (!entry.fallbackToCommandGraph) {
84
+ throw new Error(`Native fast handler ${entry.exportName} requested an unavailable command-graph fallback`);
85
+ }
86
+ const { runCli } = await import("./main.js");
87
+ await runCli();
88
+ return;
89
+ }
77
90
  // Let Node drain handler output before terminating. The timeout guard's
78
91
  // stderr message must survive the native route's process boundary.
79
92
  process.exitCode = code;
@@ -32,6 +32,8 @@ export type FastScaffoldScript = {
32
32
  familyFlagKey?: string;
33
33
  /** Shell rollback companion used while the family gate is off. */
34
34
  asset?: string;
35
+ /** Return to the Commander graph when this new route has no shell companion. */
36
+ fallbackToCommandGraph?: boolean;
35
37
  /** Root context used by the native handler. */
36
38
  root?: "live" | "cwd" | "passthrough";
37
39
  /** Cwd used for a shell rollback companion. Defaults to the caller cwd. */
@@ -217,6 +217,16 @@ export const FAST_SCAFFOLD_SCRIPTS = [
217
217
  shellHqRoot: "live",
218
218
  ...(name === "session-project" ? { readsStdin: true, readsStdinFor: (args) => args[0] === "ingest-plan" } : {}),
219
219
  })),
220
+ {
221
+ name: "hook-batch journal-due",
222
+ kind: "native",
223
+ modulePath: "./commands/core-hook-batch-native.js",
224
+ exportName: "runCoreHookBatchFast",
225
+ flagKey: "hq-core-native-fast-route",
226
+ familyFlagKey: "core-hook-batch",
227
+ root: "live",
228
+ fallbackToCommandGraph: true,
229
+ },
220
230
  ...[
221
231
  { name: "eval-trigger", modulePath: "./commands/core-eval-trigger-native.js", exportName: "runEvalTriggerFast", asset: "core/scripts/eval-trigger.sh", root: "cwd", shellCwd: "caller", shellHqRoot: "none", readsStdinFor: (args) => args[0] === "--check" },
222
232
  { name: "migrate-policy-triggers", modulePath: "./commands/core-migrate-policy-triggers-native.js", exportName: "runMigratePolicyTriggersFast", asset: "core/scripts/migrate-policy-triggers.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
package/dist/index.d.ts CHANGED
@@ -3,8 +3,17 @@ import "./node-preflight.js";
3
3
  import "./node-network-compat-fast.js";
4
4
  import { isVersionRequest } from "./version-request.js";
5
5
  import { isFastCoreRequest } from "./fast-scaffold.js";
6
+ type PackageUseLeaseLoader = () => Promise<void>;
7
+ declare function isNpmPackageEntry(entryUrl: string): boolean;
8
+ declare function dispatchWithPackageUseLease(entryUrl: string, dispatch: () => void, loadLease?: PackageUseLeaseLoader): void | Promise<void>;
9
+ declare function runCliEntry(entryUrl: string, dispatch: () => void, loadLease?: PackageUseLeaseLoader): void | Promise<void>;
6
10
  export declare const __test__: {
7
11
  isVersionRequest: typeof isVersionRequest;
8
12
  isFastCoreRequest: typeof isFastCoreRequest;
13
+ dispatchWithPackageUseLease: typeof dispatchWithPackageUseLease;
14
+ runCliEntry: typeof runCliEntry;
15
+ isNpmPackageEntry: typeof isNpmPackageEntry;
16
+ acquirePackageUseLease: (prefix: string, version: string, stateDirectory?: string) => Promise<import("./utils/package-use-lease.js").PackageUseLease>;
9
17
  };
18
+ export {};
10
19
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -17,77 +17,149 @@ import { installProcessRejectionBoundary } from "./unhandled-rejection-boundary.
17
17
  // command never run (HQ-CLI-W). Sets process.exitCode only, never process.exit,
18
18
  // so runCli's finally still finalizes release health and flushes Sentry.
19
19
  installProcessRejectionBoundary();
20
- if (isVersionRequest(process.argv)) {
21
- // Bare `--version` is the CLI semver only (the contract field updaters
22
- // compare). `hq version` and `--json` print the labelled HQ/CLI report.
23
- // Lazy so this path still skips the command graph.
24
- if (wantsLabelledVersionReport(process.argv)) {
25
- void import("./lib/flag-registry.js").then(({ kickFlagRegistryReadiness }) => {
26
- kickFlagRegistryReadiness();
27
- return import("./utils/version-report.js").then(({ runVersionCommand }) => runVersionCommand(process.argv));
20
+ function dispatchCliEntry() {
21
+ if (isVersionRequest(process.argv)) {
22
+ // Bare `--version` is the CLI semver only (the contract field updaters
23
+ // compare). `hq version` and `--json` print the labelled HQ/CLI report.
24
+ // Lazy so this path still skips the command graph.
25
+ if (wantsLabelledVersionReport(process.argv)) {
26
+ void import("./lib/flag-registry.js").then(({ kickFlagRegistryReadiness }) => {
27
+ kickFlagRegistryReadiness();
28
+ return import("./utils/version-report.js").then(({ runVersionCommand }) => runVersionCommand(process.argv));
29
+ });
30
+ }
31
+ else {
32
+ // Keep the benchmarked updater fast route limited to the installed semver.
33
+ // Registry checks and flag resolution belong to the regular command path.
34
+ void import("./cli-version.js").then(({ CLI_VERSION }) => {
35
+ process.stdout.write(`${CLI_VERSION}\n`);
36
+ });
37
+ }
38
+ }
39
+ else if (parseFastCore(process.argv)?.entry.kind === "native" &&
40
+ (process.env.HQ_FLAG_HQ_CORE_NATIVE_FAST_ROUTE === "true" ||
41
+ process.env.HQ_FLAG_HQ_CORE_NATIVE_FAST_ROUTE === "1")) {
42
+ // An explicit enabled override has already decided the global gate. Skip the
43
+ // snapshot reader import on hot native plumbing paths; runFastCore still
44
+ // checks the command-family flag before choosing native or shell rollback.
45
+ void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv));
46
+ }
47
+ else if (parseFastCore(process.argv)?.entry.kind === "native") {
48
+ // Native routes are default-off. Resolve the company-scoped persisted flag
49
+ // without loading the command graph; a miss or stale snapshot takes the
50
+ // existing full-graph route.
51
+ void import("./native-fast-route.js").then(async ({ nativeFastRouteEnabled }) => {
52
+ if (await nativeFastRouteEnabled(process.argv)) {
53
+ const { runFastCore } = await import("./fast-core.js");
54
+ await runFastCore(process.argv);
55
+ }
56
+ else {
57
+ const { runCli } = await import("./main.js");
58
+ await runCli();
59
+ }
28
60
  });
29
61
  }
30
- else {
31
- // Keep the benchmarked updater fast route limited to the installed semver.
32
- // Registry checks and flag resolution belong to the regular command path.
33
- void import("./cli-version.js").then(({ CLI_VERSION }) => {
34
- process.stdout.write(`${CLI_VERSION}\n`);
62
+ else if (parseSecretsEnvFastRequest(process.argv)) {
63
+ const request = parseSecretsEnvFastRequest(process.argv);
64
+ void import("./secrets-env-fast-route.js").then(async (route) => {
65
+ await route.dispatchSecretsEnvFastRequest(request, {
66
+ isEnabled: () => route.secretsEnvFastRouteEnabled(request),
67
+ runFast: async () => {
68
+ const { runSecretsEnvFastPath } = await import("./secrets-env-fast-path.js");
69
+ return runSecretsEnvFastPath(request);
70
+ },
71
+ runCurrent: async () => {
72
+ const { runCli } = await import("./main.js");
73
+ await runCli();
74
+ },
75
+ });
35
76
  });
36
77
  }
78
+ else if (isFastCoreRequest(process.argv)) {
79
+ // Hot relocated plumbing (`hq core hq-session`, `hq core checkpoint-stop-gate`)
80
+ // dispatches straight to the bundled-script runner, so it never pays for the
81
+ // ~60-module command graph main.ts imports. See src/fast-scaffold.ts.
82
+ void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv));
83
+ }
84
+ else {
85
+ // Keep the command lifecycle detached from module evaluation, as it was
86
+ // before the fast --version split. Some best-effort teardown work uses
87
+ // unref'd handles; top-level-awaiting runCli() makes Node turn an otherwise
88
+ // successful command into exit 13 when that teardown promise remains pending.
89
+ // A floated rejection here no longer exits 0 silently: the boundary installed
90
+ // above turns it into a non-zero exit (HQ-CLI-W).
91
+ void import("./main.js").then(({ runCli }) => runCli());
92
+ }
37
93
  }
38
- else if (parseFastCore(process.argv)?.entry.kind === "native" &&
39
- (process.env.HQ_FLAG_HQ_CORE_NATIVE_FAST_ROUTE === "true" ||
40
- process.env.HQ_FLAG_HQ_CORE_NATIVE_FAST_ROUTE === "1")) {
41
- // An explicit enabled override has already decided the global gate. Skip the
42
- // snapshot reader import on hot native plumbing paths; runFastCore still
43
- // checks the command-family flag before choosing native or shell rollback.
44
- void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv));
94
+ function isNpmPackageEntry(entryUrl) {
95
+ return /\/node_modules\/@indigoai-us\/hq-cli\/dist\/index\.js$/i.test(entryUrl);
45
96
  }
46
- else if (parseFastCore(process.argv)?.entry.kind === "native") {
47
- // Native routes are default-off. Resolve the company-scoped persisted flag
48
- // without loading the command graph; a miss or stale snapshot takes the
49
- // existing full-graph route.
50
- void import("./native-fast-route.js").then(async ({ nativeFastRouteEnabled }) => {
51
- if (await nativeFastRouteEnabled(process.argv)) {
52
- const { runFastCore } = await import("./fast-core.js");
53
- await runFastCore(process.argv);
54
- }
55
- else {
56
- const { runCli } = await import("./main.js");
57
- await runCli();
58
- }
59
- });
97
+ function isPackageUseUpdatePendingError(error) {
98
+ return Boolean(error &&
99
+ typeof error === "object" &&
100
+ error.name === "PackageUseUpdatePendingError");
60
101
  }
61
- else if (parseSecretsEnvFastRequest(process.argv)) {
62
- const request = parseSecretsEnvFastRequest(process.argv);
63
- void import("./secrets-env-fast-route.js").then(async (route) => {
64
- await route.dispatchSecretsEnvFastRequest(request, {
65
- isEnabled: () => route.secretsEnvFastRouteEnabled(request),
66
- runFast: async () => {
67
- const { runSecretsEnvFastPath } = await import("./secrets-env-fast-path.js");
68
- return runSecretsEnvFastPath(request);
69
- },
70
- runCurrent: async () => {
71
- const { runCli } = await import("./main.js");
72
- await runCli();
73
- },
74
- });
75
- });
102
+ function packageUseErrorClass(error) {
103
+ if (!error || typeof error !== "object")
104
+ return "unknown";
105
+ const value = error;
106
+ if (typeof value.code === "string")
107
+ return value.code;
108
+ if (value.cause && typeof value.cause === "object") {
109
+ const cause = value.cause;
110
+ if (typeof cause.code === "string")
111
+ return cause.code;
112
+ if (typeof cause.name === "string")
113
+ return cause.name;
114
+ }
115
+ return typeof value.name === "string" ? value.name : "unknown";
76
116
  }
77
- else if (isFastCoreRequest(process.argv)) {
78
- // Hot relocated plumbing (`hq core hq-session`, `hq core checkpoint-stop-gate`)
79
- // dispatches straight to the bundled-script runner, so it never pays for the
80
- // ~60-module command graph main.ts imports. See src/fast-scaffold.ts.
81
- void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv));
117
+ function dispatchWithPackageUseLease(entryUrl, dispatch, loadLease = async () => {
118
+ const { acquireCurrentPackageUseLease } = await import("./utils/package-use-lease.js");
119
+ acquireCurrentPackageUseLease();
120
+ }) {
121
+ if (!isNpmPackageEntry(entryUrl)) {
122
+ dispatch();
123
+ return;
124
+ }
125
+ return loadLease().then(dispatch, (error) => {
126
+ if (isPackageUseUpdatePendingError(error))
127
+ throw error;
128
+ process.stderr.write(`hq-cli package-use lease unavailable (${packageUseErrorClass(error)}); continuing without lease\n`);
129
+ dispatch();
130
+ });
82
131
  }
83
- else {
84
- // Keep the command lifecycle detached from module evaluation, as it was
85
- // before the fast --version split. Some best-effort teardown work uses
86
- // unref'd handles; top-level-awaiting runCli() makes Node turn an otherwise
87
- // successful command into exit 13 when that teardown promise remains pending.
88
- // A floated rejection here no longer exits 0 silently: the boundary installed
89
- // above turns it into a non-zero exit (HQ-CLI-W).
90
- void import("./main.js").then(({ runCli }) => runCli());
132
+ function runCliEntry(entryUrl, dispatch, loadLease) {
133
+ const handlePendingUpdate = (error) => {
134
+ if (!isPackageUseUpdatePendingError(error))
135
+ throw error;
136
+ const message = error instanceof Error
137
+ ? error.message
138
+ : "The HQ CLI package is being updated";
139
+ process.stderr.write(`hq-cli: ${message}\n`);
140
+ process.exitCode = 1;
141
+ };
142
+ try {
143
+ const result = dispatchWithPackageUseLease(entryUrl, dispatch, loadLease);
144
+ return result?.catch(handlePendingUpdate);
145
+ }
146
+ catch (error) {
147
+ handlePendingUpdate(error);
148
+ }
91
149
  }
92
- export const __test__ = { isVersionRequest, isFastCoreRequest };
150
+ // Local builds and benchmark fixtures are not installed in an npm prefix.
151
+ // Keep their fast route free of package-use filesystem work; npm installs still
152
+ // publish a lease before dispatch.
153
+ void runCliEntry(import.meta.url, dispatchCliEntry);
154
+ export const __test__ = {
155
+ isVersionRequest,
156
+ isFastCoreRequest,
157
+ dispatchWithPackageUseLease,
158
+ runCliEntry,
159
+ isNpmPackageEntry,
160
+ acquirePackageUseLease: async (prefix, version, stateDirectory) => {
161
+ const lease = await import("./utils/package-use-lease.js");
162
+ return lease.acquirePackageUseLease(prefix, version, stateDirectory);
163
+ },
164
+ };
93
165
  //# sourceMappingURL=index.js.map
@@ -12,6 +12,8 @@ import { type HostApp } from "./supervisor.js";
12
12
  export declare const LOG_ROTATE_BYTES: number;
13
13
  /** Exit code for "restart me": non-zero so every service manager relaunches (Windows tasks only restart on failure). */
14
14
  export declare const RESTART_EXIT_CODE = 75;
15
+ /** Route an updater request through the daemon's existing graceful handoff. */
16
+ export declare function handoffForPackageUpdateRequest(requested: boolean, restartDaemon: () => void): void;
15
17
  export interface RunDaemonOptions {
16
18
  foreground?: boolean;
17
19
  /** Started by the OS unit, which relaunches the daemon whenever it exits. */
@@ -12,6 +12,7 @@ import * as os from "node:os";
12
12
  import * as path from "node:path";
13
13
  import { loadCachedTokens } from "@indigoai-us/hq-cloud";
14
14
  import { CLI_VERSION } from "../../cli-version.js";
15
+ import { currentPackageUseUpdateRequested } from "../../utils/package-use-lease.js";
15
16
  import { isLocalDependencyInstall, resolveRunningInstall } from "../../utils/version-gate.js";
16
17
  import { agentKitPaths } from "../agent-kit/paths.js";
17
18
  import { readBotConfig } from "../bot/config.js";
@@ -39,6 +40,11 @@ import { userToolDirs } from "./tools.js";
39
40
  export const LOG_ROTATE_BYTES = 5 * 1024 * 1024;
40
41
  /** Exit code for "restart me": non-zero so every service manager relaunches (Windows tasks only restart on failure). */
41
42
  export const RESTART_EXIT_CODE = 75;
43
+ /** Route an updater request through the daemon's existing graceful handoff. */
44
+ export function handoffForPackageUpdateRequest(requested, restartDaemon) {
45
+ if (requested)
46
+ restartDaemon();
47
+ }
42
48
  const LEGACY_PROBE_TTL_MS = 60_000;
43
49
  /** A flag read that has not answered by then counts as off. */
44
50
  const HANDOFF_FLAG_TIMEOUT_MS = 10_000;
@@ -603,7 +609,10 @@ export async function runDaemon(opts) {
603
609
  log("info", `rotated ${n} log file(s)`);
604
610
  return true;
605
611
  },
606
- takeRequests: () => takeControlRequests(paths.requestsDir),
612
+ takeRequests: () => {
613
+ handoffForPackageUpdateRequest(currentPackageUseUpdateRequested(), restartDaemon);
614
+ return takeControlRequests(paths.requestsDir);
615
+ },
607
616
  restartDaemon: () => restartDaemon(),
608
617
  writeState: (state) => writeJsonAtomic(paths.statePath, state),
609
618
  log,
@@ -4,6 +4,9 @@ export declare const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-bala
4
4
  export declare const LANE_ENVELOPE_REPAIR_CONTINUE_FLAG = "lanes.envelope-repair-continue";
5
5
  /** Count only children with live work against their senior's hire grant. */
6
6
  export declare const LANE_HIRE_GRANT_FINISHED_LANES_FLAG = "lanes.hire-grant-count-live-only";
7
+ /** Skips owner refreshes during ordinary lane list polling when explicitly enabled. */
8
+ export declare const LANE_LIST_SKIP_REMOTE_REFRESH_FLAG = "lanes.list-skip-remote-refresh";
9
+ export declare const LANE_LIST_SKIP_REMOTE_REFRESH_DEFAULT_ENABLED = false;
7
10
  /** Enables missed-deadline lead nudges and second-miss operator escalation. */
8
11
  export declare const LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG = "lanes.autopilot-missed-deadline-escalation";
9
12
  export declare const LANE_AUTOPILOT_NUDGE_DEDUPE_FLAG = "lanes.autopilot-nudge-dedupe";
@@ -4,6 +4,9 @@ export const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-balancing";
4
4
  export const LANE_ENVELOPE_REPAIR_CONTINUE_FLAG = "lanes.envelope-repair-continue";
5
5
  /** Count only children with live work against their senior's hire grant. */
6
6
  export const LANE_HIRE_GRANT_FINISHED_LANES_FLAG = "lanes.hire-grant-count-live-only";
7
+ /** Skips owner refreshes during ordinary lane list polling when explicitly enabled. */
8
+ export const LANE_LIST_SKIP_REMOTE_REFRESH_FLAG = "lanes.list-skip-remote-refresh";
9
+ export const LANE_LIST_SKIP_REMOTE_REFRESH_DEFAULT_ENABLED = false;
7
10
  /** Enables missed-deadline lead nudges and second-miss operator escalation. */
8
11
  export const LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG = "lanes.autopilot-missed-deadline-escalation";
9
12
  export const LANE_AUTOPILOT_NUDGE_DEDUPE_FLAG = "lanes.autopilot-nudge-dedupe";
@@ -41,7 +41,7 @@ export declare function discoverProjects(input: {
41
41
  env?: NodeJS.ProcessEnv;
42
42
  home?: string;
43
43
  }): CatalogProject[];
44
- /** discoverProjects memoized by directory mtime. A cache hit does not rescan. */
44
+ /** discoverProjects memoized by source directory and mtime. A hit does not rescan. */
45
45
  export declare function discoverProjectsCached(input: Parameters<typeof discoverProjects>[0], nowMs?: number): CatalogProject[];
46
46
  export declare function refineProjectBinding(input: {
47
47
  hint?: string;
@@ -215,14 +215,14 @@ function catalogStamp(input) {
215
215
  if (!dir)
216
216
  return "0";
217
217
  try {
218
- return String(fs.statSync(dir).mtimeMs);
218
+ return `${dir}:${fs.statSync(dir).mtimeMs}`;
219
219
  }
220
220
  catch {
221
- return "0";
221
+ return `${dir}:0`;
222
222
  }
223
223
  }).join("|");
224
224
  }
225
- /** discoverProjects memoized by directory mtime. A cache hit does not rescan. */
225
+ /** discoverProjects memoized by source directory and mtime. A hit does not rescan. */
226
226
  export function discoverProjectsCached(input, nowMs = Date.now()) {
227
227
  const key = `${input.hqRoot ?? ""}|${input.companySlug ?? ""}|${input.companyUid ?? ""}|${input.home ?? ""}|${catalogStamp(input)}`;
228
228
  if (catalogCache && catalogCache.key === key && nowMs - catalogCache.at < CATALOG_CACHE_TTL_MS) {
@@ -0,0 +1,25 @@
1
+ export declare class PackageUseUpdatePendingError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export interface PackageUseLease {
5
+ readonly leasePath: string;
6
+ release(): void;
7
+ }
8
+ export interface PackageUseLeaseRecord {
9
+ pid: number;
10
+ start_time_ms: number;
11
+ hq_version: string;
12
+ }
13
+ export declare function npmPrefix(root: string, platform?: NodeJS.Platform): string | null;
14
+ export declare function packageUseLeaseDirectory(prefix: string, stateDirectory?: string): string;
15
+ export declare function packageUseUpdateRequestPath(prefix: string, stateDirectory?: string): string;
16
+ declare function waitForUpdateRequest(requestPath: string, timeoutMs: number): void;
17
+ export declare function acquirePackageUseLease(prefix: string, hqVersion: string, stateDirectory?: string): PackageUseLease;
18
+ export declare function acquireCurrentPackageUseLease(): PackageUseLease | null;
19
+ /** True while the desktop updater owns the per-prefix update request. */
20
+ export declare function currentPackageUseUpdateRequested(): boolean;
21
+ export declare const __test__: {
22
+ waitForUpdateRequest: typeof waitForUpdateRequest;
23
+ };
24
+ export {};
25
+ //# sourceMappingURL=package-use-lease.d.ts.map