@indigoai-us/hq-cli 5.345.147 → 5.345.149

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 (168) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +2 -0
  3. package/assets/bot-workers/setup/worker.yaml +11 -0
  4. package/dist/command-catalog.generated.d.ts +341 -11
  5. package/dist/command-catalog.generated.js +421 -11
  6. package/dist/command-help.js +2 -1
  7. package/dist/commands/access.d.ts +45 -2
  8. package/dist/commands/access.js +537 -223
  9. package/dist/commands/agent-inbox.js +2 -1
  10. package/dist/commands/billing-usage.d.ts +33 -2
  11. package/dist/commands/billing-usage.js +75 -4
  12. package/dist/commands/bot.js +2 -2
  13. package/dist/commands/channels.d.ts +27 -0
  14. package/dist/commands/channels.js +108 -4
  15. package/dist/commands/cloud.js +31 -1
  16. package/dist/commands/core-refresh-vault-access-native.d.ts +33 -0
  17. package/dist/commands/core-refresh-vault-access-native.js +356 -28
  18. package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +9 -0
  19. package/dist/commands/core-work-mesh-bind-trusted-native.js +26 -19
  20. package/dist/commands/core.js +9 -0
  21. package/dist/commands/daemon.d.ts +7 -0
  22. package/dist/commands/daemon.js +37 -3
  23. package/dist/commands/dm.js +18 -3
  24. package/dist/commands/doctor.d.ts +11 -1
  25. package/dist/commands/doctor.js +56 -1
  26. package/dist/commands/install-global.d.ts +8 -8
  27. package/dist/commands/install-global.js +139 -61
  28. package/dist/commands/lanes-shared.d.ts +3 -1
  29. package/dist/commands/lanes.d.ts +4 -4
  30. package/dist/commands/lanes.js +380 -25
  31. package/dist/commands/library-add.d.ts +31 -1
  32. package/dist/commands/library-add.js +69 -2
  33. package/dist/commands/library.js +37 -3
  34. package/dist/commands/pack-install.d.ts +2 -2
  35. package/dist/commands/pack-install.js +133 -35
  36. package/dist/commands/reindex.js +19 -0
  37. package/dist/commands/sync-manifest.d.ts +2 -9
  38. package/dist/commands/sync-manifest.js +4 -15
  39. package/dist/hook-engine-path.d.ts +4 -0
  40. package/dist/hook-engine-path.js +43 -0
  41. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2783 -2354
  42. package/dist/hq-cloud-sync-runner/package.json +1 -1
  43. package/dist/index.d.ts +10 -0
  44. package/dist/index.js +76 -5
  45. package/dist/lazy-commands.js +2 -0
  46. package/dist/lib/agent-kit/inbox-redact.d.ts +44 -0
  47. package/dist/lib/agent-kit/inbox-redact.js +361 -0
  48. package/dist/lib/agent-kit/inbox-state.js +11 -2
  49. package/dist/lib/agent-kit/mcp/tools.js +2 -1
  50. package/dist/lib/agent-kit/run/inbox.js +36 -7
  51. package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
  52. package/dist/lib/anywhere-runtime-flag-key.js +9 -0
  53. package/dist/lib/billing/usage-cost.d.ts +31 -1
  54. package/dist/lib/billing/usage-cost.js +46 -0
  55. package/dist/lib/company-skill-wrapper.js +6 -4
  56. package/dist/lib/daemon/install.d.ts +47 -1
  57. package/dist/lib/daemon/install.js +181 -15
  58. package/dist/lib/daemon/lock.d.ts +32 -0
  59. package/dist/lib/daemon/lock.js +55 -2
  60. package/dist/lib/db/local.d.ts +2 -2
  61. package/dist/lib/db/local.js +2 -2
  62. package/dist/lib/db/sql.js +2 -2
  63. package/dist/lib/db/sqlite-binding.d.ts +22 -0
  64. package/dist/lib/db/sqlite-binding.js +168 -0
  65. package/dist/lib/doctor/__testing__/run-doctor.js +4 -0
  66. package/dist/lib/doctor/checks/anywhere.d.ts +15 -0
  67. package/dist/lib/doctor/checks/anywhere.js +77 -11
  68. package/dist/lib/doctor/checks/cli-path.d.ts +12 -0
  69. package/dist/lib/doctor/checks/cli-path.js +46 -0
  70. package/dist/lib/doctor/checks/hook-engine.d.ts +4 -0
  71. package/dist/lib/doctor/checks/hook-engine.js +17 -0
  72. package/dist/lib/doctor/checks/sync-health.d.ts +7 -0
  73. package/dist/lib/doctor/checks/sync-health.js +38 -2
  74. package/dist/lib/doctor/checks/temp-homes.d.ts +19 -0
  75. package/dist/lib/doctor/checks/temp-homes.js +54 -0
  76. package/dist/lib/doctor/fix/apply.d.ts +90 -1
  77. package/dist/lib/doctor/fix/apply.js +176 -10
  78. package/dist/lib/doctor/fix/json-output.d.ts +4 -1
  79. package/dist/lib/doctor/fix/json-output.js +6 -0
  80. package/dist/lib/doctor/fix/remediation.d.ts +7 -1
  81. package/dist/lib/doctor/fix/remediation.js +23 -1
  82. package/dist/lib/doctor/registry.js +10 -0
  83. package/dist/lib/doctor/temp-home-sweeper.d.ts +149 -0
  84. package/dist/lib/doctor/temp-home-sweeper.js +418 -0
  85. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  86. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  87. package/dist/lib/flag-registry.js +9 -1
  88. package/dist/lib/flag-snapshot-file.js +8 -3
  89. package/dist/lib/hook-engine/reconcile.d.ts +9 -0
  90. package/dist/lib/hook-engine/reconcile.js +134 -0
  91. package/dist/lib/hook-engine/registration.d.ts +17 -0
  92. package/dist/lib/hook-engine/registration.js +50 -0
  93. package/dist/lib/hook-engine/resolve.d.ts +31 -0
  94. package/dist/lib/hook-engine/resolve.js +134 -0
  95. package/dist/lib/install/claude-targets.d.ts +2 -1
  96. package/dist/lib/install/claude-targets.js +8 -3
  97. package/dist/lib/install/codex-targets.d.ts +3 -0
  98. package/dist/lib/install/codex-targets.js +15 -4
  99. package/dist/lib/install/hook-target.d.ts +9 -0
  100. package/dist/lib/install/hook-target.js +39 -0
  101. package/dist/lib/lanes/admission-host.d.ts +1 -0
  102. package/dist/lib/lanes/admission-host.js +8 -1
  103. package/dist/lib/lanes/admission.d.ts +4 -0
  104. package/dist/lib/lanes/admission.js +6 -2
  105. package/dist/lib/lanes/envelope-repair-dispatch.d.ts +5 -0
  106. package/dist/lib/lanes/envelope-repair-dispatch.js +12 -1
  107. package/dist/lib/lanes/index.d.ts +2 -2
  108. package/dist/lib/lanes/index.js +1 -1
  109. package/dist/lib/lanes/keep-alive.js +4 -1
  110. package/dist/lib/lanes/local-host-identity.d.ts +40 -0
  111. package/dist/lib/lanes/local-host-identity.js +314 -0
  112. package/dist/lib/lanes/reaper.d.ts +1 -1
  113. package/dist/lib/lanes/reaper.js +7 -0
  114. package/dist/lib/lanes/reconcile.d.ts +4 -0
  115. package/dist/lib/lanes/reconcile.js +16 -8
  116. package/dist/lib/lanes/spawn.d.ts +20 -0
  117. package/dist/lib/lanes/spawn.js +48 -2
  118. package/dist/lib/lanes/status-fields.js +6 -1
  119. package/dist/lib/lanes/store.js +8 -1
  120. package/dist/lib/lanes/throttle.d.ts +49 -6
  121. package/dist/lib/lanes/throttle.js +154 -17
  122. package/dist/lib/lanes/types.d.ts +22 -1
  123. package/dist/lib/lanes/types.js +27 -3
  124. package/dist/lib/lanes/watcher.js +27 -2
  125. package/dist/lib/lanes/worker-continuity.d.ts +2 -0
  126. package/dist/lib/lanes/worker-continuity.js +18 -11
  127. package/dist/lib/manifest-disabled.d.ts +13 -0
  128. package/dist/lib/manifest-disabled.js +19 -0
  129. package/dist/lib/service-manager/launchd.d.ts +1 -0
  130. package/dist/lib/service-manager/launchd.js +1 -1
  131. package/dist/lib/windows/console-encoding.d.ts +24 -0
  132. package/dist/lib/windows/console-encoding.js +83 -0
  133. package/dist/lib/windows/install-followup.d.ts +35 -0
  134. package/dist/lib/windows/install-followup.js +142 -0
  135. package/dist/lib/windows/user-path.d.ts +125 -0
  136. package/dist/lib/windows/user-path.js +275 -0
  137. package/dist/lib/work-context/auto-bind.d.ts +1 -1
  138. package/dist/lib/work-context/auto-bind.js +22 -3
  139. package/dist/lib/work-context/lane-bind.d.ts +31 -0
  140. package/dist/lib/work-context/lane-bind.js +101 -0
  141. package/dist/lib/work-context/organize.js +1 -0
  142. package/dist/lib/work-context/prd-sync.js +7 -0
  143. package/dist/lib/work-context/project.d.ts +36 -10
  144. package/dist/lib/work-context/project.js +130 -41
  145. package/dist/lib/work-context/reconcile.d.ts +6 -0
  146. package/dist/lib/work-context/reconcile.js +25 -5
  147. package/dist/lib/work-context/state.d.ts +6 -0
  148. package/dist/lib/work-context/state.js +29 -1
  149. package/dist/main.js +2 -1
  150. package/dist/unknown-command.d.ts +18 -0
  151. package/dist/unknown-command.js +88 -0
  152. package/dist/utils/cognito-session.d.ts +8 -0
  153. package/dist/utils/cognito-session.js +14 -2
  154. package/dist/utils/copy-tree.d.ts +61 -0
  155. package/dist/utils/copy-tree.js +249 -0
  156. package/dist/utils/environmental-error.js +7 -3
  157. package/dist/utils/hook-trust.d.ts +9 -0
  158. package/dist/utils/hook-trust.js +30 -2
  159. package/dist/utils/npm-invocation.d.ts +27 -0
  160. package/dist/utils/npm-invocation.js +54 -0
  161. package/dist/utils/qmd-native-binding-error.js +3 -1
  162. package/dist/utils/resolve-executable.d.ts +2 -0
  163. package/dist/utils/resolve-executable.js +6 -2
  164. package/dist/utils/safe-link.d.ts +50 -0
  165. package/dist/utils/safe-link.js +300 -0
  166. package/package.json +17 -5
  167. package/scripts/hook-engine-manifest.mjs +22 -0
  168. package/scripts/install-hook-engine.mjs +10 -0
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
+ import { observeLocalHostIdentity } from "./local-host-identity.js";
3
4
  export const OUTPOST_ID_PATH = "/etc/outpost/outpost-id";
4
5
  export const OUTPOST_DIR = "/etc/outpost";
5
6
  export function readOutpostIdentity(existsSync = (p) => fs.existsSync(p), readFileSync = (p) => fs.readFileSync(p, "utf8")) {
@@ -25,9 +26,15 @@ export function readOutpostIdentity(existsSync = (p) => fs.existsSync(p), readFi
25
26
  }
26
27
  export function resolveAdmissionHost(deps = {}) {
27
28
  const identity = deps.readOutpostIdentity?.() ?? readOutpostIdentity(deps.existsSync, deps.readFileSync);
29
+ const hostname = (deps.hostname ?? os.hostname)();
30
+ const observed = observeLocalHostIdentity({
31
+ homedir: deps.homedir?.() ?? os.homedir(),
32
+ hostname,
33
+ });
28
34
  return {
29
- hostname: (deps.hostname ?? os.hostname)(),
35
+ hostname,
30
36
  outpostId: identity.kind === "id" ? identity.id : null,
37
+ ...(observed ? { machineId: observed.id, aliases: observed.aliases } : {}),
31
38
  };
32
39
  }
33
40
  /** `primary` names the controller registry unless the caller is a different Outpost. */
@@ -3,6 +3,10 @@ import { type LaneRecord, type LanesConfig } from "./types.js";
3
3
  export interface AdmissionHost {
4
4
  hostname: string;
5
5
  outpostId: string | null;
6
+ /** Stable id from ~/.hq/lane-host.json, when this process has read one. */
7
+ machineId?: string;
8
+ /** Hostnames this machine has already used. */
9
+ aliases?: readonly string[];
6
10
  }
7
11
  export type AdmissionCapReason = "provider_cap" | "account_cap" | "profile_cap";
8
12
  export declare const ADMISSION_LOCK_STALE_MS = 30000;
@@ -2,8 +2,9 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { accountMaxRunning } from "./config.js";
4
4
  import { LanesError } from "./errors.js";
5
+ import { localHostIdsReferToSameMachine } from "./local-host-identity.js";
5
6
  import { quotaBlockFor } from "./quota.js";
6
- import { isTerminalLaneState } from "./types.js";
7
+ import { isSubagentLane, isTerminalLaneState } from "./types.js";
7
8
  export const ADMISSION_LOCK_STALE_MS = 30_000;
8
9
  const ADMISSION_LOCK_TIMEOUT_MS = 5_000;
9
10
  const ADMISSION_RESERVATION_GRACE_MS = 30_000;
@@ -117,7 +118,7 @@ export function laneRunsOnHost(lane, host) {
117
118
  if (host.outpostId && lane.host.kind === "outpost") {
118
119
  return lane.host.id === host.outpostId;
119
120
  }
120
- return lane.host.kind === "local" && lane.host.id === host.hostname;
121
+ return lane.host.kind === "local" && localHostIdsReferToSameMachine(lane.host.id, host);
121
122
  }
122
123
  export function compareLaneQueueOrder(a, b) {
123
124
  if (b.priority !== a.priority)
@@ -188,6 +189,9 @@ export function laneOccupiesRunningSlot(lane, nowMs = Date.now()) {
188
189
  // count as a second owner of the slot that blocked it.
189
190
  if (lane.state === "queued" && lane.queued_reason === "account_cap")
190
191
  return false;
192
+ // A subagent lane runs inside its senior session, not in an HQ provider slot.
193
+ if (isSubagentLane(lane))
194
+ return false;
191
195
  return lane.state === "running" ||
192
196
  hasActiveAdmissionReservation(lane, nowMs) ||
193
197
  hasActivePendingResume(lane, nowMs);
@@ -17,6 +17,11 @@ export interface EnvelopeRepairDispatchDeps {
17
17
  env?: NodeJS.ProcessEnv;
18
18
  spawnSync?: (command: string, args: string[], options: EnvelopeRepairDispatchOptions) => EnvelopeRepairDispatchResult;
19
19
  }
20
+ /**
21
+ * True for the detached lane runner scripts. They are not the hq CLI: running
22
+ * one with `lanes ...` argv starts that runner for a lane named "lanes".
23
+ */
24
+ export declare function isLaneRunnerEntry(entry: string): boolean;
20
25
  /**
21
26
  * Dispatch repair into the currently installed CLI. A watcher can stay alive
22
27
  * across upgrades, so repair must not call its in-memory spawn implementation.
@@ -1,6 +1,14 @@
1
1
  import { spawnSync as nodeSpawnSync } from "node:child_process";
2
2
  import * as path from "node:path";
3
3
  import { LanesError } from "./errors.js";
4
+ const LANE_RUNNER_ENTRY_NAMES = new Set(["watcher-main", "worker-process", "remote-control-lane"]);
5
+ /**
6
+ * True for the detached lane runner scripts. They are not the hq CLI: running
7
+ * one with `lanes ...` argv starts that runner for a lane named "lanes".
8
+ */
9
+ export function isLaneRunnerEntry(entry) {
10
+ return LANE_RUNNER_ENTRY_NAMES.has(path.basename(entry).replace(/\.[cm]?[jt]s$/, ""));
11
+ }
4
12
  /**
5
13
  * Dispatch repair into the currently installed CLI. A watcher can stay alive
6
14
  * across upgrades, so repair must not call its in-memory spawn implementation.
@@ -28,7 +36,10 @@ export function spawnEnvelopeRepairViaCurrentCli(hqRoot, lane, input, deps = {})
28
36
  });
29
37
  const env = deps.env ?? process.env;
30
38
  const cliEntry = env.HQ_LANE_CLI_ENTRY?.trim();
31
- if (!cliEntry || !path.isAbsolute(cliEntry) || path.resolve(cliEntry) !== cliEntry) {
39
+ if (!cliEntry ||
40
+ !path.isAbsolute(cliEntry) ||
41
+ path.resolve(cliEntry) !== cliEntry ||
42
+ isLaneRunnerEntry(cliEntry)) {
32
43
  throw new LanesError("repair_cli_entry_invalid", "Lane watcher has no valid absolute hq CLI entrypoint for envelope repair", { lane_id: lane.lane_id });
33
44
  }
34
45
  const result = run(process.execPath, [cliEntry, "lanes", "_spawn-envelope-repair"], {
@@ -36,8 +36,8 @@ export { ENVELOPE_INVALID_REASON, decideEnvelopeRepair, emitMeshSoft, envelopeQu
36
36
  export type { EnvelopeRepairDecision, LatestEnvelopeFile } from "./watcher.js";
37
37
  export { LANE_MESH_EVENTS, WIRE_EVENT_KINDS, createLaneMesh, emitLaneDecisionEvents, emitLaneProgressEvent, laneCanEmit, parseLaneMeshEvent, progressEventDue, toWireThreadEvent, } from "./mesh-events.js";
38
38
  export type { CreateLaneMeshOptions, LaneMesh, LaneMeshEvent, WireEventKind, WireThreadEvent, } from "./mesh-events.js";
39
- export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, VM_STAT_BIN, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, parseMemAvailableKb, parseVmStatAvailableMb, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
40
- export type { CpuTimes, ThrottleAdmitReason, ThrottleDecision, ThrottleDeferReason, ThrottleDeps, ThrottleState, } from "./throttle.js";
39
+ export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, MEMORY_PRESSURE_SYSCTLS, SYSCTL_BIN, VM_STAT_BIN, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, formatMemoryHoldMessage, isCpuCheckEnabled, parseMemAvailableKb, parseMemoryPressureSysctl, parseVmStatAvailableMb, readAvailableMemMb, readMemorySignal, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
40
+ export type { CpuTimes, MemoryHold, MemoryPressureLevel, MemorySignal, MemorySignalSource, ThrottleAdmitReason, ThrottleDecision, ThrottleDeferReason, ThrottleDeps, ThrottleState, } from "./throttle.js";
41
41
  export { effectiveAccountHome, } from "./account-home.js";
42
42
  export type { EffectiveAccountHome, } from "./account-home.js";
43
43
  export { AUTH_JSON_NAME, CLAUDE_CREDENTIALS_NAME, PROVIDER_CREDENTIAL_FILES, PROVIDER_HOME_ENV, accountEnv, accountStatus, authJsonPath, authStateFor, claudeIdentityDiffers, claudeIdentityFromProbe, clearQuotaCommand, credentialPaths, describeClaudeIdentity, hasCredentialFile, isCredentialProven, isDefaultProviderHome, isLoggedOut, probeInteractiveClaudeIdentity, withProviderHomeEnv, isOccupyingLaneState, lastStartedAt, listAccountStatuses, occupyingLaneCount, resolveAccount, runningLaneCount, selectBalancedAccount, } from "./accounts.js";
@@ -20,7 +20,7 @@ export { PID_LIVENESS_DECISIONS, PROVIDER_COMMAND, PROVIDER_MODEL_FLAG, SPAWN_TE
20
20
  export { SILENCE_MS, WATCHER_DECISION_EVENTS, WATCHER_OUT_MAX_BYTES, WATCHER_POLL_MS, WATCHER_SIGNAL_KINDS, appendCappedLog, appendWatcherSignal, applyTerminalToLane, classifyLaneExit, clearLaneStalled, isPidAlive, laneIdentityFromRecord, markLaneStalled, notifySlotFreed, parseWatcherSignalKind, readLatestEnvelope, runWatcherLoop, stalledReason, tickWatcher, watcherEventsPath, watcherLastSignalPath, watcherOutLogPath, writeWatcherDecision, } from "./watcher.js";
21
21
  export { ENVELOPE_INVALID_REASON, decideEnvelopeRepair, emitMeshSoft, envelopeQuestionIds, readLatestEnvelopeFile, readLatestEnvelopeRecord, } from "./watcher.js";
22
22
  export { LANE_MESH_EVENTS, WIRE_EVENT_KINDS, createLaneMesh, emitLaneDecisionEvents, emitLaneProgressEvent, laneCanEmit, parseLaneMeshEvent, progressEventDue, toWireThreadEvent, } from "./mesh-events.js";
23
- export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, VM_STAT_BIN, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, parseMemAvailableKb, parseVmStatAvailableMb, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
23
+ export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, MEMORY_PRESSURE_SYSCTLS, SYSCTL_BIN, VM_STAT_BIN, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, formatMemoryHoldMessage, isCpuCheckEnabled, parseMemAvailableKb, parseMemoryPressureSysctl, parseVmStatAvailableMb, readAvailableMemMb, readMemorySignal, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
24
24
  export { effectiveAccountHome, } from "./account-home.js";
25
25
  export { AUTH_JSON_NAME, CLAUDE_CREDENTIALS_NAME, PROVIDER_CREDENTIAL_FILES, PROVIDER_HOME_ENV, accountEnv, accountStatus, authJsonPath, authStateFor, claudeIdentityDiffers, claudeIdentityFromProbe, clearQuotaCommand, credentialPaths, describeClaudeIdentity, hasCredentialFile, isCredentialProven, isDefaultProviderHome, isLoggedOut, probeInteractiveClaudeIdentity, withProviderHomeEnv, isOccupyingLaneState, lastStartedAt, listAccountStatuses, occupyingLaneCount, resolveAccount, runningLaneCount, selectBalancedAccount, } from "./accounts.js";
26
26
  export { LIVE_WITHIN_MS, LIVENESS_EVENT_KINDS, SILENT_LOST_MS, TURN_START_LOST_MS, buildLivenessIndexFromSpool, classifySessionLiveness, defaultLivenessDeps, defaultSpoolPath, ingestSpoolLine, listSessionLiveness, livenessIndexPath, loadLivenessIndex, readDaemonPresenceConnected, readLivenessIndex, rebuildLivenessIndex, sessionLiveness, } from "./liveness.js";
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { LanesError } from "./errors.js";
4
4
  import { dropboxInboxHasMessages } from "./dropbox.js";
5
5
  import { appendCappedHistory, listQuestionIds, readQuestion, retryUpdateLane } from "./store.js";
6
- import { isTerminalLaneState, isIsoDateTime, } from "./types.js";
6
+ import { isSubagentLane, 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
9
  export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS = 24 * 60 * 60 * 1000;
@@ -24,6 +24,9 @@ export const LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS = 5 * 60 * 1000;
24
24
  const LEAD_LANE_KEEP_ALIVE_MAX_BACKOFF_MS = 60 * 60 * 1000;
25
25
  /** Shared resume transition for terminal gate follow-ups. */
26
26
  export function queueLaneTerminalResume(current, at, resumeText, options = {}) {
27
+ // HQ holds no provider session for a subagent lane, so it can never resume one.
28
+ if (isSubagentLane(current))
29
+ return { lane: current, resumeQueued: false, resumeSkippedReason: "no_provider_session" };
27
30
  if (options.mergeExisting && current.pending_resume !== undefined) {
28
31
  return {
29
32
  lane: {
@@ -0,0 +1,40 @@
1
+ export declare const LANE_HOST_SCHEMA = "hq-lane-host.v1";
2
+ export declare const LANE_HOST_FILENAME = "lane-host.json";
3
+ export interface LocalHostIdentity {
4
+ schema_version: typeof LANE_HOST_SCHEMA;
5
+ id: string;
6
+ aliases: string[];
7
+ /** Hardware UUID, `/etc/machine-id`, or Windows MachineGuid. Absent on older files. */
8
+ hardware_id?: string;
9
+ }
10
+ export interface LocalHostMatch {
11
+ hostname: string;
12
+ machineId?: string;
13
+ aliases?: readonly string[];
14
+ }
15
+ export declare function laneHostIdentityPath(homedir: string): string;
16
+ /** True when `recordedId` is this machine's stable id, current hostname, or a known earlier hostname. */
17
+ export declare function localHostIdsReferToSameMachine(recordedId: string, host: LocalHostMatch): boolean;
18
+ /**
19
+ * Read the stable id, creating it once. A lost race re-reads the winner
20
+ * instead of minting a second id that would strand the lanes just created.
21
+ * A stored hardware id that does not match this machine mints a new id so a
22
+ * cloned home directory does not keep the source machine's id.
23
+ */
24
+ export declare function resolveLocalHostIdentity(input: {
25
+ homedir: string;
26
+ hostname: string;
27
+ createId?: () => string;
28
+ readHardwareId?: () => string | null;
29
+ }): LocalHostIdentity;
30
+ /**
31
+ * Read an existing identity. Does not create the file, record a hostname,
32
+ * or replace an id. A hardware mismatch returns null so a cloned id is not
33
+ * treated as this machine.
34
+ */
35
+ export declare function observeLocalHostIdentity(input: {
36
+ homedir: string;
37
+ hostname: string;
38
+ readHardwareId?: () => string | null;
39
+ }): LocalHostIdentity | null;
40
+ //# sourceMappingURL=local-host-identity.d.ts.map
@@ -0,0 +1,314 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import * as crypto from "node:crypto";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import { LanesError } from "./errors.js";
6
+ export const LANE_HOST_SCHEMA = "hq-lane-host.v1";
7
+ export const LANE_HOST_FILENAME = "lane-host.json";
8
+ const MAX_ALIASES = 32;
9
+ /**
10
+ * mDNS and DHCP rename a machine by swapping only this suffix
11
+ * (`Mac.localdomain` → `Mac.lan`). The label before the first dot must match,
12
+ * and the entire remainder must be one of these. `ec2.internal` and public
13
+ * DNS names are not in the set.
14
+ *
15
+ * A lane stamped with the bare label (`Mac`) matches a current or recorded
16
+ * name whose first label is that same word (`Mac.lan`). Two different
17
+ * machines that share a short label and a lane store can still collide for
18
+ * an old hostname-stamped lane. New lanes record the stable id below, and a
19
+ * copied home directory remints that id when the hardware id differs.
20
+ */
21
+ const LINK_LOCAL_SUFFIXES = new Set(["local", "localdomain", "lan", "home"]);
22
+ const HOST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
23
+ export function laneHostIdentityPath(homedir) {
24
+ return path.join(homedir, ".hq", LANE_HOST_FILENAME);
25
+ }
26
+ function corruptIdentity(file, problem, details = {}) {
27
+ return new LanesError("host_identity_corrupt", `${problem} The lane host file is ${file}. Restore that file, or delete it if no lane's host id came from it, then retry. Do not edit a lane's host.id by hand.`, { path: file, ...details });
28
+ }
29
+ function assertHostId(file, id) {
30
+ const trimmed = id.trim();
31
+ if (!HOST_ID_PATTERN.test(trimmed)) {
32
+ throw corruptIdentity(file, `Lane host id ${JSON.stringify(id)} is not a stable host id.`);
33
+ }
34
+ return trimmed;
35
+ }
36
+ function linkLocalLabel(name) {
37
+ const trimmed = name.trim();
38
+ const dot = trimmed.indexOf(".");
39
+ if (dot <= 0)
40
+ return null;
41
+ const suffix = trimmed.slice(dot + 1);
42
+ if (!LINK_LOCAL_SUFFIXES.has(suffix))
43
+ return null;
44
+ const label = trimmed.slice(0, dot);
45
+ return label.length > 0 ? label : null;
46
+ }
47
+ function linkLocalSameMachine(recorded, current) {
48
+ const recordedLabel = linkLocalLabel(recorded);
49
+ const currentLabel = linkLocalLabel(current);
50
+ return recordedLabel !== null && recordedLabel === currentLabel;
51
+ }
52
+ function firstLabel(name) {
53
+ const trimmed = name.trim();
54
+ const dot = trimmed.indexOf(".");
55
+ return dot === -1 ? trimmed : trimmed.slice(0, dot);
56
+ }
57
+ /** A bare stamp (`Mac`) and a longer name with that first label (`Mac.lan`) are the same machine. */
58
+ function bareLabelSameMachine(recorded, current) {
59
+ const left = recorded.trim();
60
+ const right = current.trim();
61
+ if (!left || !right || left === right)
62
+ return false;
63
+ const leftBare = !left.includes(".");
64
+ const rightBare = !right.includes(".");
65
+ if (leftBare === rightBare)
66
+ return false;
67
+ const bare = leftBare ? left : right;
68
+ const dotted = leftBare ? right : left;
69
+ return bare === firstLabel(dotted);
70
+ }
71
+ function sameMachineName(recorded, current) {
72
+ return linkLocalSameMachine(recorded, current) || bareLabelSameMachine(recorded, current);
73
+ }
74
+ /** True when `recordedId` is this machine's stable id, current hostname, or a known earlier hostname. */
75
+ export function localHostIdsReferToSameMachine(recordedId, host) {
76
+ const recorded = recordedId.trim();
77
+ if (!recorded)
78
+ return false;
79
+ const hostname = host.hostname.trim();
80
+ if (hostname !== "" && recorded === hostname)
81
+ return true;
82
+ const machineId = host.machineId?.trim();
83
+ if (machineId && recorded === machineId)
84
+ return true;
85
+ const aliases = host.aliases ?? [];
86
+ if (aliases.some((alias) => alias.trim() === recorded))
87
+ return true;
88
+ if (hostname !== "" && sameMachineName(recorded, hostname))
89
+ return true;
90
+ return aliases.some((alias) => sameMachineName(recorded, alias));
91
+ }
92
+ function cleanedAliases(file, aliases) {
93
+ if (!Array.isArray(aliases)) {
94
+ throw corruptIdentity(file, "Lane host identity aliases must be a list.");
95
+ }
96
+ const cleaned = [];
97
+ for (const alias of aliases) {
98
+ if (typeof alias !== "string") {
99
+ throw corruptIdentity(file, "Lane host identity aliases must be strings.");
100
+ }
101
+ const trimmed = alias.trim();
102
+ if (!trimmed || cleaned.includes(trimmed))
103
+ continue;
104
+ cleaned.push(trimmed);
105
+ }
106
+ return cleaned;
107
+ }
108
+ function withHostname(aliases, hostname) {
109
+ const host = hostname.trim();
110
+ const next = [...aliases];
111
+ if (host && !next.includes(host))
112
+ next.push(host);
113
+ if (next.length <= MAX_ALIASES)
114
+ return next;
115
+ const kept = next.slice(next.length - MAX_ALIASES);
116
+ if (host && !kept.includes(host)) {
117
+ kept.shift();
118
+ kept.push(host);
119
+ }
120
+ return kept;
121
+ }
122
+ function normalizeHardwareId(value) {
123
+ if (typeof value !== "string")
124
+ return null;
125
+ const trimmed = value.trim();
126
+ if (!trimmed || trimmed.length > 128 || /\s/.test(trimmed))
127
+ return null;
128
+ return trimmed;
129
+ }
130
+ function parseIdentity(file, raw) {
131
+ let parsed;
132
+ try {
133
+ parsed = JSON.parse(raw);
134
+ }
135
+ catch {
136
+ throw corruptIdentity(file, "Lane host identity file is not valid JSON.");
137
+ }
138
+ if (!parsed || typeof parsed !== "object") {
139
+ throw corruptIdentity(file, "Lane host identity file is not an object.");
140
+ }
141
+ const record = parsed;
142
+ if (record.schema_version !== LANE_HOST_SCHEMA) {
143
+ throw corruptIdentity(file, "Lane host identity file has the wrong schema.", {
144
+ schema_version: record.schema_version ?? null,
145
+ });
146
+ }
147
+ if (typeof record.id !== "string") {
148
+ throw corruptIdentity(file, "Lane host identity file is missing an id.");
149
+ }
150
+ let hardwareId;
151
+ if (record.hardware_id !== undefined) {
152
+ const normalized = normalizeHardwareId(record.hardware_id);
153
+ if (!normalized) {
154
+ throw corruptIdentity(file, "Lane host identity hardware id is not usable.");
155
+ }
156
+ hardwareId = normalized;
157
+ }
158
+ return {
159
+ schema_version: LANE_HOST_SCHEMA,
160
+ id: assertHostId(file, record.id),
161
+ aliases: cleanedAliases(file, record.aliases),
162
+ ...(hardwareId ? { hardware_id: hardwareId } : {}),
163
+ };
164
+ }
165
+ function serialize(identity) {
166
+ return `${JSON.stringify(identity, null, 2)}\n`;
167
+ }
168
+ function readIdentityFile(file) {
169
+ let raw;
170
+ try {
171
+ raw = fs.readFileSync(file, "utf8");
172
+ }
173
+ catch (err) {
174
+ if (err.code === "ENOENT")
175
+ return null;
176
+ throw err;
177
+ }
178
+ return parseIdentity(file, raw);
179
+ }
180
+ function sameAliases(left, right) {
181
+ return left.length === right.length && left.every((alias, index) => alias === right[index]);
182
+ }
183
+ function writeIdentity(file, identity) {
184
+ const dir = path.dirname(file);
185
+ fs.mkdirSync(dir, { recursive: true });
186
+ const tmp = path.join(dir, `.${LANE_HOST_FILENAME}.${crypto.randomBytes(6).toString("hex")}.tmp`);
187
+ fs.writeFileSync(tmp, serialize(identity));
188
+ fs.renameSync(tmp, file);
189
+ }
190
+ function readTextFile(file) {
191
+ try {
192
+ const text = fs.readFileSync(file, "utf8").trim();
193
+ return text || null;
194
+ }
195
+ catch {
196
+ return null;
197
+ }
198
+ }
199
+ let cachedHardwareId;
200
+ function readPlatformHardwareId() {
201
+ if (process.platform === "darwin") {
202
+ const result = spawnSync("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
203
+ encoding: "utf8",
204
+ timeout: 2000,
205
+ });
206
+ if (result.error || result.status !== 0)
207
+ return null;
208
+ const match = (result.stdout ?? "").match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
209
+ return match?.[1]?.trim() || null;
210
+ }
211
+ if (process.platform === "linux") {
212
+ return readTextFile("/etc/machine-id") ?? readTextFile("/var/lib/dbus/machine-id");
213
+ }
214
+ if (process.platform === "win32") {
215
+ const result = spawnSync("reg", ["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"], {
216
+ encoding: "utf8",
217
+ timeout: 2000,
218
+ });
219
+ if (result.error || result.status !== 0)
220
+ return null;
221
+ const match = (result.stdout ?? "").match(/MachineGuid\s+REG_SZ\s+(\S+)/);
222
+ return match?.[1]?.trim() || null;
223
+ }
224
+ return null;
225
+ }
226
+ function currentHardwareId(readHardwareId) {
227
+ if (readHardwareId)
228
+ return normalizeHardwareId(safeRead(readHardwareId));
229
+ if (cachedHardwareId !== undefined)
230
+ return cachedHardwareId;
231
+ cachedHardwareId = normalizeHardwareId(safeRead(readPlatformHardwareId));
232
+ return cachedHardwareId;
233
+ }
234
+ function safeRead(read) {
235
+ try {
236
+ return read();
237
+ }
238
+ catch {
239
+ return null;
240
+ }
241
+ }
242
+ function mintIdentity(file, hostname, hardwareId, createId) {
243
+ return {
244
+ schema_version: LANE_HOST_SCHEMA,
245
+ id: assertHostId(file, (createId ?? (() => crypto.randomUUID()))()),
246
+ aliases: hostname ? [hostname] : [],
247
+ ...(hardwareId ? { hardware_id: hardwareId } : {}),
248
+ };
249
+ }
250
+ function alignStoredIdentity(file, existing, hostname, hardwareId, createId) {
251
+ if (hardwareId && existing.hardware_id && existing.hardware_id !== hardwareId) {
252
+ const reminted = mintIdentity(file, hostname, hardwareId, createId);
253
+ writeIdentity(file, reminted);
254
+ return reminted;
255
+ }
256
+ const aliases = withHostname(existing.aliases, hostname);
257
+ const next = {
258
+ ...existing,
259
+ aliases,
260
+ ...(hardwareId && !existing.hardware_id ? { hardware_id: hardwareId } : {}),
261
+ };
262
+ if (next.hardware_id === existing.hardware_id && sameAliases(aliases, existing.aliases))
263
+ return existing;
264
+ writeIdentity(file, next);
265
+ return next;
266
+ }
267
+ /**
268
+ * Read the stable id, creating it once. A lost race re-reads the winner
269
+ * instead of minting a second id that would strand the lanes just created.
270
+ * A stored hardware id that does not match this machine mints a new id so a
271
+ * cloned home directory does not keep the source machine's id.
272
+ */
273
+ export function resolveLocalHostIdentity(input) {
274
+ const hostname = input.hostname.trim();
275
+ if (!hostname) {
276
+ throw new LanesError("host_identity_unknown", "Cannot resolve this host for lane placement", {});
277
+ }
278
+ const file = laneHostIdentityPath(input.homedir);
279
+ const hardwareId = currentHardwareId(input.readHardwareId);
280
+ const existing = readIdentityFile(file);
281
+ if (existing)
282
+ return alignStoredIdentity(file, existing, hostname, hardwareId, input.createId);
283
+ const created = mintIdentity(file, hostname, hardwareId, input.createId);
284
+ fs.mkdirSync(path.dirname(file), { recursive: true });
285
+ try {
286
+ fs.writeFileSync(file, serialize(created), { flag: "wx" });
287
+ }
288
+ catch (err) {
289
+ if (err.code !== "EEXIST")
290
+ throw err;
291
+ const raced = readIdentityFile(file);
292
+ if (!raced) {
293
+ throw corruptIdentity(file, "Lane host identity file disappeared while it was created.");
294
+ }
295
+ return alignStoredIdentity(file, raced, hostname, hardwareId, input.createId);
296
+ }
297
+ return created;
298
+ }
299
+ /**
300
+ * Read an existing identity. Does not create the file, record a hostname,
301
+ * or replace an id. A hardware mismatch returns null so a cloned id is not
302
+ * treated as this machine.
303
+ */
304
+ export function observeLocalHostIdentity(input) {
305
+ const file = laneHostIdentityPath(input.homedir);
306
+ const existing = readIdentityFile(file);
307
+ if (!existing)
308
+ return null;
309
+ const hardwareId = currentHardwareId(input.readHardwareId);
310
+ if (hardwareId && existing.hardware_id && existing.hardware_id !== hardwareId)
311
+ return null;
312
+ return existing;
313
+ }
314
+ //# sourceMappingURL=local-host-identity.js.map
@@ -1,4 +1,4 @@
1
- import type { LaneRecord } from "./types.js";
1
+ import { type LaneRecord } from "./types.js";
2
2
  import { type LegacyPoolFinding } from "./legacy-pool.js";
3
3
  export declare const DEFAULT_REAP_MIN_AGE_MINUTES = 120;
4
4
  export declare const REAP_SINGLE_FLIGHT_LOCK_STALE_MS: number;
@@ -8,6 +8,7 @@ import { laneRunsOnHost } from "./admission.js";
8
8
  import { laneRunDir, isPidRecordAlive, readPidRecord, readProcStarttime } from "./spawn.js";
9
9
  import { readAllLanes, readLane } from "./store.js";
10
10
  import { LanesError } from "./errors.js";
11
+ import { isSubagentLane } from "./types.js";
11
12
  import { findLegacyPoolLanes, stopLegacyPoolLane } from "./legacy-pool.js";
12
13
  export const DEFAULT_REAP_MIN_AGE_MINUTES = 120;
13
14
  export const REAP_SINGLE_FLIGHT_LOCK_STALE_MS = 60 * 60 * 1000;
@@ -220,6 +221,12 @@ export async function classifyLocalLanes(root, now, minAgeMinutes, deps = {}) {
220
221
  const ageMs = now.getTime() - Date.parse(lane.created_at);
221
222
  const age = Number.isFinite(ageMs) ? Math.max(0, Math.floor(ageMs / 60_000)) : 0;
222
223
  const finding = { lane_id: lane.lane_id, orphaned: false, reason: "not_a_candidate", age_minutes: age };
224
+ if (isSubagentLane(lane)) {
225
+ // A subagent lane has no process tree to reap; its senior session owns it.
226
+ finding.reason = "subagent_runner";
227
+ result.push(finding);
228
+ continue;
229
+ }
223
230
  const laneRunDirectory = laneRunDir(root, lane.lane_id);
224
231
  const workerRecord = readPidRecord(path.join(laneRunDirectory, "worker.pid"));
225
232
  const watcherRecord = readPidRecord(path.join(laneRunDirectory, "watcher.pid"));
@@ -87,6 +87,10 @@ export interface ReconcileSummary {
87
87
  export interface ReconcileHost {
88
88
  hostname: string;
89
89
  outpostId: string | null;
90
+ /** Stable id from ~/.hq/lane-host.json, when this process has read one. */
91
+ machineId?: string;
92
+ /** Hostnames this machine has already used. */
93
+ aliases?: readonly string[];
90
94
  }
91
95
  export interface ReconcileDeps {
92
96
  now: () => Date;
@@ -22,7 +22,7 @@ import { terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./gr
22
22
  import { classifySessionLiveness, defaultLivenessDeps, loadLivenessIndex, readDaemonPresenceConnected, unindexedClaudeSessionLiveness, } from "./liveness.js";
23
23
  import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, envelopeRepairMode, isPidRecordAlive, laneRunDir, nextEnvelopePath, readPidRecord, readProcStarttime, readSpawnManifest, markSpawnFailure, stopLaneProcesses, spawnEnvelopeRepair, spawnLaneWorker, retryLaneOnAlternateAccount, } from "./spawn.js";
24
24
  import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, retryUpdateLane, readQuestion, listQuestionIds, updateLane, updateQuestion, } from "./store.js";
25
- import { applyThrottleDecision, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyObservation, recordCpuBusyObservation, throttleStatePath, writeThrottleState, } from "./throttle.js";
25
+ import { applyThrottleDecision, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, readMemorySignal, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyObservation, recordCpuBusyObservation, throttleStatePath, writeThrottleState, } from "./throttle.js";
26
26
  import { readLanesRegistryFlag, readLanesRegistryFlagFailOff, readLanesRegistryFlagFailOpen } from "./registry-flag.js";
27
27
  import { LANE_BLOCKED_ESCALATION_FLAG, LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_KEEP_ALIVE_DUE_WAKE_PRECISE_FLAG, LANE_LEAD_ADMISSION_PRIORITY_FLAG, LANE_QUOTA_ACCOUNT_FAILOVER_FLAG, LANE_ENVELOPE_REPAIR_GUIDANCE_FLAG, } from "./flag-keys.js";
28
28
  import { checkLaneDiskSpace, configuredCodexRolloutHomes, laneDiskAccount, retainCodexRollouts } from "./disk-guard.js";
@@ -32,7 +32,7 @@ import { monitorSessionDir } from "../monitor.js";
32
32
  import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolGuard, RUNAWAY_TOOL_GUARD_FLAG, } from "./runaway-tool-guard.js";
33
33
  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, dueKeepAliveNextCheckMs, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
34
34
  import { isFailedQuotaKeepAliveLane, providerSupportsSessionResume } from "./delivery.js";
35
- import { LANE_PROVIDERS, isIsoDateTime, isTerminalLaneState } from "./types.js";
35
+ import { LANE_PROVIDERS, isIsoDateTime, isSubagentLane, isTerminalLaneState } from "./types.js";
36
36
  import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, readQuotaSignal, recordAccountQuota, } from "./quota.js";
37
37
  import { deliverQuotaNotice, quotaLaneNotice } from "./quota-notice.js";
38
38
  import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
@@ -1586,6 +1586,10 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
1586
1586
  for (const lane of lanes) {
1587
1587
  if (lane.state !== "running")
1588
1588
  continue;
1589
+ // No HQ process backs a subagent lane, so a missing pid is not a dead
1590
+ // worker. Its senior session reports progress and the final envelope.
1591
+ if (isSubagentLane(lane))
1592
+ continue;
1589
1593
  const local = laneRunsHere(lane, deps.host);
1590
1594
  if (local) {
1591
1595
  const runDir = laneRunDir(hqRoot, lane.lane_id);
@@ -2097,11 +2101,12 @@ async function admitQueued(hqRoot, lanes, summary, deps, now, onlyLaneIds, exclu
2097
2101
  const busy = deps.cpuBusyFraction !== undefined
2098
2102
  ? deps.cpuBusyFraction
2099
2103
  : await resolveCpuBusyFraction(throttleDeps);
2100
- const availableMemMb = readAvailableMemMb(throttleDeps);
2104
+ const memory = readMemorySignal(throttleDeps);
2101
2105
  const nowMs = now.getTime();
2102
2106
  const decision = evaluateThrottle({
2103
2107
  busy,
2104
- availableMemMb,
2108
+ availableMemMb: memory?.available_mb ?? null,
2109
+ memory,
2105
2110
  cpuCheckEnabled: isCpuCheckEnabled(throttleDeps.env),
2106
2111
  state: throttleState,
2107
2112
  throttle: config.throttle,
@@ -3570,11 +3575,14 @@ async function resumeAwaiting(hqRoot, lanes, questions, summary, deps, onlyLaneI
3570
3575
  if (deps.dryRun) {
3571
3576
  const loaded = readThrottleState(throttleStatePath(hqRoot), throttleDeps);
3572
3577
  const state = loaded === "missing" ? emptyThrottleState() : loaded;
3578
+ const busy = deps.cpuBusyFraction !== undefined
3579
+ ? deps.cpuBusyFraction
3580
+ : deps.cpuObservation?.busy ?? await resolveCpuBusyFraction(throttleDeps);
3581
+ const memory = readMemorySignal(throttleDeps);
3573
3582
  throttleDecision = evaluateThrottle({
3574
- busy: deps.cpuBusyFraction !== undefined
3575
- ? deps.cpuBusyFraction
3576
- : deps.cpuObservation?.busy ?? await resolveCpuBusyFraction(throttleDeps),
3577
- availableMemMb: readAvailableMemMb(throttleDeps),
3583
+ busy,
3584
+ availableMemMb: memory?.available_mb ?? null,
3585
+ memory,
3578
3586
  cpuCheckEnabled: isCpuCheckEnabled(throttleDeps.env),
3579
3587
  state,
3580
3588
  throttle: config.throttle,
@@ -190,6 +190,17 @@ export declare const STANDING_AUTHORIZATION: string;
190
190
  * turn while it was still running; nothing in the brief said not to.
191
191
  */
192
192
  export declare const FOREGROUND_ONLY_RULE: string;
193
+ /**
194
+ * Temp-home rule carried in every composed brief, for every provider. Lanes
195
+ * that ran tests under a raw mktemp HOME re-downloaded the Rust toolchain and
196
+ * pnpm store (1.4-1.8 GB) per run and never deleted the folder; on 2026-10-18
197
+ * that refilled about 45 GB of disk.
198
+ *
199
+ * The wrapper path is absolute because a worker's cwd is often a repo, not
200
+ * the HQ root. The wrapper ships in hq-core (hq-core-staging#1420); until an
201
+ * install has it, the command fails with "No such file".
202
+ */
203
+ export declare function tempHomeRule(hqRoot: string): string;
193
204
  /**
194
205
  * The answers as written by the reconcile pass (`answered-questions.json`),
195
206
  * reduced to id + choice. A malformed file reads as no inline answers; the
@@ -288,6 +299,15 @@ export declare function markSpawnFailure(hqRoot: string, laneId: string, reasonR
288
299
  * Must run before the worker is spawned.
289
300
  */
290
301
  export declare function refuseOutpostSelfTarget(hqRoot: string, lane: LaneRecord, deps: SpawnLaneDeps): void;
302
+ /**
303
+ * The hq CLI entry a watcher runs internal `hq lanes` commands through. A
304
+ * watcher admits queued lanes and retries accounts from its own process,
305
+ * where process.argv[1] is watcher-main.js rather than the CLI. Recording that
306
+ * path made envelope repair run `watcher-main.js lanes _spawn-envelope-repair`,
307
+ * which started a watcher for a lane named "lanes". A runner script in argv[1]
308
+ * is replaced by the CLI entry that ships beside this module.
309
+ */
310
+ export declare function resolveHqCliEntry(override: string | undefined, argvEntry?: string | undefined, existsSync?: (file: string) => boolean): string | undefined;
291
311
  /** Arm (or re-arm) the detached per-lane watcher. */
292
312
  export declare function armLaneWatcher(hqRoot: string, laneId: string, deps?: SpawnLaneDeps): number;
293
313
  export interface SpawnManifestFile {