@indigoai-us/hq-cli 5.202.1 → 5.204.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 (42) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +24 -0
  3. package/assets/lanes-hooks/hq-lanes-drain.mjs +36 -15
  4. package/dist/command-catalog.generated.d.ts +195 -0
  5. package/dist/command-catalog.generated.js +249 -0
  6. package/dist/command-registration-plan.d.ts +6 -0
  7. package/dist/command-registration-plan.js +1 -0
  8. package/dist/commands/lanes.d.ts +4 -0
  9. package/dist/commands/lanes.js +350 -108
  10. package/dist/commands/monitor.d.ts +58 -0
  11. package/dist/commands/monitor.js +965 -0
  12. package/dist/lib/lanes/account-home.d.ts +8 -0
  13. package/dist/lib/lanes/account-home.js +10 -0
  14. package/dist/lib/lanes/accounts.d.ts +4 -1
  15. package/dist/lib/lanes/accounts.js +15 -10
  16. package/dist/lib/lanes/claude-account-balance.d.ts +3 -0
  17. package/dist/lib/lanes/claude-account-balance.js +19 -4
  18. package/dist/lib/lanes/config.d.ts +2 -1
  19. package/dist/lib/lanes/config.js +55 -13
  20. package/dist/lib/lanes/delivery-core.d.ts +15 -0
  21. package/dist/lib/lanes/delivery-core.js +21 -0
  22. package/dist/lib/lanes/delivery.d.ts +21 -5
  23. package/dist/lib/lanes/delivery.js +71 -7
  24. package/dist/lib/lanes/dropbox.d.ts +13 -1
  25. package/dist/lib/lanes/dropbox.js +61 -5
  26. package/dist/lib/lanes/host-capacity.d.ts +4 -0
  27. package/dist/lib/lanes/host-capacity.js +8 -1
  28. package/dist/lib/lanes/index.d.ts +3 -1
  29. package/dist/lib/lanes/index.js +2 -1
  30. package/dist/lib/lanes/liveness.d.ts +1 -0
  31. package/dist/lib/lanes/liveness.js +3 -1
  32. package/dist/lib/lanes/move-account.js +1 -1
  33. package/dist/lib/lanes/reconcile.js +20 -9
  34. package/dist/lib/lanes/spawn.d.ts +7 -3
  35. package/dist/lib/lanes/spawn.js +71 -22
  36. package/dist/lib/lanes/types.d.ts +8 -0
  37. package/dist/lib/monitor.d.ts +153 -0
  38. package/dist/lib/monitor.js +519 -0
  39. package/dist/main.js +40 -28
  40. package/dist/utils/cli-network-lifecycle.d.ts +4 -0
  41. package/dist/utils/cli-network-lifecycle.js +11 -0
  42. package/package.json +1 -1
@@ -12,6 +12,7 @@ import * as fs from "node:fs";
12
12
  import * as path from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
14
  import { PROVIDER_HOME_ENV, accountStatus, isLoggedOut, resolveAccount, selectBalancedAccount, } from "./accounts.js";
15
+ import { effectiveAccountHome } from "./account-home.js";
15
16
  import { COMPACTION_PROMPT, compactionAfterUsage, compactionDue, compactLaneSession, } from "./compaction.js";
16
17
  import { installDrainHook } from "./hooks-install.js";
17
18
  import { loadLanesConfig, providerAutoCompactTokens, providerCompactAfterMs, } from "./config.js";
@@ -112,7 +113,7 @@ function accountModelForSpawn(provider, accountId, config, pinnedModel, profileM
112
113
  providerModel: config.providers[provider].model,
113
114
  });
114
115
  }
115
- function supportsSpawnModel(provider, accountId, config, pinnedModel, profileModel, modelDeps) {
116
+ function supportsSpawnModel(provider, accountId, config, pinnedModel, profileModel, modelDeps, hostname) {
116
117
  const account = config.providers[provider].accounts[accountId];
117
118
  if (account === undefined)
118
119
  return false;
@@ -120,7 +121,7 @@ function supportsSpawnModel(provider, accountId, config, pinnedModel, profileMod
120
121
  if (model === undefined)
121
122
  return true;
122
123
  try {
123
- validateLaneModel(provider, model, account.home, modelDeps);
124
+ validateLaneModel(provider, model, effectiveAccountHome(account, hostname).home, modelDeps);
124
125
  return true;
125
126
  }
126
127
  catch (err) {
@@ -129,7 +130,7 @@ function supportsSpawnModel(provider, accountId, config, pinnedModel, profileMod
129
130
  throw err;
130
131
  }
131
132
  }
132
- function spawnModelFailure(provider, accountId, config, pinnedModel, profileModel, profileId, modelDeps) {
133
+ function spawnModelFailure(provider, accountId, config, pinnedModel, profileModel, profileId, modelDeps, hostname) {
133
134
  const account = config.providers[provider].accounts[accountId];
134
135
  if (account === undefined)
135
136
  return new LanesError("unknown_account", `Unknown account ${accountId} for ${provider}`, { provider, account: accountId });
@@ -137,7 +138,7 @@ function spawnModelFailure(provider, accountId, config, pinnedModel, profileMode
137
138
  if (model === undefined)
138
139
  return null;
139
140
  try {
140
- validateLaneModel(provider, model, account.home, modelDeps);
141
+ validateLaneModel(provider, model, effectiveAccountHome(account, hostname).home, modelDeps);
141
142
  return null;
142
143
  }
143
144
  catch (err) {
@@ -155,17 +156,18 @@ function spawnModelFailure(provider, accountId, config, pinnedModel, profileMode
155
156
  }
156
157
  }
157
158
  function selectSpawnAccount(input) {
158
- const { hqRoot, lane, config, existsSync, accountDeps, modelDeps, pinnedModel, profileModel, currentModelValidated } = input;
159
+ const { hqRoot, lane, config, existsSync, accountDeps, modelDeps, pinnedModel, profileModel, currentModelValidated, hostname } = input;
159
160
  const context = {
160
161
  hqRoot,
161
162
  existsSync,
162
163
  accountDeps,
164
+ hostname,
163
165
  ...(pinnedModel ?? profileModel ? { model: pinnedModel ?? profileModel } : {}),
164
166
  };
165
167
  if (lane.account === undefined) {
166
168
  const modelSpecified = pinnedModel !== undefined || profileModel !== undefined;
167
169
  const allowedAccounts = modelSpecified
168
- ? Object.keys(config.providers[lane.provider].accounts).filter((id) => supportsSpawnModel(lane.provider, id, config, pinnedModel, profileModel, modelDeps))
170
+ ? Object.keys(config.providers[lane.provider].accounts).filter((id) => supportsSpawnModel(lane.provider, id, config, pinnedModel, profileModel, modelDeps, hostname))
169
171
  : undefined;
170
172
  return {
171
173
  account: selectBalancedAccount(lane.provider, config, {
@@ -188,6 +190,7 @@ function selectSpawnAccount(input) {
188
190
  else {
189
191
  const status = accountStatus(lane.provider, lane.account, config, {
190
192
  existsSync,
193
+ hostname,
191
194
  ...(accountDeps?.probe ? { probe: accountDeps.probe } : {}),
192
195
  ...(accountDeps?.env ? { env: accountDeps.env } : {}),
193
196
  });
@@ -200,7 +203,7 @@ function selectSpawnAccount(input) {
200
203
  resolveAccount(lane.provider, lane.account, config, context);
201
204
  const modelFailure = currentModelValidated
202
205
  ? null
203
- : spawnModelFailure(lane.provider, lane.account, config, pinnedModel, profileModel, lane.worker_profile, modelDeps);
206
+ : spawnModelFailure(lane.provider, lane.account, config, pinnedModel, profileModel, lane.worker_profile, modelDeps, hostname);
204
207
  if (modelFailure !== null) {
205
208
  reason = "model_change";
206
209
  refusal = modelFailure;
@@ -240,10 +243,11 @@ function selectSpawnAccount(input) {
240
243
  .filter(([id, account]) => id !== lane.account && account.enabled)
241
244
  .filter(([id]) => accountStatus(lane.provider, id, config, {
242
245
  existsSync,
246
+ hostname,
243
247
  ...(accountDeps?.probe ? { probe: accountDeps.probe } : {}),
244
248
  ...(accountDeps?.env ? { env: accountDeps.env } : {}),
245
249
  }).logged_in === true)
246
- .filter(([id]) => supportsSpawnModel(lane.provider, id, config, pinnedModel, profileModel, modelDeps))
250
+ .filter(([id]) => supportsSpawnModel(lane.provider, id, config, pinnedModel, profileModel, modelDeps, hostname))
247
251
  .map(([id]) => id);
248
252
  try {
249
253
  return {
@@ -345,6 +349,8 @@ export async function retryLaneOnAlternateAccount(hqRoot, lane, reason, writer =
345
349
  const config = loadLanesConfig(hqRoot);
346
350
  const existsSync = deps.existsSync ?? ((p) => fs.existsSync(p));
347
351
  const env = deps.env ?? process.env;
352
+ const admissionHost = deps.admissionHost ?? resolveAdmissionHost();
353
+ const hostname = deps.hostname?.trim() || admissionHost.hostname.trim() || resolveAdmissionHost().hostname;
348
354
  const modelDeps = {
349
355
  ...deps.modelDeps,
350
356
  env: deps.modelDeps?.env ?? env,
@@ -353,7 +359,6 @@ export async function retryLaneOnAlternateAccount(hqRoot, lane, reason, writer =
353
359
  probe: modelDeps.probe ?? defaultProbe,
354
360
  env,
355
361
  };
356
- const admissionHost = deps.admissionHost ?? resolveAdmissionHost();
357
362
  const profile = lane.worker_profile ? loadWorkerProfile(hqRoot, lane.worker_profile) : undefined;
358
363
  const profileModel = profile?.meta.provider === lane.provider ? profile.meta.model : undefined;
359
364
  const pinnedModel = lane.model_source === undefined || lane.model_source === "cli" ? lane.model : undefined;
@@ -362,15 +367,17 @@ export async function retryLaneOnAlternateAccount(hqRoot, lane, reason, writer =
362
367
  .filter(([id, account]) => id !== lane.account && !unavailableAccounts.has(id) && account.enabled)
363
368
  .filter(([id]) => accountStatus(lane.provider, id, config, {
364
369
  existsSync,
370
+ hostname,
365
371
  ...(accountDeps?.probe ? { probe: accountDeps.probe } : {}),
366
372
  ...(accountDeps?.env ? { env: accountDeps.env } : {}),
367
373
  }).logged_in === true)
368
- .filter(([id]) => supportsSpawnModel(lane.provider, id, config, pinnedModel, profileModel, modelDeps))
374
+ .filter(([id]) => supportsSpawnModel(lane.provider, id, config, pinnedModel, profileModel, modelDeps, hostname))
369
375
  .map(([id]) => id);
370
376
  const modelContext = {
371
377
  hqRoot,
372
378
  existsSync,
373
379
  accountDeps,
380
+ hostname,
374
381
  ...(pinnedModel ?? profileModel ? { model: pinnedModel ?? profileModel } : {}),
375
382
  allowedAccounts,
376
383
  };
@@ -392,7 +399,7 @@ export async function retryLaneOnAlternateAccount(hqRoot, lane, reason, writer =
392
399
  : undefined;
393
400
  if (source && sessionId) {
394
401
  copySessionFiles(planSessionCopy(lane.provider, {
395
- sourceHome: source.home,
402
+ sourceHome: lane.provider_home_at_spawn ?? effectiveAccountHome(source, hostname).home,
396
403
  targetHome: target.home,
397
404
  cwd: hqRoot,
398
405
  sessionId,
@@ -535,6 +542,11 @@ function appendCodexWorkerPolicyDirFlag(provider, args, workerPolicyDir) {
535
542
  return [...args, "-c", setting];
536
543
  return [...args.slice(0, separator), "-c", setting, ...args.slice(separator)];
537
544
  }
545
+ function accountHomeHostname(deps) {
546
+ return (deps.hostname?.trim() ||
547
+ deps.admissionHost?.hostname.trim() ||
548
+ resolveAdmissionHost().hostname);
549
+ }
538
550
  export const PID_LIVENESS_DECISIONS = [
539
551
  "esrch",
540
552
  "starttime_mismatch",
@@ -995,7 +1007,10 @@ export function resumePrompt(input) {
995
1007
  ? "The previous run ended without writing its envelope; continue the work from where it stopped."
996
1008
  : "Continue the work from where the previous run stopped."));
997
1009
  }
998
- if (input.operatorMessage !== undefined) {
1010
+ if (input.monitorEvent !== undefined) {
1011
+ parts.push(input.monitorEvent);
1012
+ }
1013
+ else if (input.operatorMessage !== undefined) {
999
1014
  parts.push("OPERATOR INSTRUCTION (outranks the lane brief). " +
1000
1015
  "The following message was sent to this lane by the operator. Treat it as an operator instruction that outranks the brief.\n\n" +
1001
1016
  input.operatorMessage);
@@ -1022,6 +1037,7 @@ export function resumePrompt(input) {
1022
1037
  // Every resume turn carries the authorization, including a textless
1023
1038
  // `hq lanes resume`: a pre-upgrade session's brief never had the paragraph.
1024
1039
  if (input.operatorMessage !== undefined ||
1040
+ input.monitorEvent !== undefined ||
1025
1041
  input.answersPath !== undefined ||
1026
1042
  input.resumedFrom !== undefined) {
1027
1043
  parts.push(STANDING_AUTHORIZATION);
@@ -1097,6 +1113,21 @@ export function buildProviderCommand(provider, input) {
1097
1113
  input.resume.prompt,
1098
1114
  ];
1099
1115
  }
1116
+ else if (input.resume && parsed === "grok") {
1117
+ // `grok --single <prompt> --resume <id>` (1.0.34) continues the recorded
1118
+ // session and prints the same `sessionId` in its JSON result.
1119
+ args = [
1120
+ "--single",
1121
+ input.resume.prompt,
1122
+ "--resume",
1123
+ input.resume.session_id,
1124
+ "--permission-mode",
1125
+ "bypassPermissions",
1126
+ "--always-approve",
1127
+ "--output-format",
1128
+ "json",
1129
+ ];
1130
+ }
1100
1131
  else if (input.resume) {
1101
1132
  args = [
1102
1133
  "-p",
@@ -1508,6 +1539,7 @@ function inferTrigger(lane) {
1508
1539
  */
1509
1540
  export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1510
1541
  const env = deps.env ?? process.env;
1542
+ const hostname = accountHomeHostname(deps);
1511
1543
  const modelDeps = {
1512
1544
  ...deps.modelDeps,
1513
1545
  env: deps.modelDeps?.env ?? env,
@@ -1625,6 +1657,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1625
1657
  pinnedModel,
1626
1658
  profileModel,
1627
1659
  currentModelValidated: deps.currentModelValidated === true,
1660
+ hostname,
1628
1661
  });
1629
1662
  }
1630
1663
  catch (err) {
@@ -1640,7 +1673,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1640
1673
  throw err;
1641
1674
  }
1642
1675
  const resolved = selection.account;
1643
- const status = accountStatus(lane.provider, resolved.id, config, { existsSync });
1676
+ const status = accountStatus(lane.provider, resolved.id, config, { existsSync, hostname });
1644
1677
  if (isLoggedOut(status)) {
1645
1678
  const failed = markPreSpawnFailure
1646
1679
  ? markSpawnFailure(hqRoot, lane.lane_id, "account_not_logged_in", {
@@ -1658,7 +1691,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1658
1691
  existing.provider_session_id &&
1659
1692
  providerSupportsSessionResume(existing.provider)) {
1660
1693
  copySessionFiles(planSessionCopy(existing.provider, {
1661
- sourceHome: source.home,
1694
+ sourceHome: existing.provider_home_at_spawn ?? effectiveAccountHome(source, hostname).home,
1662
1695
  targetHome: resolved.home,
1663
1696
  cwd: hqRoot,
1664
1697
  sessionId: existing.provider_session_id,
@@ -1672,7 +1705,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1672
1705
  cwd: hqRoot,
1673
1706
  targetHome: resolved.home,
1674
1707
  registeredHomes: Object.values(config.providers[existing.provider].accounts)
1675
- .map((account) => account.home),
1708
+ .map((account) => effectiveAccountHome(account, hostname).home),
1676
1709
  providerHomeAtSpawn: existing.provider_home_at_spawn,
1677
1710
  });
1678
1711
  }
@@ -1745,11 +1778,18 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1745
1778
  session_id: plan.session_id,
1746
1779
  prompt: resumePrompt({
1747
1780
  envelopePath,
1748
- operatorMessage: trigger.kind === "message"
1781
+ operatorMessage: trigger.kind === "message" && trigger.monitor_event !== true
1749
1782
  ? trigger.text
1750
1783
  : trigger.kind === "resume"
1751
1784
  ? trigger.text
1752
- : pendingResume?.text,
1785
+ : pendingResume?.monitor_event === true
1786
+ ? undefined
1787
+ : pendingResume?.text,
1788
+ monitorEvent: trigger.kind === "message" && trigger.monitor_event === true
1789
+ ? trigger.text
1790
+ : pendingResume?.monitor_event === true
1791
+ ? pendingResume.text
1792
+ : undefined,
1753
1793
  resumedFrom: trigger.kind === "resume" ? trigger.from : pendingResume?.resumed_from,
1754
1794
  answersPath: (trigger.kind === "answered_questions" || pendingResume?.trigger === "answered_questions") && existsSync(answersPath)
1755
1795
  ? answersPath
@@ -1933,6 +1973,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1933
1973
  account: resolved.id,
1934
1974
  trigger: pendingResume.trigger,
1935
1975
  ...(pendingResume.message_id !== undefined ? { message_id: pendingResume.message_id } : {}),
1976
+ ...(pendingResume.monitor_event === true ? { monitor_event: true } : {}),
1936
1977
  ...(pendingResume.resumed_from !== undefined ? { resumed_from: pendingResume.resumed_from } : {}),
1937
1978
  ...(pendingResume.text !== undefined ? { text: pendingResume.text } : {}),
1938
1979
  ...(pendingResume.compact !== undefined ? { compact: pendingResume.compact } : {}),
@@ -1945,7 +1986,11 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1945
1986
  account: resolved.id,
1946
1987
  trigger: trigger.kind,
1947
1988
  ...(trigger.kind === "message"
1948
- ? { message_id: trigger.message_id, text: trigger.text }
1989
+ ? {
1990
+ message_id: trigger.message_id,
1991
+ text: trigger.text,
1992
+ ...(trigger.monitor_event === true ? { monitor_event: true } : {}),
1993
+ }
1949
1994
  : {}),
1950
1995
  ...(launch.compact !== undefined ? { compact: launch.compact } : {}),
1951
1996
  ...(trigger.kind === "resume"
@@ -2043,12 +2088,14 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
2043
2088
  }
2044
2089
  /**
2045
2090
  * `resume`: claude and codex continue the recorded session with the repair
2046
- * prompt. `fresh_turn`: grok has no session resume, so the repair is a new
2047
- * single-turn invocation of the same provider on the same account, whose
2048
- * prompt carries the schema, the parser error and the raw failed output.
2091
+ * prompt. `fresh_turn`: grok gets a new single-turn invocation on the same
2092
+ * account, whose prompt carries the schema, the parser error and the raw
2093
+ * failed output. Grok can resume a session (operator messages and
2094
+ * `hq lanes resume` use it), but its repair turn stays a fresh turn: that
2095
+ * path was built and proven that way, and a repair by resume has not been.
2049
2096
  */
2050
2097
  export function envelopeRepairMode(provider) {
2051
- return providerSupportsSessionResume(provider) ? "resume" : "fresh_turn";
2098
+ return providerSupportsSessionResume(provider) && provider !== "grok" ? "resume" : "fresh_turn";
2052
2099
  }
2053
2100
  /**
2054
2101
  * Spec §4 repair turn. The lane is still `running` (the watcher has not
@@ -2062,6 +2109,7 @@ export function envelopeRepairMode(provider) {
2062
2109
  */
2063
2110
  export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
2064
2111
  const env = deps.env ?? process.env;
2112
+ const hostname = accountHomeHostname(deps);
2065
2113
  const existsSync = deps.existsSync ?? ((p) => fs.existsSync(p));
2066
2114
  const spawnProcess = deps.spawnProcess ?? defaultSpawnProcess;
2067
2115
  const proveDetached = deps.proveDetached ?? defaultProveDetached;
@@ -2108,6 +2156,7 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
2108
2156
  const resolved = resolveAccount(lane.provider, lane.account, config, {
2109
2157
  hqRoot,
2110
2158
  existsSync,
2159
+ hostname,
2111
2160
  model: pinnedModel ?? profileModel,
2112
2161
  });
2113
2162
  if (profileDefaultsApply && profileModel !== undefined && pinnedModel === undefined) {
@@ -514,6 +514,8 @@ export interface PendingResume {
514
514
  resumed_from?: "interrupted" | "done" | "blocked" | "failed";
515
515
  text?: string;
516
516
  message_id?: string;
517
+ /** The text is already framed as untrusted monitor data, not an operator instruction. */
518
+ monitor_event?: true;
517
519
  /** False when the command passed --no-compact; the deferred spawn honours it. */
518
520
  compact?: boolean;
519
521
  at: string;
@@ -534,6 +536,7 @@ export interface LaneResumeRecord {
534
536
  account?: string;
535
537
  trigger: "answered_questions" | "message" | "resume";
536
538
  message_id?: string;
539
+ monitor_event?: true;
537
540
  /**
538
541
  * `hq lanes resume` only: the state the lane was in when the operator
539
542
  * resumed it (`interrupted`, `done` or `blocked`) and the text they sent.
@@ -642,6 +645,8 @@ export interface LaneEnvelope {
642
645
  }
643
646
  export interface AccountConfig {
644
647
  home: string;
648
+ /** Optional account home overrides keyed by the owning host's hostname. */
649
+ hosts?: Record<string, AccountHostHome>;
645
650
  enabled: boolean;
646
651
  disabled_reason?: string;
647
652
  max_running?: number;
@@ -650,6 +655,9 @@ export interface AccountConfig {
650
655
  /** Default effort for this account. Beats the provider default; beaten by --effort. */
651
656
  effort?: string;
652
657
  }
658
+ export interface AccountHostHome {
659
+ home: string;
660
+ }
653
661
  export interface ProviderConfig {
654
662
  max_running: number;
655
663
  max_queued: number;
@@ -0,0 +1,153 @@
1
+ import type { LaneCompanyRef, LaneProvider } from "./lanes/types.js";
2
+ export declare const MONITOR_SCHEMA_VERSION: "hq-monitor.v1";
3
+ export declare const MONITOR_FLAG = "monitor.enabled";
4
+ export declare const MONITOR_FLAG_DEFAULT = false;
5
+ export declare const MONITOR_DEFAULT_TIMEOUT_MS: number;
6
+ export declare const MONITOR_DEFAULT_CHECKIN_MS: number;
7
+ export declare const MONITOR_PROMPT_CACHE_TTL_MS: number;
8
+ export declare const MONITOR_BATCH_WINDOW_MS = 200;
9
+ export declare const MONITOR_POLL_INTERVAL_MS = 1000;
10
+ export declare const MONITOR_RATE_WINDOW_MS = 10000;
11
+ /** Sustained stdout floods stop after 3 windows; the per-window cap can be overridden with HQ_MONITOR_MAX_EVENTS_PER_WINDOW. */
12
+ export declare const MONITOR_MAX_EVENTS_PER_WINDOW = 60;
13
+ export declare const MONITOR_MAX_FLOOD_WINDOWS = 3;
14
+ export declare const MONITOR_LOCK_STALE_MS = 30000;
15
+ export declare const MONITOR_MAX_TIMER_DELAY_MS = 2147000000;
16
+ export type MonitorTarget = {
17
+ kind: "lane";
18
+ laneId: string;
19
+ } | {
20
+ kind: "session";
21
+ provider: LaneProvider;
22
+ sessionId: string;
23
+ };
24
+ export interface MonitorProcessEntry {
25
+ pid: number;
26
+ ppid: number;
27
+ command: string;
28
+ }
29
+ export interface DetectMonitorTargetOptions {
30
+ env: NodeJS.ProcessEnv;
31
+ parentPid: number;
32
+ readProcess: (pid: number) => MonitorProcessEntry | null;
33
+ }
34
+ export interface NearestMonitorRuntime {
35
+ provider: LaneProvider;
36
+ pid: number;
37
+ }
38
+ export interface MonitorRuntimeIdentity extends NearestMonitorRuntime {
39
+ starttime: number;
40
+ }
41
+ export interface MonitorSessionOwner {
42
+ pid: number;
43
+ starttime: number | null;
44
+ }
45
+ export interface MonitorMetadata {
46
+ schema_version: typeof MONITOR_SCHEMA_VERSION;
47
+ monitor_id: string;
48
+ target: MonitorTarget;
49
+ target_key: string;
50
+ company: LaneCompanyRef | null;
51
+ description: string;
52
+ command: string | null;
53
+ argv: string[] | null;
54
+ cwd: string;
55
+ created_at: string;
56
+ timeout_ms: number | null;
57
+ persistent: boolean;
58
+ checkin_ms: number | null;
59
+ session_owner: MonitorSessionOwner | null;
60
+ }
61
+ export interface MonitorState {
62
+ status: "starting" | "running" | "stopping" | "terminal";
63
+ pid: number | null;
64
+ starttime: number | null;
65
+ child_pid?: number | null;
66
+ child_starttime?: number | null;
67
+ started_at?: string;
68
+ finished_at?: string;
69
+ exit_code?: number | null;
70
+ terminal_reason?: string;
71
+ event_count: number;
72
+ /** Last sequence written to events.jsonl, including undeliverable events. */
73
+ event_sequence?: number;
74
+ }
75
+ export interface MonitorEvent {
76
+ seq: number;
77
+ at: string;
78
+ text: string;
79
+ delivery: string;
80
+ reason?: string;
81
+ }
82
+ export interface MonitorTargetActivity {
83
+ last_delivery_ms: number;
84
+ last_delivery_kind: "monitor_event" | "checkin" | "start";
85
+ checkin_count: number;
86
+ last_activity_ms?: number;
87
+ last_checkin_attempt_ms?: number;
88
+ }
89
+ export interface MonitorCheckinDeps {
90
+ now: () => Date;
91
+ isWakeable: (targetKey: string) => boolean | Promise<boolean>;
92
+ readTargetActivityAt?: (targetKey: string) => number | null | Promise<number | null>;
93
+ deliver: (text: string, sequence: number) => Promise<{
94
+ delivered: boolean;
95
+ outcome: string;
96
+ }>;
97
+ pidIsAlive: (pid: number, starttime: number | null) => boolean;
98
+ readStarttime: (pid: number) => number | null;
99
+ sleep?: (ms: number) => Promise<void>;
100
+ }
101
+ export interface MonitorCheckinResult {
102
+ sent: boolean;
103
+ sequence: number | null;
104
+ text: string | null;
105
+ outcome: string | null;
106
+ }
107
+ /** This guard is first in monitor start so a conflicting invocation has no side effects. */
108
+ export declare function assertMonitorTimeoutCompatibility(persistent: boolean, timeoutProvided: boolean): void;
109
+ export declare function parseMonitorDuration(value: string, kind: "timeout" | "checkin"): number;
110
+ export declare function assertMonitorSessionId(sessionId: string): void;
111
+ export declare function parseMonitorTarget(value: string): MonitorTarget;
112
+ export declare function monitorTargetKey(target: MonitorTarget): string;
113
+ export declare function detectMonitorTarget(options: DetectMonitorTargetOptions): MonitorTarget;
114
+ /** The nearest runtime process in the caller chain, shared by target detection and hook mapping. */
115
+ export declare function findNearestMonitorRuntime(options: {
116
+ parentPid: number;
117
+ readProcess: (pid: number) => MonitorProcessEntry | null;
118
+ }): NearestMonitorRuntime | null;
119
+ export declare function monitorSessionDir(hqRoot: string, provider: LaneProvider, sessionId: string): string;
120
+ export declare function monitorSessionHasActiveMarkers(hqRoot: string, provider: LaneProvider, sessionId: string): boolean;
121
+ export declare function monitorSessionHasActiveMarkerForOwner(hqRoot: string, provider: LaneProvider, sessionId: string, owner: MonitorRuntimeIdentity): boolean;
122
+ export declare function monitorSessionHasInboxFile(hqRoot: string, provider: LaneProvider, sessionId: string): boolean;
123
+ export declare function monitorSessionInboxHasEvents(hqRoot: string, provider: LaneProvider, sessionId: string): boolean;
124
+ export declare function monitorSessionHasWork(hqRoot: string, provider: LaneProvider, sessionId: string): boolean;
125
+ export declare function monitorDir(hqRoot: string, monitorId: string): string;
126
+ export declare function makeMonitorId(): string;
127
+ export declare function formatMonitorEvent(input: {
128
+ monitorId: string;
129
+ description: string;
130
+ sequence: number;
131
+ text: string;
132
+ }): string;
133
+ export declare function nextMonitorEventSequence(state: Pick<MonitorState, "event_sequence">): number;
134
+ export declare function nextMonitorTimeoutDelay(timeoutMs: number, elapsedMs: number): number;
135
+ export declare function recordMonitorEventState(state: MonitorState, event: MonitorEvent): MonitorState;
136
+ /** Keep the supervisor alive until its command has closed so TERM escalation can run. */
137
+ export declare function requestMonitorStop(childPid: number | null, terminate: () => void, finishWithoutChild: () => void): void;
138
+ export declare function parseHookSessionId(payloadText: string): string;
139
+ export declare function recordHookSessionMapping(hqRoot: string, provider: LaneProvider, environmentSessionId: string | undefined, payloadSessionId: string, runtime: MonitorRuntimeIdentity | undefined): void;
140
+ export declare function resolveMappedSessionId(hqRoot: string, provider: LaneProvider, environmentSessionId: string, runtime: MonitorRuntimeIdentity | undefined): string;
141
+ export declare function withMonitorTargetLock<T>(hqRoot: string, targetKey: string, operation: (activityPath: string) => Promise<T> | T, options?: {
142
+ now?: () => Date;
143
+ pidIsAlive?: (pid: number, starttime: number | null) => boolean;
144
+ sleep?: (ms: number) => Promise<void>;
145
+ }): Promise<T>;
146
+ export declare function recordMonitorTargetDelivery(hqRoot: string, targetKey: string, now?: () => Date, kind?: MonitorTargetActivity["last_delivery_kind"]): Promise<void>;
147
+ export declare function recordMonitorTargetActivity(hqRoot: string, targetKey: string, now?: () => Date): Promise<void>;
148
+ export declare function recordMonitorTargetDeliveryAtPath(activityPath: string, now?: () => Date, kind?: MonitorTargetActivity["last_delivery_kind"]): void;
149
+ /** Elapsed idle time in whole units; live timers never land on exact multiples. */
150
+ export declare function formatElapsed(ms: number): string;
151
+ export declare function runMonitorCheckinTick(hqRoot: string, targetKey: string, deps: MonitorCheckinDeps): Promise<MonitorCheckinResult>;
152
+ export declare function readMonitorPidLiveness(pid: number | null, starttime: number | null, readStarttime?: (pid: number) => number | null): boolean;
153
+ //# sourceMappingURL=monitor.d.ts.map