@indigoai-us/hq-cli 5.253.0 → 5.255.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.255.0] — 2026-09-28
6
+
7
+ ### Changed
8
+
9
+ - Internal: moved watch, monitor-check, and wait handlers to src/commands/lanes-watch.ts; behavior is unchanged.
10
+
11
+ ## [5.254.0] — 2026-09-28
12
+
13
+ ### Added
14
+
15
+ - The `lanes.manager-status-gate` flag checks a manager lane's declared
16
+ status file after each provider turn. It queues at most one corrective
17
+ resume if the file was not updated. An invalid or out-of-boundary mapping
18
+ disables the gate for that lane and lets the lane start.
19
+
20
+ ### Fixed
21
+
22
+ - The end-of-session work report is no longer dropped when the background
23
+ transcript watcher polls late or adopts a session after a restart. A
24
+ synthesized `turn_end` now carries the transcript time, capped at now, and
25
+ is never earlier than the last event sent for the session.
26
+ - `hq db sql --remote` is hidden because remote SQL is not available in this
27
+ CLI build. Existing scripts get a clear error, and local SQL remains
28
+ available without `--remote`.
29
+
5
30
  ## [5.253.0] — 2026-09-27
6
31
 
7
32
  ### Added
@@ -11,6 +36,7 @@
11
36
  not own with a plain message. `--as` is unchanged and cannot be combined with `--handle`.
12
37
 
13
38
  ## [5.252.0] — 2026-09-27
39
+
14
40
  ### Added
15
41
 
16
42
  - `hq report publish` takes a snapshot of the finished page and publishes it as the report's preview image, using a browser already installed on the computer. A page that names its own preview image keeps it. If no browser is found the report publishes without a preview.
@@ -1270,7 +1270,7 @@ export declare const COMMAND_CATALOG: readonly [{
1270
1270
  readonly subcommands: readonly [];
1271
1271
  }, {
1272
1272
  readonly name: "sql";
1273
- readonly description: "Run SQL against the company vault DB (local default; --remote for secrets-injected remote; read-only by default)";
1273
+ readonly description: "Run SQL against the company vault DB (local; read-only by default)";
1274
1274
  readonly aliases: readonly [];
1275
1275
  readonly hidden: false;
1276
1276
  readonly usage: "[options] [sql...]";
@@ -1287,7 +1287,7 @@ export declare const COMMAND_CATALOG: readonly [{
1287
1287
  readonly description: "Allow write/DDL statements (discouraged for ad-hoc DDL)";
1288
1288
  }, {
1289
1289
  readonly flags: "--remote";
1290
- readonly description: "Use remote tier via vault-bound credentials (not auto-replicated with local)";
1290
+ readonly description: "Remote SQL is not available in this CLI build yet; local queries work without --remote";
1291
1291
  }, {
1292
1292
  readonly flags: "--db-path <path>";
1293
1293
  readonly description: "Dangerous: open a non-canonical DB path (denied unless --allow-cross-company-path)";
@@ -1619,7 +1619,7 @@ export const COMMAND_CATALOG = [
1619
1619
  },
1620
1620
  {
1621
1621
  "name": "sql",
1622
- "description": "Run SQL against the company vault DB (local default; --remote for secrets-injected remote; read-only by default)",
1622
+ "description": "Run SQL against the company vault DB (local; read-only by default)",
1623
1623
  "aliases": [],
1624
1624
  "hidden": false,
1625
1625
  "usage": "[options] [sql...]",
@@ -1641,7 +1641,7 @@ export const COMMAND_CATALOG = [
1641
1641
  },
1642
1642
  {
1643
1643
  "flags": "--remote",
1644
- "description": "Use remote tier via vault-bound credentials (not auto-replicated with local)"
1644
+ "description": "Remote SQL is not available in this CLI build yet; local queries work without --remote"
1645
1645
  },
1646
1646
  {
1647
1647
  "flags": "--db-path <path>",
@@ -4,9 +4,9 @@
4
4
  * Default is read-only. Prefer `hq db migrate` for schema changes.
5
5
  * Never prints remote connection strings.
6
6
  */
7
+ import { Option } from "commander";
7
8
  import chalk from "chalk";
8
9
  import fs from "node:fs";
9
- import { runRemoteSql } from "../lib/db/remote-sql.js";
10
10
  import { formatSqlResult, runLocalSql } from "../lib/db/sql.js";
11
11
  function readSqlArg(sqlArgs, stdinFlag) {
12
12
  if (stdinFlag || sqlArgs.length === 0) {
@@ -19,10 +19,10 @@ function readSqlArg(sqlArgs, stdinFlag) {
19
19
  }
20
20
  export function registerDbSqlCommand(db) {
21
21
  db.command("sql")
22
- .description("Run SQL against the company vault DB (local default; --remote for secrets-injected remote; read-only by default)")
22
+ .description("Run SQL against the company vault DB (local; read-only by default)")
23
23
  .requiredOption("--company <slug>", "Company slug (tenant scope; required)")
24
24
  .option("--write", "Allow write/DDL statements (discouraged for ad-hoc DDL)", false)
25
- .option("--remote", "Use remote tier via vault-bound credentials (not auto-replicated with local)", false)
25
+ .addOption(new Option("--remote", "Remote SQL is not available in this CLI build yet; local queries work without --remote").hideHelp())
26
26
  .option("--db-path <path>", "Dangerous: open a non-canonical DB path (denied unless --allow-cross-company-path)")
27
27
  .option("--allow-cross-company-path", "Dangerous: permit --db-path outside this company's canonical local DB", false)
28
28
  .option("--format <fmt>", "Output format: jsonl | table", "jsonl")
@@ -37,27 +37,13 @@ export function registerDbSqlCommand(db) {
37
37
  process.exitCode = 1;
38
38
  return;
39
39
  }
40
- const sql = readSqlArg(sqlParts, !!opts.stdin).trim();
41
- const format = opts.format === "table" ? "table" : "jsonl";
42
40
  if (opts.remote) {
43
- // Local and remote are not auto-replicated (v1 limitation).
44
- const remoteResult = await runRemoteSql({
45
- company,
46
- sql,
47
- getConnectionConfig: async () => {
48
- throw new Error("no remote binding");
49
- },
50
- });
51
- console.log(formatSqlResult({
52
- columns: remoteResult.rows[0]
53
- ? Object.keys(remoteResult.rows[0])
54
- : [],
55
- rows: remoteResult.rows,
56
- changes: 0,
57
- readonly: true,
58
- }, format));
41
+ console.error(chalk.red("Error: Remote SQL is not available in this CLI build yet. Local queries work without --remote."));
42
+ process.exitCode = 1;
59
43
  return;
60
44
  }
45
+ const sql = readSqlArg(sqlParts, !!opts.stdin).trim();
46
+ const format = opts.format === "table" ? "table" : "jsonl";
61
47
  const result = runLocalSql({
62
48
  company,
63
49
  sql,
@@ -9,6 +9,7 @@ import { createLocalTrackerStore } from "../lib/lanes/tracker-store.js";
9
9
  import { sessionLiveness } from "../lib/lanes/liveness.js";
10
10
  import { findOrphanLanes, parseOrphanMinAge } from "../lib/lanes/orphans.js";
11
11
  import { assertNoSecretText } from "../lib/lanes/text-scan.js";
12
+ import { checkCompanyPath } from "../lib/lanes/checked-company-path.js";
12
13
  import { LANES_TRACKER_FLAG } from "./lanes-tracker.js";
13
14
  function fail(error, message, details = {}) {
14
15
  throw new LanesError(error, message, details);
@@ -56,37 +57,6 @@ function readJsonObject(file, missing = "error") {
56
57
  return fail("tracker_checks_input_invalid", "Checks input must be a JSON object", { path: file });
57
58
  return parsed;
58
59
  }
59
- function isWithin(root, candidate) {
60
- const relative = path.relative(root, candidate);
61
- return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
62
- }
63
- function checkCompanyPath(root, company, file, detailsPath) {
64
- if (path.isAbsolute(file))
65
- return fail("tracker_checks_mapping_invalid", "Mapped path must be relative to the HQ root", { path: detailsPath });
66
- const absolute = path.resolve(root, file);
67
- const rootReal = fs.realpathSync(root);
68
- if (!isWithin(rootReal, absolute))
69
- return fail("tracker_checks_mapping_invalid", "Mapped path escapes the HQ root", { path: detailsPath });
70
- const relative = path.relative(rootReal, absolute).split(path.sep);
71
- if (relative[0] === "companies" && relative[1] !== company) {
72
- return fail("tracker_checks_mapping_invalid", "Mapped path crosses the active company boundary", { path: detailsPath });
73
- }
74
- try {
75
- const real = fs.realpathSync(absolute);
76
- if (!isWithin(rootReal, real))
77
- return fail("tracker_checks_mapping_invalid", "Mapped path resolves outside the HQ root", { path: detailsPath });
78
- const realRelative = path.relative(rootReal, real).split(path.sep);
79
- if (realRelative[0] === "companies" && realRelative[1] !== company) {
80
- return fail("tracker_checks_mapping_invalid", "Mapped path resolves into another company", { path: detailsPath });
81
- }
82
- }
83
- catch (err) {
84
- if (err.code !== "ENOENT") {
85
- return fail("tracker_checks_mapping_unreadable", `Mapped path could not be checked: ${err instanceof Error ? err.message : String(err)}`, { path: absolute });
86
- }
87
- }
88
- return absolute;
89
- }
90
60
  function projectFromPrdReference(root, company, raw, registryPath) {
91
61
  if (typeof raw !== "string" || raw.trim() === "") {
92
62
  return fail("tracker_checks_mapping_invalid", "Team mapping needs a PRD path", { path: registryPath });
@@ -10,6 +10,7 @@ import { isPidAlive, runWatcherLoop, WATCHER_OUT_MAX_BYTES, watcherOutLogPath, w
10
10
  import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
11
11
  import { laneRunDir, probePidRecord, readPidRecord, readProcStarttime, spawnLaneWorker } from "../lib/lanes/spawn.js";
12
12
  import { keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
13
+ import { managerStatusGateEnabledForLane } from "../lib/lanes/manager-status-gate.js";
13
14
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
14
15
  import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
15
16
  export function createLaneMessageHandlers(deps) {
@@ -285,6 +286,7 @@ export function createLaneMessageHandlers(deps) {
285
286
  markAccepted();
286
287
  }
287
288
  else {
289
+ const managerStatusGateEnabled = await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env });
288
290
  const dispatched = await dispatchLaneDelivery(lane, {
289
291
  dropbox: () => {
290
292
  // Live (or about-to-be-live) process: the PreToolUse drain delivers it.
@@ -341,6 +343,7 @@ export function createLaneMessageHandlers(deps) {
341
343
  }
342
344
  const started = spawnLaneWorker(hqRoot, lane, {
343
345
  env: ctx.env,
346
+ managerStatusGateEnabled,
344
347
  modelDeps: {
345
348
  readFileSync: ctx.readFileSync,
346
349
  probe: ctx.probeProvider,
@@ -5,6 +5,7 @@ import { parseCliEffort } from "../lib/lanes/effort.js";
5
5
  import { parseOutpostHireGrantContext } from "../lib/lanes/hire-grant-dispatch.js";
6
6
  import { assertNoSecretText } from "../lib/lanes/text-scan.js";
7
7
  import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
8
+ import { managerStatusGateEnabledForLane } from "../lib/lanes/manager-status-gate.js";
8
9
  import { spawnLaneWorker } from "../lib/lanes/spawn.js";
9
10
  import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, hasKeepAliveProgress, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
10
11
  export function createLaneResumeHandlers(deps) {
@@ -281,6 +282,7 @@ export function createLaneResumeHandlers(deps) {
281
282
  resumed = lane;
282
283
  }
283
284
  else {
285
+ const managerStatusGateEnabled = await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env });
284
286
  resumed = await withLaneAdmissionLock(hqRoot, lane.provider, async () => {
285
287
  const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
286
288
  if (admission.action === "refuse" && admission.error)
@@ -327,6 +329,7 @@ export function createLaneResumeHandlers(deps) {
327
329
  }
328
330
  return spawnLaneWorker(hqRoot, lane, {
329
331
  env: ctx.env,
332
+ managerStatusGateEnabled,
330
333
  modelDeps: {
331
334
  readFileSync: ctx.readFileSync,
332
335
  probe: ctx.probeProvider,
@@ -7,6 +7,7 @@ import type { WatcherLoopDeps } from "../lib/lanes/watcher.js";
7
7
  import type { RunCompactionFn } from "../lib/lanes/compaction.js";
8
8
  import type { LaneMesh, LaneMeshEvent } from "../lib/lanes/mesh-events.js";
9
9
  import type { BalanceHostGap, BalanceHostSnapshot } from "../lib/lanes/claude-balance.js";
10
+ import type { HostCapacitySnapshot } from "../lib/lanes/host-capacity.js";
10
11
  import { type ResolvedEffort } from "../lib/lanes/effort.js";
11
12
  import type { SessionCopyResult } from "../lib/lanes/move-account.js";
12
13
  import type { WorkerProfile } from "../lib/lanes/profile.js";
@@ -223,6 +224,32 @@ export interface Ctx {
223
224
  laneTrackerEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
224
225
  trackerActor: () => string | null;
225
226
  }
227
+ export interface LaneWaitCommandOptions {
228
+ any?: string[];
229
+ for?: string[];
230
+ timeout?: string;
231
+ json?: boolean;
232
+ }
233
+ export interface MonitorCheckOptions {
234
+ session?: string;
235
+ engine?: string;
236
+ json?: boolean;
237
+ reminder?: boolean;
238
+ }
239
+ export interface LaneWatchOptions {
240
+ intervalMs?: string;
241
+ json?: boolean;
242
+ }
243
+ export interface LaneWatchHandlerDeps {
244
+ laneHostFromCtx(ctx: Ctx): LaneRecord["host"];
245
+ loadAllLanes(hqRoot: string): LaneRecord[];
246
+ loadClaudeBalanceHostSnapshots(ctx: Ctx, hqRoot: string, config: LanesConfig): Promise<{
247
+ snapshots: BalanceHostSnapshot[];
248
+ unavailableHosts: BalanceHostGap[];
249
+ }>;
250
+ printJson(value: unknown): void;
251
+ readOutpostCapacity(ctx: Ctx, outpostId: string): Promise<HostCapacitySnapshot>;
252
+ }
226
253
  export interface PrimaryOwnedOutpostQuestion {
227
254
  question: QuestionRecord;
228
255
  lane: LaneRecord;
@@ -0,0 +1,7 @@
1
+ import type { Ctx, MonitorCheckOptions, LaneWaitCommandOptions, LaneWatchHandlerDeps, LaneWatchOptions } from "./lanes-shared.js";
2
+ export declare function createLaneWatchHandlers(deps: LaneWatchHandlerDeps): {
3
+ runWatch: (ctx: Ctx, laneIds: string[], opts: LaneWatchOptions) => Promise<number>;
4
+ runMonitorCheck: (ctx: Ctx, opts: MonitorCheckOptions) => Promise<number>;
5
+ runWait: (ctx: Ctx, opts: LaneWaitCommandOptions) => Promise<number>;
6
+ };
7
+ //# sourceMappingURL=lanes-watch.d.ts.map
@@ -0,0 +1,215 @@
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 } from "../lib/lanes/keep-alive.js";
3
+ import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
4
+ import { isLanesError, LanesError } from "../lib/lanes/errors.js";
5
+ import { LANE_WAIT_INVALID_INPUT_EXIT_CODE, LANE_WAIT_TIMEOUT_EXIT_CODE, runLaneWait, } from "../lib/lanes/wait.js";
6
+ const MONITOR_ACTIVE_LANE_STATES = new Set([
7
+ "running",
8
+ "queued",
9
+ "awaiting_input",
10
+ "blocked",
11
+ ]);
12
+ function resolveMonitorEngine(requested, env) {
13
+ const value = requested?.trim().toLowerCase() ||
14
+ env.HQ_CONDUCT_ENGINE?.trim().toLowerCase() ||
15
+ "claude";
16
+ if (!MONITOR_ENGINES.includes(value)) {
17
+ throw new LanesError("monitor_invalid_engine", `Unknown monitor-check engine ${JSON.stringify(value)}. Accepted: ${MONITOR_ENGINES.join(", ")}`, { accepted: [...MONITOR_ENGINES], value });
18
+ }
19
+ return value;
20
+ }
21
+ function monitorCallsForLaneIds(laneIds) {
22
+ const calls = [];
23
+ for (let offset = 0; offset < laneIds.length; offset += MAX_WATCHED_LANES) {
24
+ const chunk = laneIds.slice(offset, offset + MAX_WATCHED_LANES);
25
+ calls.push({
26
+ command: `hq lanes watch ${chunk.join(" ")}`,
27
+ timeout_ms: MONITOR_MAX_TIMEOUT_MS,
28
+ persistent: true,
29
+ });
30
+ }
31
+ return calls;
32
+ }
33
+ function laneWaitHumanLine(result) {
34
+ const details = Object.entries(result.change)
35
+ .filter(([key]) => key !== "type")
36
+ .map(([key, value]) => `${key}=${String(value)}`)
37
+ .join(" ");
38
+ return `lane=${result.lane_id} change=${result.change.type}${details ? ` ${details}` : ""} at=${result.at}`;
39
+ }
40
+ export function createLaneWatchHandlers(deps) {
41
+ const { laneHostFromCtx, loadAllLanes, loadClaudeBalanceHostSnapshots, printJson, readOutpostCapacity, } = deps;
42
+ async function runWatch(ctx, laneIds, opts) {
43
+ const rawInterval = opts.intervalMs === undefined ? undefined : Number(opts.intervalMs);
44
+ const hqRoot = ctx.hqRoot();
45
+ await runLaneWatch(hqRoot, laneIds, {
46
+ intervalMs: rawInterval,
47
+ sleep: ctx.sleep,
48
+ keepAliveEnabled: (lane) => readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, {
49
+ company: lane.company,
50
+ env: ctx.env,
51
+ }),
52
+ readAccountStatuses: async (lanes) => {
53
+ if (lanes.length === 0)
54
+ return [];
55
+ const config = loadLanesConfig(hqRoot);
56
+ const currentHost = laneHostFromCtx(ctx);
57
+ const currentHostname = ctx.hostname().trim();
58
+ const isCurrentHost = (host) => (host.kind === currentHost.kind && host.id === currentHost.id) ||
59
+ (host.kind === "local" && host.id === currentHostname);
60
+ const remoteOutpostIds = [...new Set(lanes
61
+ .filter((lane) => lane.host.kind === "outpost" && !isCurrentHost(lane.host))
62
+ .map((lane) => lane.host.id))];
63
+ const remoteOutposts = await Promise.all(remoteOutpostIds.map(async (id) => [id, await readOutpostCapacity(ctx, id)]));
64
+ const outpostSnapshots = new Map(remoteOutposts);
65
+ const remoteLocalLanes = lanes.filter((lane) => lane.host.kind === "local" && !isCurrentHost(lane.host));
66
+ const remoteLocalHosts = remoteLocalLanes.length > 0
67
+ ? await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config)
68
+ : undefined;
69
+ const localStatusCache = new Map();
70
+ return lanes.map((lane) => {
71
+ if (config.providers[lane.provider].accounts[lane.account] === undefined) {
72
+ throw new LanesError("watch_account_unconfigured", `Lane ${lane.lane_id} records unconfigured ${lane.provider} account ${lane.account}`, { lane_id: lane.lane_id, provider: lane.provider, account: lane.account, host: lane.host });
73
+ }
74
+ let loggedIn;
75
+ if (isCurrentHost(lane.host)) {
76
+ const key = JSON.stringify([lane.provider, lane.account, currentHost.kind, currentHost.id]);
77
+ if (!localStatusCache.has(key)) {
78
+ localStatusCache.set(key, accountStatus(lane.provider, lane.account, config, {
79
+ existsSync: ctx.existsSync,
80
+ probe: ctx.probeProvider,
81
+ env: ctx.env,
82
+ hostname: currentHostname,
83
+ }).logged_in);
84
+ }
85
+ loggedIn = localStatusCache.get(key);
86
+ }
87
+ else if (lane.host.kind === "outpost") {
88
+ loggedIn = outpostSnapshots.get(lane.host.id)?.providers[lane.provider].accounts[lane.account]?.logged_in;
89
+ }
90
+ else {
91
+ const snapshot = remoteLocalHosts?.snapshots.find((candidate) => candidate.capacity.host.kind === lane.host.kind &&
92
+ candidate.capacity.host.id === lane.host.id);
93
+ if (!snapshot) {
94
+ const gap = remoteLocalHosts?.unavailableHosts.find((candidate) => candidate.host.kind === lane.host.kind && candidate.host.id === lane.host.id);
95
+ throw new LanesError("watch_account_host_unavailable", `Cannot check ${lane.provider} account ${lane.account} on ${lane.host.kind}:${lane.host.id}${gap ? `: ${gap.reason}` : ""}`, { lane_id: lane.lane_id, provider: lane.provider, account: lane.account, host: lane.host });
96
+ }
97
+ loggedIn = snapshot.capacity.providers[lane.provider].accounts[lane.account]?.logged_in;
98
+ }
99
+ if (loggedIn === undefined) {
100
+ throw new LanesError("watch_account_unconfigured", `Lane ${lane.lane_id} records unconfigured ${lane.provider} account ${lane.account} on ${lane.host.kind}:${lane.host.id}`, { lane_id: lane.lane_id, provider: lane.provider, account: lane.account, host: lane.host });
101
+ }
102
+ return {
103
+ lane_id: lane.lane_id,
104
+ provider: lane.provider,
105
+ account: lane.account,
106
+ host: lane.host,
107
+ logged_in: loggedIn,
108
+ };
109
+ });
110
+ },
111
+ writeLine: (line) => console.log(opts.json ? JSON.stringify({ event: line }) : line),
112
+ writeAccountEvent: (event) => console.log(opts.json ? JSON.stringify(event) : `account_logged_out provider=${event.provider} account=${event.account} host=${event.host.kind}:${event.host.id} lane_ids=${event.lane_ids.join(",")}`),
113
+ writeError: (line) => process.stderr.write(`${line}\n`),
114
+ });
115
+ return 0;
116
+ }
117
+ async function runMonitorCheck(ctx, opts) {
118
+ if (opts.reminder) {
119
+ console.log(monitorReminderText());
120
+ return 0;
121
+ }
122
+ const sessionId = opts.session?.trim();
123
+ if (!sessionId) {
124
+ throw new LanesError("monitor_session_required", "hq lanes monitor-check requires --session <id> unless --reminder is used");
125
+ }
126
+ const engine = resolveMonitorEngine(opts.engine, ctx.env);
127
+ const activeLanes = loadAllLanes(ctx.hqRoot()).filter((lane) => lane.senior.kind === "session" &&
128
+ lane.senior.id === sessionId &&
129
+ MONITOR_ACTIVE_LANE_STATES.has(lane.state));
130
+ if (engine !== "claude") {
131
+ if (opts.json) {
132
+ printJson({
133
+ ok: true,
134
+ action: "monitor-check",
135
+ session_id: sessionId,
136
+ engine,
137
+ active_lane_ids: activeLanes.map((lane) => lane.lane_id),
138
+ covered_lane_ids: [],
139
+ uncovered_lane_ids: [],
140
+ skipped: "engine_has_no_claude_monitor",
141
+ });
142
+ }
143
+ return 0;
144
+ }
145
+ if (activeLanes.length === 0) {
146
+ if (opts.json) {
147
+ printJson({
148
+ ok: true,
149
+ action: "monitor-check",
150
+ session_id: sessionId,
151
+ engine,
152
+ active_lane_ids: [],
153
+ covered_lane_ids: [],
154
+ uncovered_lane_ids: [],
155
+ });
156
+ }
157
+ return 0;
158
+ }
159
+ const evidence = await ctx.monitorEvidence(sessionId);
160
+ if (evidence.sessionId !== sessionId) {
161
+ throw new LanesError("monitor_evidence_session_mismatch", `Monitor evidence was for ${JSON.stringify(evidence.sessionId)}, expected ${JSON.stringify(sessionId)}`);
162
+ }
163
+ const covered = coveredLaneIds(evidence, ctx.now());
164
+ const activeIds = activeLanes.map((lane) => lane.lane_id);
165
+ const uncovered = activeIds.filter((laneId) => !covered.has(laneId));
166
+ const monitorCalls = monitorCallsForLaneIds(uncovered);
167
+ if (opts.json) {
168
+ printJson({
169
+ ok: uncovered.length === 0,
170
+ action: "monitor-check",
171
+ session_id: sessionId,
172
+ engine,
173
+ active_lane_ids: activeIds,
174
+ covered_lane_ids: activeIds.filter((laneId) => covered.has(laneId)),
175
+ uncovered_lane_ids: uncovered,
176
+ monitor_call: monitorCalls.length === 1 ? monitorCalls[0] : null,
177
+ monitor_calls: monitorCalls,
178
+ });
179
+ }
180
+ else if (uncovered.length > 0) {
181
+ for (const laneId of uncovered)
182
+ console.log(`uncovered lane: ${laneId}`);
183
+ for (const monitorCall of monitorCalls) {
184
+ console.log(`Monitor(command=${JSON.stringify(monitorCall.command)}, timeout_ms=${monitorCall.timeout_ms}, persistent=true)`);
185
+ }
186
+ }
187
+ return uncovered.length > 0 ? MONITOR_UNCOVERED_EXIT_CODE : 0;
188
+ }
189
+ async function runWait(ctx, opts) {
190
+ const timeoutSeconds = opts.timeout === undefined ? Number.NaN : Number(opts.timeout);
191
+ try {
192
+ const result = await runLaneWait(ctx.hqRoot(), opts.any ?? [], opts.for ?? [], timeoutSeconds * 1000, { now: ctx.now, sleep: ctx.sleep });
193
+ if (result) {
194
+ console.log(opts.json ? JSON.stringify(result) : laneWaitHumanLine(result));
195
+ return 0;
196
+ }
197
+ console.log(opts.json
198
+ ? JSON.stringify({ ok: false, error: "wait_timeout" })
199
+ : "wait_timeout");
200
+ return LANE_WAIT_TIMEOUT_EXIT_CODE;
201
+ }
202
+ catch (err) {
203
+ if (!isLanesError(err) || err.error !== "wait_invalid_input")
204
+ throw err;
205
+ const output = { ok: false, error: err.error, message: err.message };
206
+ if (opts.json)
207
+ console.log(JSON.stringify(output));
208
+ else
209
+ process.stderr.write(`${err.message}\n`);
210
+ return LANE_WAIT_INVALID_INPUT_EXIT_CODE;
211
+ }
212
+ }
213
+ return { runWatch, runMonitorCheck, runWait };
214
+ }
215
+ //# sourceMappingURL=lanes-watch.js.map
@@ -58,7 +58,7 @@ export interface LanesCommandDeps {
58
58
  /** Test seam for worker PID identity checks. */
59
59
  readStarttime?: (pid: number) => number | null;
60
60
  /** Test seams for synchronous dead-worker classification and repair. */
61
- watcherLoopDeps?: Pick<WatcherLoopDeps, "sleep" | "spawnRepair">;
61
+ watcherLoopDeps?: Pick<WatcherLoopDeps, "sleep" | "spawnRepair" | "readManagerStatusGateFlag">;
62
62
  stopSleepMs?: (ms: number) => void;
63
63
  livenessDeps?: LivenessDeps;
64
64
  presenceConnected?: boolean;