@indigoai-us/hq-cli 5.311.1 → 5.311.2

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 (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/commands/lanes-message.js +18 -7
  3. package/dist/commands/lanes-shared.d.ts +2 -0
  4. package/dist/commands/lanes-watch.js +1 -5
  5. package/dist/commands/lanes.js +4 -10
  6. package/dist/fast-scaffold.d.ts +4 -0
  7. package/dist/fast-scaffold.js +5 -0
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/lib/flag-registry.d.ts +2 -0
  11. package/dist/lib/flag-registry.js +19 -12
  12. package/dist/lib/lanes/autopilot.js +19 -2
  13. package/dist/lib/lanes/delivery.d.ts +1 -0
  14. package/dist/lib/lanes/keep-alive.d.ts +1 -3
  15. package/dist/lib/lanes/keep-alive.js +2 -4
  16. package/dist/lib/lanes/reconcile.js +14 -36
  17. package/dist/lib/lanes/spawn.d.ts +2 -0
  18. package/dist/lib/lanes/spawn.js +35 -12
  19. package/dist/lib/lanes/types.d.ts +2 -2
  20. package/dist/lib/lanes/watch.d.ts +0 -1
  21. package/dist/lib/lanes/watch.js +4 -9
  22. package/dist/main.js +1 -1
  23. package/dist/node-network-compat-core.d.ts +12 -0
  24. package/dist/node-network-compat-core.js +41 -0
  25. package/dist/node-network-compat-fast.d.ts +7 -0
  26. package/dist/node-network-compat-fast.js +23 -0
  27. package/dist/node-network-compat.d.ts +2 -18
  28. package/dist/node-network-compat.js +4 -39
  29. package/dist/node-preflight.d.ts +7 -1
  30. package/dist/node-preflight.js +5 -5
  31. package/dist/utils/hq-core-yaml.d.ts +10 -0
  32. package/dist/utils/hq-core-yaml.js +40 -0
  33. package/dist/utils/pack-contributions.d.ts +2 -9
  34. package/dist/utils/pack-contributions.js +1 -39
  35. package/dist/utils/scaffold-fast-import-guard.js +5 -1
  36. package/dist/utils/version-report.js +1 -1
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.311.2] — 2026-10-01
6
+ - Reduce fast-path startup work by loading network compatibility only on routes that need it.
7
+ - Make envelope `next_check_at` an unconditional keep-alive hold and remove its completed rollout flag.
8
+ - Lead autopilot no longer asks a lead to review its own lane as a finished worker, or to relaunch a worker for a story assigned to the lead itself.
9
+ - Lead autopilot counts a story's current `lane` together with its `lanes` history, so `lanes: []` no longer hides the assigned lane.
10
+ - Keep the CLI company seed fixture byte-matched to the hq-core starter golden, including the session sentinel ignore rule.
11
+ - Record lane-watcher continuations as `watcher` resumes and label their prompt source accurately; record monitor-event and system-notice message sources distinctly in lane resume history.
12
+
5
13
  ## [5.311.1] — 2026-10-01
6
14
  - Preserve shell-compatible diagnostics when delegate pickup receives a malformed manifest.
7
15
  - Print URLs returned by `hq integrations call` without redacting their paths or query values. Other integration JSON output keeps its existing URL redaction.
@@ -448,6 +448,11 @@ export function createLaneMessageHandlers(deps) {
448
448
  text: promptedText,
449
449
  });
450
450
  const deliveryText = framedMonitorEvent ?? promptedText;
451
+ const messageHistoryTrigger = opts.monitorEvent !== undefined
452
+ ? "monitor_event"
453
+ : opts.messageSource ?? "message";
454
+ const isMonitorEvent = messageHistoryTrigger === "monitor_event";
455
+ const isSystemNotice = messageHistoryTrigger === "system_notice";
451
456
  const dropboxMessage = {
452
457
  at,
453
458
  message_id: messageId,
@@ -562,9 +567,9 @@ export function createLaneMessageHandlers(deps) {
562
567
  ...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
563
568
  ...(hireGrantContext !== undefined ? { grantContext: hireGrantContext } : {}),
564
569
  pending: {
565
- trigger: "message",
570
+ trigger: messageHistoryTrigger,
566
571
  text: deliveryText,
567
- ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
572
+ ...(isMonitorEvent ? { monitor_event: true } : {}),
568
573
  message_id: messageId,
569
574
  compact: opts.compact !== false,
570
575
  },
@@ -589,9 +594,9 @@ export function createLaneMessageHandlers(deps) {
589
594
  ...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
590
595
  ...(hireGrantContext !== undefined ? { grantContext: hireGrantContext } : {}),
591
596
  pending: {
592
- trigger: "message",
597
+ trigger: messageHistoryTrigger,
593
598
  text: deliveryText,
594
- ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
599
+ ...(isMonitorEvent ? { monitor_event: true } : {}),
595
600
  message_id: messageId,
596
601
  compact: opts.compact !== false,
597
602
  },
@@ -674,10 +679,10 @@ export function createLaneMessageHandlers(deps) {
674
679
  queued_at: at,
675
680
  pending_resume: {
676
681
  session_id: sessionId,
677
- trigger: "message",
682
+ trigger: messageHistoryTrigger,
678
683
  caller: ctx.env.HQ_LANE_ID?.trim() || "user",
679
684
  text: deliveryText,
680
- ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
685
+ ...(isMonitorEvent ? { monitor_event: true } : {}),
681
686
  message_id: messageId,
682
687
  compact: opts.compact !== false,
683
688
  at,
@@ -708,7 +713,8 @@ export function createLaneMessageHandlers(deps) {
708
713
  kind: "message",
709
714
  text: deliveryText,
710
715
  message_id: messageId,
711
- ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
716
+ ...(isMonitorEvent ? { monitor_event: true } : {}),
717
+ ...(isSystemNotice ? { system_notice: true } : {}),
712
718
  },
713
719
  compact: opts.compact !== false,
714
720
  effort: resumeEffort ?? recordedResumeEffort(lane),
@@ -868,6 +874,11 @@ export function createLaneMessageHandlers(deps) {
868
874
  try {
869
875
  await runMessage(ctx, laneId, {
870
876
  text: formatDropboxMessages(messages),
877
+ messageSource: messages.some((message) => message.kind === "monitor_event")
878
+ ? "monitor_event"
879
+ : messages.some((message) => message.kind === "system_notice")
880
+ ? "system_notice"
881
+ : undefined,
871
882
  compact: false,
872
883
  silent: true,
873
884
  onAccepted: () => { accepted = true; },
@@ -52,6 +52,8 @@ export interface LaneMessageOptions {
52
52
  description: string;
53
53
  sequence: number;
54
54
  };
55
+ /** Source kind for a message recovered from the lane dropbox. */
56
+ messageSource?: "monitor_event" | "system_notice";
55
57
  /** Internal recovery messages do not emit a second command response. */
56
58
  silent?: boolean;
57
59
  /** Internal recovery claim is safe to archive once this callback runs. */
@@ -1,5 +1,5 @@
1
1
  import { accountStatus, coveredLaneIds, loadLanesConfig, MAX_WATCHED_LANES, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, monitorReminderText, runLaneWatch, } from "../lib/lanes/index.js";
2
- import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG } from "../lib/lanes/keep-alive.js";
2
+ import { LEAD_LANE_KEEP_ALIVE_FLAG } from "../lib/lanes/keep-alive.js";
3
3
  import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
4
4
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
5
5
  import { isLanesError, LanesError } from "../lib/lanes/errors.js";
@@ -50,10 +50,6 @@ export function createLaneWatchHandlers(deps) {
50
50
  company: resolveLaneCompany(hqRoot, lane).company ?? undefined,
51
51
  env: ctx.env,
52
52
  }),
53
- keepAliveNextCheckAtEnabled: (lane) => readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, {
54
- company: resolveLaneCompany(hqRoot, lane).company ?? undefined,
55
- env: ctx.env,
56
- }),
57
53
  readAccountStatuses: async (lanes) => {
58
54
  if (lanes.length === 0)
59
55
  return [];
@@ -61,7 +61,7 @@ import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STA
61
61
  import { laneStories, storyWarning, } from "../lib/lanes/stories.js";
62
62
  import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
63
63
  import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
64
- import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
64
+ import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
65
65
  import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
66
66
  import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
67
67
  import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
@@ -5159,17 +5159,11 @@ async function runShow(ctx, laneId, hasPendingQuestionForLane) {
5159
5159
  const keepAliveState = resolveLaneKeepAlive(hqRoot, storedLane);
5160
5160
  const keepAliveCompany = resolveLaneCompany(hqRoot, storedLane);
5161
5161
  const now = ctx.now().getTime();
5162
- const nextCheckAtEnabled = storedLane.state === "done" && storedLane.keep_alive === true
5163
- ? await readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, {
5164
- company: keepAliveCompany.company ?? undefined,
5165
- env: ctx.env,
5166
- })
5167
- : false;
5168
- const hasPendingWork = storedLane.keep_alive_standing === true || nextCheckAtEnabled
5162
+ const hasPendingWork = storedLane.state === "done" && storedLane.keep_alive === true
5169
5163
  ? hasPendingKeepAliveWork(hqRoot, storedLane)
5170
5164
  : false;
5171
5165
  const doneStateVerdict = storedLane.state === "done" && storedLane.keep_alive === true
5172
- ? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete, next_check_at: storedLane.last_envelope?.next_check_at }, storedLane.keep_alive_standing === true && hasPendingWork, { nextCheckAtEnabled, hasPendingWork, nowMs: now })
5166
+ ? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete, next_check_at: storedLane.last_envelope?.next_check_at }, storedLane.keep_alive_standing === true && hasPendingWork, { hasPendingWork, nowMs: now })
5173
5167
  : null;
5174
5168
  let managerStatusTarget;
5175
5169
  let managerStatusGateError;
@@ -5191,7 +5185,7 @@ async function runShow(ctx, laneId, hasPendingQuestionForLane) {
5191
5185
  now: ctx.now().getTime(),
5192
5186
  });
5193
5187
  const laneIsSenior = allLanes.some((item) => item.senior.kind === "lane" && item.senior.id === laneId);
5194
- const requestedNextCheckMs = nextCheckAtEnabled && !hasPendingWork
5188
+ const requestedNextCheckMs = storedLane.state === "done" && storedLane.keep_alive === true && !hasPendingWork
5195
5189
  ? keepAliveNextCheckAtMs(storedLane.last_envelope?.next_check_at, now, Date.parse(storedLane.last_envelope?.at ?? storedLane.updated_at))
5196
5190
  : undefined;
5197
5191
  const storedNextEligibleMs = Date.parse(storedLane.keep_alive_next_eligible_at ?? "");
@@ -16,6 +16,8 @@ export type FastScaffoldScript = {
16
16
  /** Path under `assets/scaffold/`, e.g. `core/scripts/foo.sh`. */
17
17
  asset: string;
18
18
  root?: "live";
19
+ /** Whether the bundled fast-route module closure needs Node's network timeout patch. */
20
+ requiresNodeNetworkCompatibility?: boolean;
19
21
  } | {
20
22
  /** Command path, joined with spaces, e.g. `policy frontmatter`. */
21
23
  name: string;
@@ -38,6 +40,8 @@ export type FastScaffoldScript = {
38
40
  shellHqRoot?: "none" | "live" | "reconcile-default";
39
41
  /** Whether this handler consumes stdin. Shell companions inherit stdin directly. */
40
42
  readsStdin?: boolean;
43
+ /** Whether the bundled fast-route module closure needs Node's network timeout patch. */
44
+ requiresNodeNetworkCompatibility?: boolean;
41
45
  };
42
46
  /** The relocated scripts that qualify for the entrypoint fast path. */
43
47
  export declare const FAST_SCAFFOLD_SCRIPTS: readonly FastScaffoldScript[];
@@ -115,6 +115,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
115
115
  flagKey: "hq-core-native-fast-route",
116
116
  familyFlagKey: "core-native-handoff",
117
117
  asset: "core/scripts/worktree-gc.sh",
118
+ requiresNodeNetworkCompatibility: true,
118
119
  root: "live",
119
120
  shellCwd: "live",
120
121
  shellHqRoot: "live",
@@ -127,6 +128,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
127
128
  flagKey: "hq-core-native-fast-route",
128
129
  familyFlagKey: "core-native-handoff",
129
130
  asset: "core/scripts/clean-worktree-reconcile-handoff.sh",
131
+ requiresNodeNetworkCompatibility: true,
130
132
  root: "cwd",
131
133
  shellCwd: "caller",
132
134
  shellHqRoot: "none",
@@ -139,6 +141,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
139
141
  flagKey: "hq-core-native-fast-route",
140
142
  familyFlagKey: "core-native-handoff",
141
143
  asset: "core/scripts/resume-thread-lock.sh",
144
+ requiresNodeNetworkCompatibility: true,
142
145
  root: "live",
143
146
  shellCwd: "live",
144
147
  shellHqRoot: "live",
@@ -151,6 +154,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
151
154
  flagKey: "hq-core-native-fast-route",
152
155
  familyFlagKey: "core-native-handoff",
153
156
  asset: "core/scripts/verify-story-deliverables.sh",
157
+ requiresNodeNetworkCompatibility: true,
154
158
  root: "live",
155
159
  shellCwd: "live",
156
160
  shellHqRoot: "live",
@@ -163,6 +167,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
163
167
  flagKey: "hq-core-native-fast-route",
164
168
  familyFlagKey: "core-native-jobs",
165
169
  asset: `core/scripts/${name === "reconcile" ? "outpost-jobs-reconcile" : `jobs-${name}`}.sh`,
170
+ requiresNodeNetworkCompatibility: true,
166
171
  root: name === "reconcile" ? "passthrough" : "cwd",
167
172
  shellCwd: "caller",
168
173
  shellHqRoot: name === "reconcile" ? "reconcile-default" : "none",
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import "./node-preflight.js";
3
- import "./node-network-compat.js";
3
+ import "./node-network-compat-fast.js";
4
4
  import { isVersionRequest } from "./version-request.js";
5
5
  import { isFastCoreRequest } from "./fast-scaffold.js";
6
6
  export declare const __test__: {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // MUST be first: guard the Node version before any dependency that needs a
3
3
  // Node 20+ API (e.g. util.styleText) or a newer native ABI is evaluated.
4
4
  import "./node-preflight.js";
5
- import "./node-network-compat.js";
5
+ import "./node-network-compat-fast.js";
6
6
  import { isVersionRequest, wantsLabelledVersionReport } from "./version-request.js";
7
7
  // Dependency-light: a pure parser + table, no command graph. Safe to load on
8
8
  // every path (including --version) without reintroducing the heavy startup.
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { type WorkerOptions } from "node:worker_threads";
9
9
  import { createFlagClient, type FlagClient, type FlagLookupOptions, type FlagSnapshot } from "@indigoai-us/hq-flags-client";
10
+ type FlagRegistrySentry = Pick<typeof import("@sentry/node"), "captureException" | "withScope">;
10
11
  export { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
11
12
  export type { ProcessFlagCompanyContext } from "./process-flag-company-context.js";
12
13
  export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
@@ -20,6 +21,7 @@ interface WorkerLike {
20
21
  export interface FlagRegistryDependencies {
21
22
  endpoint?: string;
22
23
  createWorker?: (filename: string | URL, options: WorkerOptions) => WorkerLike;
24
+ sentry?: FlagRegistrySentry;
23
25
  /** Test seam for exercising the no-source fail-open path. */
24
26
  workerSource?: string;
25
27
  captureWorkerFailure?: (error: Error, tags: Record<string, string>) => void;
@@ -6,12 +6,16 @@
6
6
  * keep a short-lived, offline CLI alive until the client's network deadline.
7
7
  */
8
8
  import { Worker } from "node:worker_threads";
9
- import * as Sentry from "@sentry/node";
10
9
  import { createFlagClient, isFlagSnapshot, readLocalOverride, resolveFlag, } from "@indigoai-us/hq-flags-client";
11
10
  import { resolveFlagRegistryEndpoint, } from "./flag-registry-endpoint.js";
12
11
  import { FLAG_REGISTRY_WORKER_SOURCE } from "./flag-registry-worker-source.generated.js";
13
12
  import { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
14
13
  import { writeFlagSnapshotFile } from "./flag-snapshot-file.js";
14
+ function captureFlagRegistryException(error, tags, sentry) {
15
+ if (!sentry)
16
+ return;
17
+ sentry.captureException(error, { tags });
18
+ }
15
19
  export { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
16
20
  export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
17
21
  const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
@@ -58,6 +62,7 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
58
62
  return processReader;
59
63
  const endpoint = resolveFlagRegistryEndpoint(dependencies.endpoint, process.env);
60
64
  const company = resolveProcessFlagCompanyContext();
65
+ const sentry = dependencies.sentry;
61
66
  const companyUid = company.companyUid;
62
67
  const companyIdentifiers = [companyUid, company.companySlug].filter((value) => Boolean(value));
63
68
  processReader = processSnapshotReader(companyIdentifiers);
@@ -65,7 +70,7 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
65
70
  const embeddedSource = dependencies.workerSource ?? FLAG_REGISTRY_WORKER_SOURCE;
66
71
  if (embeddedSource.length === 0) {
67
72
  (dependencies.captureWorkerFailure ?? ((error, tags) => {
68
- Sentry.captureException(error, { tags });
73
+ captureFlagRegistryException(error, tags, sentry);
69
74
  }))(new Error("Self-contained flag-registry worker source is unavailable"), {
70
75
  component: "flag-registry-worker-startup",
71
76
  worker: "flag-registry",
@@ -87,10 +92,14 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
87
92
  // its process-wide captureWorkerErrors option is disabled. Scope only this
88
93
  // worker so the reporting boundary can classify its startup errors without
89
94
  // changing capture for unrelated workers.
90
- const worker = Sentry.withScope((scope) => {
91
- scope.setTag("worker", "flag-registry");
92
- return (dependencies.createWorker ?? ((filename, options) => new Worker(filename, options)))(embeddedSource, workerOptions);
93
- });
95
+ const createWorker = dependencies.createWorker ??
96
+ ((filename, options) => new Worker(filename, options));
97
+ const worker = sentry
98
+ ? sentry.withScope((scope) => {
99
+ scope.setTag("worker", "flag-registry");
100
+ return createWorker(embeddedSource, workerOptions);
101
+ })
102
+ : createWorker(embeddedSource, workerOptions);
94
103
  worker.on("message", (value) => {
95
104
  if (isFlagSnapshot(value)) {
96
105
  heldSnapshot = value;
@@ -99,12 +108,10 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
99
108
  writeFlagSnapshotFile(companyUid, value.flags);
100
109
  }
101
110
  catch (error) {
102
- Sentry.captureException(error, {
103
- tags: {
104
- component: "flag-snapshot-cache-write",
105
- company_uid_present: "true",
106
- },
107
- });
111
+ captureFlagRegistryException(error instanceof Error ? error : new Error(String(error)), {
112
+ component: "flag-snapshot-cache-write",
113
+ company_uid_present: "true",
114
+ }, sentry);
108
115
  }
109
116
  }
110
117
  }
@@ -154,7 +154,7 @@ function projectStories(hqRoot, relativePath, companySlug, projectId) {
154
154
  status,
155
155
  ...(story.passes === true ? { passes: true } : {}),
156
156
  ...(briefPath ? { briefPath } : {}),
157
- ...(Array.isArray(story.lanes) ? { laneIds: story.lanes.map((lane) => typeof lane === "string" ? lane : lane.lane_id) } : typeof story.lane === "string" && story.lane.trim() ? { laneIds: [story.lane] } : story.lane === "" ? { laneIds: [] } : {}),
157
+ ...storyLaneIds(story),
158
158
  ...(typeof story.updated_at === "string" ? { updatedAt: story.updated_at } : {}),
159
159
  ...(typeof story.external_blocker === "string" ? { externalBlocker: story.external_blocker } : {}),
160
160
  };
@@ -207,6 +207,18 @@ function parseOkrReports(status) {
207
207
  return { at: value.at, kpis: parseLines(value.kpis, "KPI"), okrs: parseLines(value.okrs, "OKR") };
208
208
  }).sort((a, b) => a.at.localeCompare(b.at));
209
209
  }
210
+ // A story's current `lane` counts alongside its `lanes` history, so `lanes: []` with `lane: X` is assigned to X.
211
+ function storyLaneIds(story) {
212
+ const history = Array.isArray(story.lanes)
213
+ ? story.lanes.map((lane) => typeof lane === "string" ? lane : lane.lane_id)
214
+ : undefined;
215
+ const current = typeof story.lane === "string" && story.lane.trim() ? story.lane : undefined;
216
+ if (history)
217
+ return { laneIds: current && !history.includes(current) ? [...history, current] : history };
218
+ if (current)
219
+ return { laneIds: [current] };
220
+ return story.lane === "" ? { laneIds: [] } : {};
221
+ }
210
222
  function statusBlockers(status) {
211
223
  const raw = status.blockers;
212
224
  if (raw === undefined)
@@ -256,7 +268,9 @@ export function planLeadAutopilotWakes(input) {
256
268
  const finishedMs = Date.parse(story.updatedAt);
257
269
  if (!Number.isFinite(finishedMs) || now.getTime() - finishedMs < LEAD_AUTOPILOT_REVIEW_DELAY_MS)
258
270
  return [];
259
- return story.laneIds.filter((laneId) => !knownChildIds.has(laneId)).map((laneId) => ({ laneId, story, finishedAt: story.updatedAt }));
271
+ // The lead's own lane is never a finished worker of itself.
272
+ return story.laneIds.filter((laneId) => laneId !== lead.lane_id && !knownChildIds.has(laneId))
273
+ .map((laneId) => ({ laneId, story, finishedAt: story.updatedAt }));
260
274
  });
261
275
  const overdueReviews = [...finishedReviews, ...missingReviews].sort((a, b) => a.laneId.localeCompare(b.laneId));
262
276
  if (overdueReviews.length > 0) {
@@ -351,6 +365,9 @@ export function planLeadAutopilotWakes(input) {
351
365
  if (story.status !== "in_progress")
352
366
  return [];
353
367
  const laneIds = story.laneIds ?? [];
368
+ // A story assigned to the lead itself is worked by the lead, which is the lane being woken.
369
+ if (laneIds.includes(lead.lane_id))
370
+ return [];
354
371
  const live = laneIds.filter((laneId) => children.some((child) => child.lane_id === laneId && LIVE_STATES.has(child.state)));
355
372
  if (live.length > 0)
356
373
  return [];
@@ -137,6 +137,7 @@ export type LaunchTrigger = {
137
137
  text: string;
138
138
  message_id: string;
139
139
  monitor_event?: true;
140
+ system_notice?: true;
140
141
  } | {
141
142
  /** `hq lanes resume`: the operator's explicit opt-in to continue an exited lane. */
142
143
  kind: "resume";
@@ -1,8 +1,6 @@
1
1
  import { type LaneRecord, type ManagerStatusGateHistoryEntry, type QuestionRecord } from "./types.js";
2
2
  export declare const LEAD_LANE_KEEP_ALIVE_FLAG = "lanes.lead-keep-alive";
3
3
  export declare const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
4
- export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG = "lanes.keep-alive-next-check-at";
5
- export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_DEFAULT_ENABLED = false;
6
4
  export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS: number;
7
5
  export declare const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG = "lanes.keep-alive-resume-blocked";
8
6
  export declare const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_DEFAULT_ENABLED = false;
@@ -30,6 +28,7 @@ export declare function queueLaneTerminalResume(current: LaneRecord, at: string,
30
28
  allowResume?: boolean;
31
29
  mergeExisting?: boolean;
32
30
  allowBlockedResume?: boolean;
31
+ trigger?: "resume" | "watcher";
33
32
  }): LaneTerminalResumeResult;
34
33
  /** Persist a manager status miss and queue its one bounded follow-up turn. */
35
34
  export declare function queueManagerStatusGateResume(hqRoot: string, laneId: string, entry: Omit<ManagerStatusGateHistoryEntry, "event" | "resume_queued" | "resume_skipped_reason">, resumeText: string): LaneRecord;
@@ -47,7 +46,6 @@ export type DoneKeepAliveVerdict = "eligible" | "standing_idle" | "story_complet
47
46
  export declare function keepAliveNextCheckAtMs(value: unknown, nowMs: number, anchorMs?: number): number | undefined;
48
47
  /** Shared done-state policy used by watch, reconcile, and the show command. */
49
48
  export declare function doneKeepAliveVerdict(lane: Pick<LaneRecord, "keep_alive_standing">, envelope: KeepAliveDoneEnvelope, hasQueuedWork: boolean, options?: {
50
- nextCheckAtEnabled?: boolean;
51
49
  hasPendingWork?: boolean;
52
50
  nowMs?: number;
53
51
  nextCheckAnchorMs?: number;
@@ -6,8 +6,6 @@ import { appendCappedHistory, listQuestionIds, readQuestion, retryUpdateLane } f
6
6
  import { isTerminalLaneState, isIsoDateTime, } from "./types.js";
7
7
  export const LEAD_LANE_KEEP_ALIVE_FLAG = "lanes.lead-keep-alive";
8
8
  export const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
9
- export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG = "lanes.keep-alive-next-check-at";
10
- export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_DEFAULT_ENABLED = false;
11
9
  export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS = 24 * 60 * 60 * 1000;
12
10
  export const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG = "lanes.keep-alive-resume-blocked";
13
11
  export const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_DEFAULT_ENABLED = false;
@@ -63,7 +61,7 @@ export function queueLaneTerminalResume(current, at, resumeText, options = {}) {
63
61
  terminal_details: undefined,
64
62
  pending_resume: {
65
63
  session_id: sessionId,
66
- trigger: "resume",
64
+ trigger: options.trigger ?? "watcher",
67
65
  resumed_from: current.state,
68
66
  text: resumeText,
69
67
  compact: true,
@@ -126,7 +124,7 @@ export function doneKeepAliveVerdict(lane, envelope, hasQueuedWork, options = {}
126
124
  verdict = "story_complete";
127
125
  else
128
126
  verdict = hasQueuedWork ? "eligible" : "standing_idle";
129
- if (verdict === "eligible" && options.nextCheckAtEnabled === true && options.hasPendingWork !== true) {
127
+ if (verdict === "eligible" && options.hasPendingWork !== true) {
130
128
  const nowMs = options.nowMs ?? Date.now();
131
129
  const anchorMs = options.nextCheckAnchorMs ?? nowMs;
132
130
  const nextCheckAtMs = keepAliveNextCheckAtMs(envelope.next_check_at, nowMs, anchorMs);
@@ -25,7 +25,7 @@ import { readLanesRegistryFlag } from "./registry-flag.js";
25
25
  import { resolveLaneCompany } from "./lane-company.js";
26
26
  import { appendDropboxMessage } from "./dropbox.js";
27
27
  import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolGuard, RUNAWAY_TOOL_GUARD_FLAG, } from "./runaway-tool-guard.js";
28
- import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, queueLaneTerminalResume, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
28
+ import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, queueLaneTerminalResume, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
29
29
  import { providerSupportsSessionResume } from "./delivery.js";
30
30
  import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, readQuotaSignal, recordAccountQuota, } from "./quota.js";
31
31
  import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
@@ -1087,7 +1087,6 @@ export async function runPassBody(hqRoot, deps) {
1087
1087
  }
1088
1088
  }));
1089
1089
  const keepAliveFlagCompanies = new Map();
1090
- const nextCheckAtFlagCompanyKeys = new Set();
1091
1090
  for (const lane of lanes) {
1092
1091
  if (!(lane.state === "done" || lane.state === "interrupted" || lane.state === "blocked" || lane.state === "killed" || lane.state === "cancelled" || lane.state === "failed") ||
1093
1092
  (lane.keep_alive !== true && lane.worker_profile === undefined) ||
@@ -1098,26 +1097,25 @@ export async function runPassBody(hqRoot, deps) {
1098
1097
  !providerSupportsSessionResume(lane.provider) ||
1099
1098
  !(lane.provider_session_id ?? lane.last_resume?.session_id))))
1100
1099
  continue;
1101
- let hasNextCheckAt = false;
1102
1100
  if (lane.state === "done") {
1103
1101
  const envelope = readLatestEnvelopeRecord(hqRoot, lane.lane_id)?.envelope;
1104
1102
  if (!envelope || envelope.decision !== "done")
1105
1103
  continue;
1106
1104
  const hasQueuedWork = lane.keep_alive_standing === true && hasPendingKeepAliveWork(hqRoot, lane, questions);
1107
- if (doneKeepAliveVerdict(lane, envelope, hasQueuedWork) !== "eligible")
1105
+ if (doneKeepAliveVerdict(lane, envelope, hasQueuedWork, {
1106
+ hasPendingWork: hasPendingKeepAliveWork(hqRoot, lane, questions),
1107
+ nowMs: now.getTime(),
1108
+ nextCheckAnchorMs: Date.parse(lane.last_envelope?.at ?? lane.updated_at),
1109
+ }) !== "eligible")
1108
1110
  continue;
1109
- hasNextCheckAt = envelope.next_check_at !== undefined;
1110
1111
  }
1111
1112
  const company = resolveLaneCompany(hqRoot, lane).company;
1112
1113
  if (company === null)
1113
1114
  continue;
1114
1115
  const key = keepAliveCompanyKey(company);
1115
1116
  keepAliveFlagCompanies.set(key, company);
1116
- if (hasNextCheckAt)
1117
- nextCheckAtFlagCompanyKeys.add(key);
1118
1117
  }
1119
1118
  const keepAliveFlagByCompany = new Map();
1120
- const nextCheckAtFlagByCompany = new Map();
1121
1119
  const blockedResumeFlagByCompany = new Map();
1122
1120
  const immediateFirstResumeFlagByCompany = new Map();
1123
1121
  const supersessionLineageAwareByCompany = new Map();
@@ -1130,11 +1128,8 @@ export async function runPassBody(hqRoot, deps) {
1130
1128
  lane.keep_alive === true &&
1131
1129
  laneRunsHere(lane, startDeps.host) &&
1132
1130
  keepAliveCompanyKey(resolveLaneCompany(hqRoot, lane).company) === key);
1133
- const [keepAliveEnabled, nextCheckAtEnabled, lineageAware, blockedResumeEnabled, immediateFirstResumeEnabled] = await Promise.all([
1131
+ const [keepAliveEnabled, lineageAware, blockedResumeEnabled, immediateFirstResumeEnabled] = await Promise.all([
1134
1132
  readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, options),
1135
- nextCheckAtFlagCompanyKeys.has(key)
1136
- ? readRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, options)
1137
- : Promise.resolve(false),
1138
1133
  readRegistryFlag(LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, options),
1139
1134
  hasBlockedKeepAliveLane
1140
1135
  ? readRegistryFlag(LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, options)
@@ -1142,7 +1137,6 @@ export async function runPassBody(hqRoot, deps) {
1142
1137
  readRegistryFlag(LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, options),
1143
1138
  ]);
1144
1139
  keepAliveFlagByCompany.set(key, keepAliveEnabled);
1145
- nextCheckAtFlagByCompany.set(key, nextCheckAtEnabled);
1146
1140
  blockedResumeFlagByCompany.set(key, blockedResumeEnabled);
1147
1141
  immediateFirstResumeFlagByCompany.set(key, immediateFirstResumeEnabled);
1148
1142
  supersessionLineageAwareByCompany.set(key, lineageAware);
@@ -1165,7 +1159,7 @@ export async function runPassBody(hqRoot, deps) {
1165
1159
  const freshLanes = loadLaneRecords(hqRoot, summary);
1166
1160
  const freshQuestions = loadQuestionRecords(hqRoot, summary);
1167
1161
  await closeInterruptedDoneStoryWorkers(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, storyCloseEnabledByCompany);
1168
- await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany, immediateFirstResumeFlagByCompany);
1162
+ await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, immediateFirstResumeFlagByCompany);
1169
1163
  const drainLanes = loadLaneRecords(hqRoot, summary);
1170
1164
  await admitQueued(hqRoot, drainLanes, summary, admissionDeps, now);
1171
1165
  await resumeAwaiting(hqRoot, drainLanes, freshQuestions, summary, admissionDeps);
@@ -2188,7 +2182,7 @@ async function resumeBlockedKeepAlive(hqRoot, lane, questions, summary, deps, no
2188
2182
  current = afterPreparation;
2189
2183
  }
2190
2184
  const expectedVersion = current.version;
2191
- const result = queueLaneTerminalResume(current, now.toISOString(), LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, { allowBlockedResume: true });
2185
+ const result = queueLaneTerminalResume(current, now.toISOString(), LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, { allowBlockedResume: true, trigger: "resume" });
2192
2186
  if (!result.resumeQueued) {
2193
2187
  if (result.resumeSkippedReason !== undefined) {
2194
2188
  summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_" + result.resumeSkippedReason });
@@ -2244,7 +2238,7 @@ function appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, at, reason, dep
2244
2238
  }),
2245
2239
  }), false);
2246
2240
  }
2247
- async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany, immediateFirstResumeFlagByCompany) {
2241
+ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, immediateFirstResumeFlagByCompany) {
2248
2242
  const nowMs = now.getTime();
2249
2243
  const nowIso = now.toISOString();
2250
2244
  const config = loadLanesConfig(hqRoot);
@@ -2291,13 +2285,9 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
2291
2285
  const envelope = readLatestEnvelopeRecord(hqRoot, lane.lane_id)?.envelope;
2292
2286
  if (!envelope || envelope.decision !== "done")
2293
2287
  continue;
2294
- const nextCheckAtEnabled = nextCheckAtFlagByCompany.get(keepAliveCompanyKey(companyForLane(lane))) === true;
2295
- const hasPendingWork = lane.keep_alive_standing === true || nextCheckAtEnabled
2296
- ? hasPendingKeepAliveWork(hqRoot, lane, questions)
2297
- : false;
2288
+ const hasPendingWork = hasPendingKeepAliveWork(hqRoot, lane, questions);
2298
2289
  const hasQueuedWork = lane.keep_alive_standing === true && hasPendingWork;
2299
2290
  const verdict = doneKeepAliveVerdict(lane, envelope, hasQueuedWork, {
2300
- nextCheckAtEnabled,
2301
2291
  hasPendingWork,
2302
2292
  nowMs,
2303
2293
  nextCheckAnchorMs: Date.parse(lane.last_envelope?.at ?? lane.updated_at),
@@ -2415,13 +2405,9 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
2415
2405
  const latest = readLatestEnvelopeRecord(hqRoot, lane.lane_id);
2416
2406
  if (!latest || latest.envelope.decision !== "done")
2417
2407
  continue;
2418
- const nextCheckAtEnabled = nextCheckAtFlagByCompany.get(keepAliveCompanyKey(companyForLane(lane))) === true;
2419
- const hasPendingWork = lane.keep_alive_standing === true || nextCheckAtEnabled
2420
- ? hasPendingKeepAliveWork(hqRoot, lane, questions)
2421
- : false;
2408
+ const hasPendingWork = hasPendingKeepAliveWork(hqRoot, lane, questions);
2422
2409
  const hasQueuedWork = lane.keep_alive_standing === true && hasPendingWork;
2423
2410
  const verdict = doneKeepAliveVerdict(lane, latest.envelope, hasQueuedWork, {
2424
- nextCheckAtEnabled,
2425
2411
  hasPendingWork,
2426
2412
  nowMs,
2427
2413
  nextCheckAnchorMs: Date.parse(lane.last_envelope?.at ?? lane.updated_at),
@@ -2507,13 +2493,9 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
2507
2493
  const currentEnvelope = readLatestEnvelopeRecord(hqRoot, current.lane_id);
2508
2494
  if (!currentEnvelope || currentEnvelope.envelope.decision !== "done")
2509
2495
  continue;
2510
- const nextCheckAtEnabled = nextCheckAtFlagByCompany.get(keepAliveCompanyKey(companyForLane(current))) === true;
2511
- const hasPendingWork = current.keep_alive_standing === true || nextCheckAtEnabled
2512
- ? hasPendingKeepAliveWork(hqRoot, current, questions)
2513
- : false;
2496
+ const hasPendingWork = hasPendingKeepAliveWork(hqRoot, current, questions);
2514
2497
  const hasQueuedWork = current.keep_alive_standing === true && hasPendingWork;
2515
2498
  const verdict = doneKeepAliveVerdict(current, currentEnvelope.envelope, hasQueuedWork, {
2516
- nextCheckAtEnabled,
2517
2499
  hasPendingWork,
2518
2500
  nowMs,
2519
2501
  nextCheckAnchorMs: Date.parse(current.last_envelope?.at ?? current.updated_at),
@@ -2794,12 +2776,8 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
2794
2776
  const envelope = readLatestEnvelopeRecord(hqRoot, latest.lane_id);
2795
2777
  if (!envelope || envelope.envelope.decision !== "done")
2796
2778
  continue;
2797
- const nextCheckAtEnabled = nextCheckAtFlagByCompany.get(keepAliveCompanyKey(companyForLane(latest))) === true;
2798
- const hasPendingWork = latest.keep_alive_standing === true || nextCheckAtEnabled
2799
- ? hasPendingKeepAliveWork(hqRoot, latest, questions)
2800
- : false;
2779
+ const hasPendingWork = hasPendingKeepAliveWork(hqRoot, latest, questions);
2801
2780
  const finalDoneVerdict = doneKeepAliveVerdict(latest, envelope.envelope, latest.keep_alive_standing === true && hasPendingWork, {
2802
- nextCheckAtEnabled,
2803
2781
  hasPendingWork,
2804
2782
  nowMs,
2805
2783
  nextCheckAnchorMs: Date.parse(latest.last_envelope?.at ?? latest.updated_at),
@@ -198,6 +198,8 @@ export declare function resumePrompt(input: {
198
198
  answers?: AnsweredQuestionLine[];
199
199
  /** `hq lanes resume`: the state the operator resumed the lane from. */
200
200
  resumedFrom?: string;
201
+ /** Automatic continuation initiated by the lane watcher. */
202
+ resumeSource?: "watcher";
201
203
  /** Exact validator errors recorded after the prior envelope repair failed. */
202
204
  envelopeRepairErrors?: {
203
205
  first_error: string;
@@ -372,7 +372,18 @@ function pendingResumeForRetry(hqRoot, lane, sessionId, at) {
372
372
  if (last.trigger === "answered_questions") {
373
373
  return { session_id: sessionId, trigger: "answered_questions", at, ...compact, ...keepAlive };
374
374
  }
375
- if (last.trigger === "message") {
375
+ if (last.trigger === "watcher") {
376
+ return {
377
+ session_id: sessionId,
378
+ trigger: "watcher",
379
+ resumed_from: last.resumed_from ?? "interrupted",
380
+ ...(last.text !== undefined ? { text: last.text } : {}),
381
+ at,
382
+ ...compact,
383
+ ...keepAlive,
384
+ };
385
+ }
386
+ if (last.trigger === "message" || last.trigger === "monitor_event" || last.trigger === "system_notice") {
376
387
  if (last.message_id === undefined) {
377
388
  throw new LanesError("account_retry_context_missing", `The message id needed to resume lane ${lane.lane_id} is missing`, { lane_id: lane.lane_id, trigger: last.trigger });
378
389
  }
@@ -382,9 +393,10 @@ function pendingResumeForRetry(hqRoot, lane, sessionId, at) {
382
393
  }
383
394
  return {
384
395
  session_id: sessionId,
385
- trigger: "message",
396
+ trigger: last.trigger === "message" && last.monitor_event === true ? "monitor_event" : last.trigger,
386
397
  text,
387
398
  message_id: last.message_id,
399
+ ...(last.monitor_event === true || last.trigger === "monitor_event" ? { monitor_event: true } : {}),
388
400
  at,
389
401
  ...compact,
390
402
  ...keepAlive,
@@ -1071,7 +1083,7 @@ export function readAnsweredQuestionLines(answersPath) {
1071
1083
  export function resumePrompt(input) {
1072
1084
  const parts = [];
1073
1085
  if (input.resumedFrom !== undefined) {
1074
- parts.push(`The operator resumed this lane from state ${input.resumedFrom}. ` +
1086
+ parts.push(`${input.resumeSource === "watcher" ? "The lane watcher" : "The operator"} resumed this lane from state ${input.resumedFrom}. ` +
1075
1087
  (input.resumedFrom === "interrupted"
1076
1088
  ? "The previous run ended without writing its envelope; continue the work from where it stopped."
1077
1089
  : "Continue the work from where the previous run stopped."));
@@ -1743,7 +1755,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1743
1755
  const profileChanged = profile !== undefined && existing.worker_profile_hash !== profile.contentHash;
1744
1756
  const config = loadLanesConfig(hqRoot);
1745
1757
  const repairRecordedAccount = plan.mode === "resume" &&
1746
- (trigger.kind === "resume" || pendingResume?.trigger === "resume");
1758
+ (trigger.kind === "resume" || pendingResume?.trigger === "resume" || pendingResume?.trigger === "watcher");
1747
1759
  const recordedModel = repairRecordedAccount && existing.model_source === "account"
1748
1760
  ? existing.model
1749
1761
  : undefined;
@@ -1919,6 +1931,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1919
1931
  ? pendingResume.text
1920
1932
  : undefined,
1921
1933
  resumedFrom: trigger.kind === "resume" ? trigger.from : pendingResume?.resumed_from,
1934
+ resumeSource: pendingResume?.trigger === "watcher" ? "watcher" : undefined,
1922
1935
  answersPath: answeredLines !== undefined ? answersPath : undefined,
1923
1936
  answers: answeredLines,
1924
1937
  envelopeRepairErrors: lane.terminal_reason === ENVELOPE_INVALID_REASON &&
@@ -2019,14 +2032,24 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
2019
2032
  const nowIso = (deps.now ?? (() => new Date()))().toISOString();
2020
2033
  const resumeHistoryTrigger = pendingResume?.keep_alive === true
2021
2034
  ? "keep_alive"
2022
- : pendingResume?.trigger === "message" || trigger.kind === "message"
2023
- ? "message"
2024
- : pendingResume?.trigger === "resume" || trigger.kind === "resume"
2025
- ? "resume_cli"
2026
- : "reconcile";
2035
+ : pendingResume?.trigger === "monitor_event" || pendingResume?.monitor_event === true ||
2036
+ (trigger.kind === "message" && trigger.monitor_event === true)
2037
+ ? "monitor_event"
2038
+ : pendingResume?.trigger === "system_notice" ||
2039
+ (trigger.kind === "message" && trigger.system_notice === true)
2040
+ ? "system_notice"
2041
+ : pendingResume?.trigger === "watcher"
2042
+ ? "watcher"
2043
+ : pendingResume?.trigger === "message" || trigger.kind === "message"
2044
+ ? "message"
2045
+ : pendingResume?.trigger === "resume" || trigger.kind === "resume"
2046
+ ? "resume_cli"
2047
+ : "reconcile";
2027
2048
  const resumeHistoryCaller = pendingResume?.caller ||
2028
- (typeof env.HQ_LANE_ID === "string" ? env.HQ_LANE_ID.trim() : "") ||
2029
- (trigger.kind === "answered_questions" || trigger.kind === "resume" || trigger.kind === "message"
2049
+ (pendingResume?.trigger !== "watcher" && typeof env.HQ_LANE_ID === "string" ? env.HQ_LANE_ID.trim() : "") ||
2050
+ (pendingResume?.trigger === "message" || pendingResume?.trigger === "monitor_event" ||
2051
+ pendingResume?.trigger === "system_notice" || trigger.kind === "answered_questions" ||
2052
+ trigger.kind === "resume" || trigger.kind === "message"
2030
2053
  ? "user"
2031
2054
  : undefined);
2032
2055
  let statusGateHeartbeat;
@@ -2159,7 +2182,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
2159
2182
  session_id: resume.session_id,
2160
2183
  at: nowIso,
2161
2184
  account: resolved.id,
2162
- trigger: trigger.kind,
2185
+ trigger: trigger.kind === "message" && trigger.system_notice === true ? "system_notice" : trigger.kind,
2163
2186
  ...(trigger.kind === "message"
2164
2187
  ? {
2165
2188
  message_id: trigger.message_id,
@@ -766,7 +766,7 @@ export interface FailedResumeHistoryEntry {
766
766
  export interface PendingResume {
767
767
  /** Fresh answered-question launches need no previous session id. */
768
768
  session_id?: string;
769
- trigger: "answered_questions" | "message" | "resume";
769
+ trigger: "answered_questions" | "message" | "monitor_event" | "system_notice" | "resume" | "watcher";
770
770
  /** True when this queued resume was initiated automatically by keep-alive. */
771
771
  keep_alive?: true;
772
772
  resumed_from?: "interrupted" | "done" | "blocked" | "failed" | "awaiting_input";
@@ -797,7 +797,7 @@ export interface LaneResumeRecord {
797
797
  at: string;
798
798
  /** Account the resumed run was bound to. */
799
799
  account?: string;
800
- trigger: "answered_questions" | "message" | "resume";
800
+ trigger: "answered_questions" | "message" | "monitor_event" | "system_notice" | "resume" | "watcher";
801
801
  /** True when this was an automatic keep-alive resume. */
802
802
  keep_alive?: true;
803
803
  message_id?: string;
@@ -23,7 +23,6 @@ export interface LaneWatchDeps {
23
23
  readQuestion?: (hqRoot: string, questionId: string) => QuestionRecord | null;
24
24
  readAccountStatuses?: (lanes: readonly LaneRecord[]) => Promise<readonly LaneWatchAccountStatus[]>;
25
25
  keepAliveEnabled?: (lane: LaneRecord) => boolean | Promise<boolean>;
26
- keepAliveNextCheckAtEnabled?: (lane: LaneRecord) => boolean | Promise<boolean>;
27
26
  sleep?: (ms: number) => Promise<void>;
28
27
  intervalMs?: number;
29
28
  maxOutputLines?: number;
@@ -32,7 +32,7 @@ function validateLaneIds(laneIds) {
32
32
  }
33
33
  return unique;
34
34
  }
35
- function canResumeKeepAliveLane(hqRoot, lane, nextCheckAtEnabled = false, nowMs = Date.now()) {
35
+ function canResumeKeepAliveLane(hqRoot, lane, nowMs = Date.now()) {
36
36
  if (lane.keep_alive !== true ||
37
37
  lane.keep_alive_opted_in_at === undefined ||
38
38
  !Number.isFinite(Date.parse(lane.keep_alive_opted_in_at)) ||
@@ -49,10 +49,8 @@ function canResumeKeepAliveLane(hqRoot, lane, nextCheckAtEnabled = false, nowMs
49
49
  return lane.terminal_reason === "exited_with_partial_output";
50
50
  }
51
51
  if (lane.state === "done") {
52
- const hasPendingWork = lane.keep_alive_standing === true || nextCheckAtEnabled
53
- ? hasPendingKeepAliveWork(hqRoot, lane)
54
- : false;
55
- const nextCheckAtMs = nextCheckAtEnabled && !hasPendingWork
52
+ const hasPendingWork = hasPendingKeepAliveWork(hqRoot, lane);
53
+ const nextCheckAtMs = !hasPendingWork
56
54
  ? keepAliveNextCheckAtMs(lane.last_envelope?.next_check_at, nowMs, Date.parse(lane.last_envelope?.at ?? lane.updated_at))
57
55
  : undefined;
58
56
  if (nextCheckAtMs !== undefined && nowMs < nextCheckAtMs)
@@ -143,10 +141,7 @@ export async function runLaneWatch(hqRoot, laneIds, deps = {}) {
143
141
  continue;
144
142
  if (!await deps.keepAliveEnabled(record))
145
143
  continue;
146
- const nextCheckAtEnabled = deps.keepAliveNextCheckAtEnabled === undefined
147
- ? false
148
- : await deps.keepAliveNextCheckAtEnabled(record);
149
- if (canResumeKeepAliveLane(hqRoot, record, nextCheckAtEnabled))
144
+ if (canResumeKeepAliveLane(hqRoot, record))
150
145
  keepAliveWaiting.add(record.lane_id);
151
146
  }
152
147
  }
package/dist/main.js CHANGED
@@ -201,7 +201,7 @@ export async function runCli() {
201
201
  const isAuthLogin = process.argv[2] === "auth" && process.argv[3] === "login";
202
202
  if (!isAuthLogin) {
203
203
  runGlobalNetworkLifecycle(process.argv, () => {
204
- void kickFlagRegistryReadiness();
204
+ void kickFlagRegistryReadiness({ sentry: Sentry });
205
205
  });
206
206
  }
207
207
  // Set when a self-update re-exec'd this command on a newer CLI: the child
@@ -0,0 +1,12 @@
1
+ /** Node network defaults shared by the full graph and the selective fast path. */
2
+ export declare const HQ_NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS = 1000;
3
+ export interface NodeNetworkCompatibilityOptions {
4
+ nodeOptions?: string;
5
+ execArgv?: readonly string[];
6
+ /** Inject the net timeout setter (tests). Pass null to simulate a runtime without it. */
7
+ setAttemptTimeout?: ((milliseconds: number) => void) | null;
8
+ }
9
+ export declare function configureNodeNetworkCompatibility(options?: NodeNetworkCompatibilityOptions): boolean;
10
+ /** Apply at most once when both entrypoint routing layers import this module. */
11
+ export declare function configureNodeNetworkCompatibilityOnce(): boolean;
12
+ //# sourceMappingURL=node-network-compat-core.d.ts.map
@@ -0,0 +1,41 @@
1
+ /** Node network defaults shared by the full graph and the selective fast path. */
2
+ import * as nodeNet from "node:net";
3
+ export const HQ_NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS = 1000;
4
+ const ATTEMPT_TIMEOUT_OPTION = "--network-family-autoselection-attempt-timeout";
5
+ function hasExplicitAttemptTimeout(nodeOptions, execArgv) {
6
+ const optionPattern = new RegExp("(?:^|\\s)" + ATTEMPT_TIMEOUT_OPTION + "(?:=|\\s|$)");
7
+ return (optionPattern.test(nodeOptions) ||
8
+ execArgv.some((argument) => argument === ATTEMPT_TIMEOUT_OPTION ||
9
+ argument.startsWith(ATTEMPT_TIMEOUT_OPTION + "=")));
10
+ }
11
+ function resolveSetAttemptTimeout(override) {
12
+ if (override === null)
13
+ return undefined;
14
+ if (override)
15
+ return override;
16
+ const fn = nodeNet.setDefaultAutoSelectFamilyAttemptTimeout;
17
+ return typeof fn === "function" ? fn.bind(nodeNet) : undefined;
18
+ }
19
+ export function configureNodeNetworkCompatibility(options = {}) {
20
+ const nodeOptions = options.nodeOptions ?? process.env.NODE_OPTIONS ?? "";
21
+ const execArgv = options.execArgv ?? process.execArgv;
22
+ if (hasExplicitAttemptTimeout(nodeOptions, execArgv)) {
23
+ return false;
24
+ }
25
+ const setAttemptTimeout = resolveSetAttemptTimeout(options.setAttemptTimeout);
26
+ if (!setAttemptTimeout) {
27
+ // Runtime lacks the API; the Node preflight should already have exited.
28
+ return false;
29
+ }
30
+ setAttemptTimeout(HQ_NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS);
31
+ return true;
32
+ }
33
+ let configurationAttempted = false;
34
+ /** Apply at most once when both entrypoint routing layers import this module. */
35
+ export function configureNodeNetworkCompatibilityOnce() {
36
+ if (configurationAttempted)
37
+ return false;
38
+ configurationAttempted = true;
39
+ return configureNodeNetworkCompatibility();
40
+ }
41
+ //# sourceMappingURL=node-network-compat-core.js.map
@@ -0,0 +1,7 @@
1
+ /** Selective Node network setup for the entrypoint's fast routes. */
2
+ /**
3
+ * Full-graph invocations keep the historical unconditional network patch.
4
+ * Fast routes opt in only when their direct implementation uses Node networking.
5
+ */
6
+ export declare function shouldConfigureNodeNetworkCompatibility(argv: readonly string[]): boolean;
7
+ //# sourceMappingURL=node-network-compat-fast.d.ts.map
@@ -0,0 +1,23 @@
1
+ /** Selective Node network setup for the entrypoint's fast routes. */
2
+ import { parseFastCore, parseSecretsEnvFastRequest } from "./fast-scaffold.js";
3
+ import { isVersionRequest, wantsLabelledVersionReport } from "./version-request.js";
4
+ /**
5
+ * Full-graph invocations keep the historical unconditional network patch.
6
+ * Fast routes opt in only when their direct implementation uses Node networking.
7
+ */
8
+ export function shouldConfigureNodeNetworkCompatibility(argv) {
9
+ if (isVersionRequest(argv))
10
+ return wantsLabelledVersionReport(argv);
11
+ const fastCore = parseFastCore(argv);
12
+ if (fastCore)
13
+ return fastCore.entry.requiresNodeNetworkCompatibility === true;
14
+ // The lightweight secrets route still makes vault API requests.
15
+ if (parseSecretsEnvFastRequest(argv))
16
+ return true;
17
+ return true;
18
+ }
19
+ if (shouldConfigureNodeNetworkCompatibility(process.argv)) {
20
+ const { configureNodeNetworkCompatibilityOnce } = await import("./node-network-compat-core.js");
21
+ configureNodeNetworkCompatibilityOnce();
22
+ }
23
+ //# sourceMappingURL=node-network-compat-fast.js.map
@@ -1,19 +1,3 @@
1
- /**
2
- * Node network defaults for HQ CLI entry points.
3
- *
4
- * The compatibility timeout remains the default for HQ CLI entry points.
5
- *
6
- * Keep this module safe to import immediately after the Node version preflight.
7
- */
8
- export declare const HQ_NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS = 1000;
9
- export interface NodeNetworkCompatibilityOptions {
10
- nodeOptions?: string;
11
- execArgv?: readonly string[];
12
- /**
13
- * Inject the net timeout setter (tests). Pass null to simulate a runtime
14
- * that lacks setDefaultAutoSelectFamilyAttemptTimeout.
15
- */
16
- setAttemptTimeout?: ((milliseconds: number) => void) | null;
17
- }
18
- export declare function configureNodeNetworkCompatibility(options?: NodeNetworkCompatibilityOptions): boolean;
1
+ /** Unconditional compatibility setup for the full CLI command graph. */
2
+ export { configureNodeNetworkCompatibility, HQ_NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS, type NodeNetworkCompatibilityOptions, } from "./node-network-compat-core.js";
19
3
  //# sourceMappingURL=node-network-compat.d.ts.map
@@ -1,40 +1,5 @@
1
- /**
2
- * Node network defaults for HQ CLI entry points.
3
- *
4
- * The compatibility timeout remains the default for HQ CLI entry points.
5
- *
6
- * Keep this module safe to import immediately after the Node version preflight.
7
- */
8
- import * as nodeNet from "node:net";
9
- export const HQ_NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS = 1000;
10
- const ATTEMPT_TIMEOUT_OPTION = "--network-family-autoselection-attempt-timeout";
11
- function hasExplicitAttemptTimeout(nodeOptions, execArgv) {
12
- const optionPattern = new RegExp("(?:^|\\s)" + ATTEMPT_TIMEOUT_OPTION + "(?:=|\\s|$)");
13
- return (optionPattern.test(nodeOptions) ||
14
- execArgv.some((argument) => argument === ATTEMPT_TIMEOUT_OPTION ||
15
- argument.startsWith(ATTEMPT_TIMEOUT_OPTION + "=")));
16
- }
17
- function resolveSetAttemptTimeout(override) {
18
- if (override === null)
19
- return undefined;
20
- if (override)
21
- return override;
22
- const fn = nodeNet.setDefaultAutoSelectFamilyAttemptTimeout;
23
- return typeof fn === "function" ? fn.bind(nodeNet) : undefined;
24
- }
25
- export function configureNodeNetworkCompatibility(options = {}) {
26
- const nodeOptions = options.nodeOptions ?? process.env.NODE_OPTIONS ?? "";
27
- const execArgv = options.execArgv ?? process.execArgv;
28
- if (hasExplicitAttemptTimeout(nodeOptions, execArgv)) {
29
- return false;
30
- }
31
- const setAttemptTimeout = resolveSetAttemptTimeout(options.setAttemptTimeout);
32
- if (!setAttemptTimeout) {
33
- // Runtime lacks the API; the Node preflight should already have exited.
34
- return false;
35
- }
36
- setAttemptTimeout(HQ_NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS);
37
- return true;
38
- }
39
- configureNodeNetworkCompatibility();
1
+ /** Unconditional compatibility setup for the full CLI command graph. */
2
+ import { configureNodeNetworkCompatibilityOnce } from "./node-network-compat-core.js";
3
+ export { configureNodeNetworkCompatibility, HQ_NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS, } from "./node-network-compat-core.js";
4
+ configureNodeNetworkCompatibilityOnce();
40
5
  //# sourceMappingURL=node-network-compat.js.map
@@ -23,6 +23,12 @@ export interface NodeVersionCheck {
23
23
  major: number;
24
24
  message?: string;
25
25
  }
26
+ export interface NodePreflightOptions {
27
+ versionString?: string;
28
+ skip?: string;
29
+ stderr?: Pick<NodeJS.WriteStream, "write">;
30
+ exit?: (code: number) => void;
31
+ }
26
32
  /**
27
33
  * Pure check: is the given Node version string (e.g. "18.19.0") supported?
28
34
  * Defaults to the running runtime's version. An unparseable version is treated
@@ -35,5 +41,5 @@ export declare function checkNodeVersion(versionString?: string): NodeVersionChe
35
41
  * `HQ_SKIP_NODE_PREFLIGHT=1` to bypass (used by the test runner, which already
36
42
  * runs on a supported Node).
37
43
  */
38
- export declare function enforceNodeVersion(): void;
44
+ export declare function enforceNodeVersion(options?: NodePreflightOptions): void;
39
45
  //# sourceMappingURL=node-preflight.d.ts.map
@@ -39,13 +39,13 @@ export function checkNodeVersion(versionString = process.versions.node) {
39
39
  * `HQ_SKIP_NODE_PREFLIGHT=1` to bypass (used by the test runner, which already
40
40
  * runs on a supported Node).
41
41
  */
42
- export function enforceNodeVersion() {
43
- if (process.env.HQ_SKIP_NODE_PREFLIGHT)
42
+ export function enforceNodeVersion(options = {}) {
43
+ if (options.skip ?? process.env.HQ_SKIP_NODE_PREFLIGHT)
44
44
  return;
45
- const result = checkNodeVersion();
45
+ const result = checkNodeVersion(options.versionString ?? process.versions.node);
46
46
  if (!result.ok && result.message) {
47
- process.stderr.write(`${result.message}\n`);
48
- process.exit(1);
47
+ (options.stderr ?? process.stderr).write(`${result.message}\n`);
48
+ (options.exit ?? process.exit)(1);
49
49
  }
50
50
  }
51
51
  enforceNodeVersion();
@@ -0,0 +1,10 @@
1
+ export declare function readHqVersion(hqRoot: string): string | null;
2
+ export interface CatalogEntry {
3
+ source: string;
4
+ description?: string;
5
+ conditional?: string;
6
+ auto_install?: boolean;
7
+ }
8
+ /** Read recommended_packages from core.yaml (the curated content-pack catalog). */
9
+ export declare function readRecommendedPackages(hqRoot: string): CatalogEntry[];
10
+ //# sourceMappingURL=hq-core-yaml.d.ts.map
@@ -0,0 +1,40 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as yaml from "js-yaml";
4
+ /** Locate core.yaml: v14+ nests it under core/, older layouts at the root. */
5
+ function coreYamlPath(hqRoot) {
6
+ const nested = path.join(hqRoot, "core", "core.yaml");
7
+ if (fs.existsSync(nested))
8
+ return nested;
9
+ const flat = path.join(hqRoot, "core.yaml");
10
+ if (fs.existsSync(flat))
11
+ return flat;
12
+ return null;
13
+ }
14
+ export function readHqVersion(hqRoot) {
15
+ const filePath = coreYamlPath(hqRoot);
16
+ if (!filePath)
17
+ return null;
18
+ try {
19
+ const config = yaml.load(fs.readFileSync(filePath, "utf-8"));
20
+ return config?.hqVersion ?? null;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ /** Read recommended_packages from core.yaml (the curated content-pack catalog). */
27
+ export function readRecommendedPackages(hqRoot) {
28
+ const filePath = coreYamlPath(hqRoot);
29
+ if (!filePath)
30
+ return [];
31
+ try {
32
+ const config = yaml.load(fs.readFileSync(filePath, "utf-8"));
33
+ const list = config?.recommended_packages;
34
+ return Array.isArray(list) ? list : [];
35
+ }
36
+ catch {
37
+ return [];
38
+ }
39
+ }
40
+ //# sourceMappingURL=hq-core-yaml.js.map
@@ -39,6 +39,8 @@
39
39
  import type { PackManifest, PackContributeKey } from '../types.js';
40
40
  import { CONTRIBUTION_TABLE, type WireMode } from './contribution-table.js';
41
41
  import { type UnregisterServerResult, type SafeWriteEnv, type AcquireLockOptions } from '../commands/mcp-registration.js';
42
+ export { readHqVersion, readRecommendedPackages } from './hq-core-yaml.js';
43
+ export type { CatalogEntry } from './hq-core-yaml.js';
42
44
  export { CONTRIBUTION_TABLE };
43
45
  export type { WireMode };
44
46
  /**
@@ -163,13 +165,4 @@ export interface UnwirePackMcpOptions {
163
165
  * @param contributes the pack's `contributes` block (its `mcp` list drives removal)
164
166
  */
165
167
  export declare function unwirePackMcp(packName: string, contributes: Partial<Record<PackContributeKey, string[]>>, options?: UnwirePackMcpOptions): UnwireMcpResult;
166
- export declare function readHqVersion(hqRoot: string): string | null;
167
- export interface CatalogEntry {
168
- source: string;
169
- description?: string;
170
- conditional?: string;
171
- auto_install?: boolean;
172
- }
173
- /** Read `recommended_packages` from core.yaml (the curated content-pack catalog). */
174
- export declare function readRecommendedPackages(hqRoot: string): CatalogEntry[];
175
168
  //# sourceMappingURL=pack-contributions.d.ts.map
@@ -41,6 +41,7 @@ import * as path from 'path';
41
41
  import * as yaml from 'js-yaml';
42
42
  import { CONTRIBUTION_TABLE, payloadFor } from './contribution-table.js';
43
43
  import { unregisterMcpServers, } from '../commands/mcp-registration.js';
44
+ export { readHqVersion, readRecommendedPackages } from './hq-core-yaml.js';
44
45
  // NOTE on dependency direction: this module imports from ../commands/mcp-registration.js
45
46
  // to un-register `wire:merge` (mcp) servers on uninstall. mcp-registration.ts imports
46
47
  // ONLY fs/os/path/smol-toml (it does NOT import from pack-contributions.ts), so there
@@ -343,43 +344,4 @@ export function unwirePackMcp(packName, contributes, options) {
343
344
  }
344
345
  return { servers };
345
346
  }
346
- // ---------------------------------------------------------------------------
347
- // Host introspection: hqVersion + recommended_packages catalog
348
- // ---------------------------------------------------------------------------
349
- /** Locate core.yaml -- v14+ nests it under `core/`, older layouts at the root. */
350
- function coreYamlPath(hqRoot) {
351
- const nested = path.join(hqRoot, 'core', 'core.yaml');
352
- if (fs.existsSync(nested))
353
- return nested;
354
- const flat = path.join(hqRoot, 'core.yaml');
355
- if (fs.existsSync(flat))
356
- return flat;
357
- return null;
358
- }
359
- export function readHqVersion(hqRoot) {
360
- const p = coreYamlPath(hqRoot);
361
- if (!p)
362
- return null;
363
- try {
364
- const c = yaml.load(fs.readFileSync(p, 'utf-8'));
365
- return c?.hqVersion ?? null;
366
- }
367
- catch {
368
- return null;
369
- }
370
- }
371
- /** Read `recommended_packages` from core.yaml (the curated content-pack catalog). */
372
- export function readRecommendedPackages(hqRoot) {
373
- const p = coreYamlPath(hqRoot);
374
- if (!p)
375
- return [];
376
- try {
377
- const c = yaml.load(fs.readFileSync(p, 'utf-8'));
378
- const list = c?.recommended_packages;
379
- return Array.isArray(list) ? list : [];
380
- }
381
- catch {
382
- return [];
383
- }
384
- }
385
347
  //# sourceMappingURL=pack-contributions.js.map
@@ -6,6 +6,8 @@ function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandMo
6
6
  const commands = `${path.join(dist, "commands")}${path.sep}`;
7
7
  const flagRegistry = path.join(dist, "lib", "flag-registry.js");
8
8
  const coreCommand = path.join(dist, "commands", "core.js");
9
+ const fullNetworkCompat = path.join(dist, "node-network-compat.js");
10
+ const sentryEntrypoint = path.join(dist, "sentry.js");
9
11
  const allowedCommands = new Set(allowedCommandModules.map((module) => path.resolve(module)));
10
12
  return [...new Set(loadedModules.map((loaded) => {
11
13
  if (loaded.startsWith("file:")) {
@@ -22,7 +24,9 @@ function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandMo
22
24
  path.resolve(loaded).startsWith(commands) ||
23
25
  path.resolve(loaded) === flagRegistry ||
24
26
  path.resolve(loaded) === coreCommand ||
25
- path.resolve(loaded).includes(`${path.sep}node_modules${path.sep}@sentry${path.sep}node${path.sep}`))
27
+ path.resolve(loaded) === fullNetworkCompat ||
28
+ path.resolve(loaded) === sentryEntrypoint ||
29
+ path.resolve(loaded).includes(`${path.sep}node_modules${path.sep}@sentry${path.sep}`))
26
30
  .filter((loaded) => !allowedCommands.has(path.resolve(loaded)) ||
27
31
  path.resolve(loaded) === main || path.resolve(loaded) === coreCommand)
28
32
  .sort();
@@ -12,7 +12,7 @@ import semver from "semver";
12
12
  import { CLI_VERSION } from "../cli-version.js";
13
13
  import { wantsJson } from "../version-request.js";
14
14
  import { findHqRoot } from "./manifest.js";
15
- import { readHqVersion } from "./pack-contributions.js";
15
+ import { readHqVersion } from "./hq-core-yaml.js";
16
16
  export const HQ_CORE_LATEST_URL = "https://api.github.com/repos/indigoai-us/hq-core/releases/latest";
17
17
  export const LATEST_FETCH_TIMEOUT_MS = 3_000;
18
18
  export function normalizeHqCoreTag(tag) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.311.1",
3
+ "version": "5.311.2",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {