harnery 0.43.0 → 0.45.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 (323) hide show
  1. package/README.md +3 -3
  2. package/dist/commander.js +1 -0
  3. package/dist/commands/agents.d.ts.map +1 -1
  4. package/dist/commands/agents.js +41 -48
  5. package/dist/commands/browse.d.ts.map +1 -1
  6. package/dist/commands/browse.js +38 -11
  7. package/dist/commands/diagnostics.d.ts.map +1 -1
  8. package/dist/commands/diagnostics.js +10 -0
  9. package/dist/commands/doctor.d.ts +12 -0
  10. package/dist/commands/doctor.d.ts.map +1 -1
  11. package/dist/commands/doctor.js +22 -7
  12. package/dist/commands/files.d.ts +2 -2
  13. package/dist/commands/files.d.ts.map +1 -1
  14. package/dist/commands/files.js +7 -4
  15. package/dist/commands/governor.js +22 -10
  16. package/dist/commands/inbox.d.ts +1 -1
  17. package/dist/commands/inbox.d.ts.map +1 -1
  18. package/dist/commands/inbox.js +55 -2
  19. package/dist/commands/rm.d.ts.map +1 -1
  20. package/dist/commands/rm.js +28 -4
  21. package/dist/commands/servers.d.ts.map +1 -1
  22. package/dist/commands/servers.js +26 -17
  23. package/dist/commands/supervisor.d.ts.map +1 -1
  24. package/dist/commands/supervisor.js +7 -2
  25. package/dist/commands/toc.d.ts +14 -0
  26. package/dist/commands/toc.d.ts.map +1 -1
  27. package/dist/commands/toc.js +7 -4
  28. package/dist/commands/tunnel.d.ts +15 -0
  29. package/dist/commands/tunnel.d.ts.map +1 -1
  30. package/dist/commands/tunnel.js +453 -153
  31. package/dist/commands/turn-rollback.d.ts +5 -0
  32. package/dist/commands/turn-rollback.d.ts.map +1 -0
  33. package/dist/commands/turn-rollback.js +76 -0
  34. package/dist/commands/workflow.d.ts.map +1 -1
  35. package/dist/commands/workflow.js +68 -30
  36. package/dist/core/agents/coord-client.d.ts.map +1 -1
  37. package/dist/core/agents/coord-client.js +18 -0
  38. package/dist/core/agents/live-authority-v3.d.ts +1 -0
  39. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  40. package/dist/core/agents/live-authority-v3.js +11 -0
  41. package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
  42. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  43. package/dist/core/agents/live-lifecycle-v3.js +28 -7
  44. package/dist/core/agents/mailbox.d.ts +9 -8
  45. package/dist/core/agents/mailbox.d.ts.map +1 -1
  46. package/dist/core/agents/mailbox.js +24 -17
  47. package/dist/core/agents/render/prompt-context.d.ts +2 -0
  48. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  49. package/dist/core/agents/render/prompt-context.js +8 -12
  50. package/dist/core/agents/render/session-context.d.ts +2 -8
  51. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  52. package/dist/core/agents/render/session-context.js +7 -30
  53. package/dist/core/agents/rules/stop-hook.js +3 -3
  54. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  55. package/dist/core/agents/state/heartbeat-writer.js +25 -14
  56. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  57. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  58. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  59. package/dist/core/agents/state/pidmap.js +5 -0
  60. package/dist/core/agents/state/proc-start.d.ts +19 -1
  61. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  62. package/dist/core/agents/state/proc-start.js +46 -1
  63. package/dist/core/agents/state/process-handle.d.ts +26 -0
  64. package/dist/core/agents/state/process-handle.d.ts.map +1 -0
  65. package/dist/core/agents/state/process-handle.js +121 -0
  66. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  67. package/dist/core/agents/state/stale-sweep.js +180 -70
  68. package/dist/core/agents/state/unchanged-file.d.ts +44 -0
  69. package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
  70. package/dist/core/agents/state/unchanged-file.js +390 -0
  71. package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
  72. package/dist/core/artifacts/delivery-card.js +2 -1
  73. package/dist/core/context/index.d.ts +22 -10
  74. package/dist/core/context/index.d.ts.map +1 -1
  75. package/dist/core/context/index.js +381 -119
  76. package/dist/core/context/observation.d.ts +26 -0
  77. package/dist/core/context/observation.d.ts.map +1 -0
  78. package/dist/core/context/observation.js +11 -0
  79. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  80. package/dist/core/diagnostics/bundle.js +25 -14
  81. package/dist/core/diagnostics/path-identity.d.ts +3 -0
  82. package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
  83. package/dist/core/diagnostics/path-identity.js +12 -0
  84. package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
  85. package/dist/core/diagnostics/sanitize.js +4 -3
  86. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  87. package/dist/core/events/v3/authority-outbox.js +29 -0
  88. package/dist/core/events/v3/generation-custody.d.ts +5 -0
  89. package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
  90. package/dist/core/events/v3/generation-custody.js +50 -0
  91. package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
  92. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  93. package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
  94. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  95. package/dist/core/events/v3/producers/recorder.js +77 -64
  96. package/dist/core/governor/planning.d.ts.map +1 -1
  97. package/dist/core/governor/planning.js +14 -1
  98. package/dist/core/governor/service-read.d.ts +10 -2
  99. package/dist/core/governor/service-read.d.ts.map +1 -1
  100. package/dist/core/governor/service-read.js +105 -22
  101. package/dist/core/governor/service.d.ts +20 -2
  102. package/dist/core/governor/service.d.ts.map +1 -1
  103. package/dist/core/governor/service.js +72 -47
  104. package/dist/core/hooks/cli.js +33 -5
  105. package/dist/core/inbox/contract.d.ts +2 -1
  106. package/dist/core/inbox/contract.d.ts.map +1 -1
  107. package/dist/core/inbox/index.d.ts +2 -0
  108. package/dist/core/inbox/index.d.ts.map +1 -1
  109. package/dist/core/inbox/index.js +2 -0
  110. package/dist/core/inbox/peer-messages.d.ts +37 -0
  111. package/dist/core/inbox/peer-messages.d.ts.map +1 -0
  112. package/dist/core/inbox/peer-messages.js +72 -0
  113. package/dist/core/inbox/service.d.ts +8 -1
  114. package/dist/core/inbox/service.d.ts.map +1 -1
  115. package/dist/core/inbox/service.js +67 -21
  116. package/dist/core/inbox/targets.d.ts +22 -0
  117. package/dist/core/inbox/targets.d.ts.map +1 -0
  118. package/dist/core/inbox/targets.js +108 -0
  119. package/dist/core/inbox/watcher.js +7 -3
  120. package/dist/core/journal/index.d.ts.map +1 -1
  121. package/dist/core/journal/index.js +17 -2
  122. package/dist/core/resources/contract.d.ts +3 -0
  123. package/dist/core/resources/contract.d.ts.map +1 -1
  124. package/dist/core/resources/service-status.d.ts +14 -1
  125. package/dist/core/resources/service-status.d.ts.map +1 -1
  126. package/dist/core/resources/service-status.js +23 -13
  127. package/dist/core/resources/service.d.ts +7 -13
  128. package/dist/core/resources/service.d.ts.map +1 -1
  129. package/dist/core/resources/service.js +34 -91
  130. package/dist/core/resources/status.d.ts +2 -5
  131. package/dist/core/resources/status.d.ts.map +1 -1
  132. package/dist/core/resources/status.js +36 -7
  133. package/dist/core/semantic/service.d.ts.map +1 -1
  134. package/dist/core/semantic/service.js +2 -9
  135. package/dist/core/servers/index.d.ts +34 -22
  136. package/dist/core/servers/index.d.ts.map +1 -1
  137. package/dist/core/servers/index.js +297 -144
  138. package/dist/core/servers/tunnels.d.ts +7 -7
  139. package/dist/core/servers/tunnels.d.ts.map +1 -1
  140. package/dist/core/servers/tunnels.js +47 -10
  141. package/dist/core/supervisor/contract.d.ts +12 -0
  142. package/dist/core/supervisor/contract.d.ts.map +1 -1
  143. package/dist/core/supervisor/explanations.d.ts.map +1 -1
  144. package/dist/core/supervisor/explanations.js +2 -1
  145. package/dist/core/supervisor/findings.d.ts.map +1 -1
  146. package/dist/core/supervisor/findings.js +54 -9
  147. package/dist/core/supervisor/service.d.ts +7 -1
  148. package/dist/core/supervisor/service.d.ts.map +1 -1
  149. package/dist/core/supervisor/service.js +59 -29
  150. package/dist/core/supervisor/services.d.ts.map +1 -1
  151. package/dist/core/supervisor/services.js +22 -1
  152. package/dist/core/supervisor/status.d.ts +17 -2
  153. package/dist/core/supervisor/status.d.ts.map +1 -1
  154. package/dist/core/supervisor/status.js +70 -12
  155. package/dist/core/supervisor/timeline.d.ts.map +1 -1
  156. package/dist/core/supervisor/timeline.js +2 -1
  157. package/dist/core/turn-rollback/content.d.ts +53 -0
  158. package/dist/core/turn-rollback/content.d.ts.map +1 -0
  159. package/dist/core/turn-rollback/content.js +396 -0
  160. package/dist/core/turn-rollback/index.d.ts +72 -0
  161. package/dist/core/turn-rollback/index.d.ts.map +1 -0
  162. package/dist/core/turn-rollback/index.js +443 -0
  163. package/dist/core/work/state.js +7 -7
  164. package/dist/core/workflow/engine.d.ts.map +1 -1
  165. package/dist/core/workflow/engine.js +105 -32
  166. package/dist/core/workflow/failure-evidence.d.ts +16 -0
  167. package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
  168. package/dist/core/workflow/failure-evidence.js +114 -0
  169. package/dist/core/workflow/index.d.ts +5 -3
  170. package/dist/core/workflow/index.d.ts.map +1 -1
  171. package/dist/core/workflow/index.js +3 -2
  172. package/dist/core/workflow/live-session-v3.d.ts +6 -0
  173. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  174. package/dist/core/workflow/live-session-v3.js +126 -5
  175. package/dist/core/workflow/proof.d.ts +5 -5
  176. package/dist/core/workflow/proof.d.ts.map +1 -1
  177. package/dist/core/workflow/proof.js +115 -20
  178. package/dist/core/workflow/recovery-audit.d.ts +30 -0
  179. package/dist/core/workflow/recovery-audit.d.ts.map +1 -0
  180. package/dist/core/workflow/recovery-audit.js +116 -0
  181. package/dist/core/workflow/run-state.d.ts +20 -1
  182. package/dist/core/workflow/run-state.d.ts.map +1 -1
  183. package/dist/core/workflow/run-state.js +141 -32
  184. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  185. package/dist/core/workflow/spawn-claude.js +19 -0
  186. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  187. package/dist/core/workflow/spawn-codex.js +13 -0
  188. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  189. package/dist/core/workflow/spawn-cursor.js +19 -0
  190. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  191. package/dist/core/workflow/spawn-opencode.js +14 -0
  192. package/dist/core/workflow/types.d.ts +64 -17
  193. package/dist/core/workflow/types.d.ts.map +1 -1
  194. package/dist/core/workflow/workspaces/index.d.ts +0 -1
  195. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  196. package/dist/core/workflow/workspaces/index.js +0 -1
  197. package/dist/core/workflow/workspaces/leases.d.ts +3 -1
  198. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  199. package/dist/core/workflow/workspaces/leases.js +30 -16
  200. package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
  201. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  202. package/dist/core/workflow/workspaces/local-git.js +328 -8
  203. package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
  204. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
  205. package/dist/core/workflow/workspaces/ownership.js +194 -0
  206. package/dist/core/workflow/workspaces/reclaim.d.ts +40 -0
  207. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  208. package/dist/core/workflow/workspaces/reclaim.js +14 -0
  209. package/dist/core/workflow/workspaces/recovery.d.ts +2 -0
  210. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  211. package/dist/core/workflow/workspaces/recovery.js +42 -10
  212. package/dist/core/workflow/workspaces/state.d.ts +1 -0
  213. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  214. package/dist/core/workflow/workspaces/state.js +21 -1
  215. package/dist/lib/browser/windows-chrome.d.ts +100 -12
  216. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  217. package/dist/lib/browser/windows-chrome.js +262 -73
  218. package/dist/lib/docs-links.d.ts.map +1 -1
  219. package/dist/lib/docs-links.js +45 -2
  220. package/dist/lib/guarded-remove.d.ts +7 -0
  221. package/dist/lib/guarded-remove.d.ts.map +1 -1
  222. package/dist/lib/guarded-remove.js +106 -12
  223. package/dist/lib/identities/assume.d.ts.map +1 -1
  224. package/dist/lib/identities/assume.js +4 -0
  225. package/dist/lib/tunnel/state.d.ts +13 -6
  226. package/dist/lib/tunnel/state.d.ts.map +1 -1
  227. package/dist/lib/tunnel/state.js +58 -12
  228. package/dist/lib/zip/index.d.ts +41 -0
  229. package/dist/lib/zip/index.d.ts.map +1 -0
  230. package/dist/lib/zip/index.js +398 -0
  231. package/package.json +12 -1
  232. package/src/commander.ts +10 -0
  233. package/src/commands/agents.ts +54 -63
  234. package/src/commands/browse.ts +50 -14
  235. package/src/commands/diagnostics.ts +10 -0
  236. package/src/commands/doctor.ts +26 -7
  237. package/src/commands/files.ts +13 -5
  238. package/src/commands/governor.ts +23 -10
  239. package/src/commands/inbox.ts +52 -2
  240. package/src/commands/rm.ts +43 -4
  241. package/src/commands/servers.ts +27 -26
  242. package/src/commands/supervisor.ts +7 -2
  243. package/src/commands/toc.ts +14 -6
  244. package/src/commands/tunnel.ts +533 -166
  245. package/src/commands/turn-rollback.ts +111 -0
  246. package/src/commands/workflow.ts +89 -30
  247. package/src/core/agents/coord-client.ts +20 -0
  248. package/src/core/agents/live-authority-v3.ts +17 -0
  249. package/src/core/agents/live-lifecycle-v3.ts +52 -14
  250. package/src/core/agents/mailbox.ts +26 -17
  251. package/src/core/agents/render/prompt-context.ts +10 -12
  252. package/src/core/agents/render/session-context.ts +9 -32
  253. package/src/core/agents/rules/stop-hook.ts +3 -3
  254. package/src/core/agents/state/heartbeat-writer.ts +29 -12
  255. package/src/core/agents/state/live-coordination-writer.ts +29 -0
  256. package/src/core/agents/state/pidmap.ts +13 -0
  257. package/src/core/agents/state/proc-start.ts +62 -2
  258. package/src/core/agents/state/process-handle.ts +165 -0
  259. package/src/core/agents/state/stale-sweep.ts +247 -82
  260. package/src/core/agents/state/unchanged-file.ts +456 -0
  261. package/src/core/artifacts/delivery-card.ts +7 -1
  262. package/src/core/context/index.ts +446 -136
  263. package/src/core/context/observation.ts +29 -0
  264. package/src/core/diagnostics/bundle.ts +23 -18
  265. package/src/core/diagnostics/path-identity.ts +15 -0
  266. package/src/core/diagnostics/sanitize.ts +4 -3
  267. package/src/core/events/v3/authority-outbox.ts +56 -0
  268. package/src/core/events/v3/generation-custody.ts +59 -0
  269. package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
  270. package/src/core/events/v3/producers/recorder.ts +94 -68
  271. package/src/core/governor/planning.ts +20 -8
  272. package/src/core/governor/service-read.ts +128 -20
  273. package/src/core/governor/service.ts +119 -42
  274. package/src/core/hooks/cli.ts +33 -5
  275. package/src/core/inbox/contract.ts +2 -1
  276. package/src/core/inbox/index.ts +2 -0
  277. package/src/core/inbox/peer-messages.ts +94 -0
  278. package/src/core/inbox/service.ts +88 -22
  279. package/src/core/inbox/targets.ts +148 -0
  280. package/src/core/inbox/watcher.ts +8 -9
  281. package/src/core/journal/index.ts +19 -2
  282. package/src/core/resources/contract.ts +4 -0
  283. package/src/core/resources/service-status.ts +35 -12
  284. package/src/core/resources/service.ts +57 -95
  285. package/src/core/resources/status.ts +57 -8
  286. package/src/core/semantic/service.ts +2 -8
  287. package/src/core/servers/index.ts +376 -141
  288. package/src/core/servers/tunnels.ts +57 -13
  289. package/src/core/supervisor/contract.ts +13 -0
  290. package/src/core/supervisor/explanations.ts +4 -1
  291. package/src/core/supervisor/findings.ts +68 -10
  292. package/src/core/supervisor/service.ts +87 -26
  293. package/src/core/supervisor/services.ts +26 -8
  294. package/src/core/supervisor/status.ts +89 -12
  295. package/src/core/supervisor/timeline.ts +2 -1
  296. package/src/core/turn-rollback/content.ts +499 -0
  297. package/src/core/turn-rollback/index.ts +580 -0
  298. package/src/core/work/state.ts +7 -7
  299. package/src/core/workflow/engine.ts +131 -49
  300. package/src/core/workflow/failure-evidence.ts +141 -0
  301. package/src/core/workflow/index.ts +12 -2
  302. package/src/core/workflow/live-session-v3.ts +146 -5
  303. package/src/core/workflow/proof.ts +134 -26
  304. package/src/core/workflow/recovery-audit.ts +181 -0
  305. package/src/core/workflow/run-state.ts +165 -34
  306. package/src/core/workflow/spawn-claude.ts +18 -0
  307. package/src/core/workflow/spawn-codex.ts +13 -0
  308. package/src/core/workflow/spawn-cursor.ts +18 -0
  309. package/src/core/workflow/spawn-opencode.ts +14 -0
  310. package/src/core/workflow/types.ts +51 -17
  311. package/src/core/workflow/workspaces/index.ts +0 -1
  312. package/src/core/workflow/workspaces/leases.ts +48 -17
  313. package/src/core/workflow/workspaces/local-git.ts +388 -7
  314. package/src/core/workflow/workspaces/ownership.ts +232 -0
  315. package/src/core/workflow/workspaces/reclaim.ts +62 -0
  316. package/src/core/workflow/workspaces/recovery.ts +45 -10
  317. package/src/core/workflow/workspaces/state.ts +20 -0
  318. package/src/lib/browser/windows-chrome.ts +346 -86
  319. package/src/lib/docs-links.ts +49 -2
  320. package/src/lib/guarded-remove.ts +132 -14
  321. package/src/lib/identities/assume.ts +4 -0
  322. package/src/lib/tunnel/state.ts +70 -15
  323. package/src/lib/zip/index.ts +483 -0
@@ -1,4 +1,3 @@
1
- import { spawn } from "node:child_process";
2
1
  import { randomUUID } from "node:crypto";
3
2
  import {
4
3
  closeSync,
@@ -10,9 +9,15 @@ import {
10
9
  unlinkSync,
11
10
  writeFileSync,
12
11
  } from "node:fs";
13
- import { hostname, platform } from "node:os";
12
+ import { hostname } from "node:os";
14
13
  import { resolve } from "node:path";
15
- import { checkPidToken, processStartToken } from "../agents/state/proc-start.ts";
14
+ import {
15
+ type ProcessGenerationSignalDependencies,
16
+ type ProcessGenerationVerdict,
17
+ processStartToken,
18
+ signalIfSameGeneration,
19
+ } from "../agents/state/proc-start.ts";
20
+ import { resolveBinName } from "../config.ts";
16
21
  import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.ts";
17
22
  import { stateDirMode, stateFileMode } from "../storage/modes.ts";
18
23
  import {
@@ -22,7 +27,11 @@ import {
22
27
  type ResourceServiceStatusRecord,
23
28
  } from "./contract.ts";
24
29
  import { sampleResources } from "./sampler.ts";
25
- import { readResourceServiceStatus } from "./service-status.ts";
30
+ import {
31
+ type ResourceProcessDependencies,
32
+ readResourceServiceStatus,
33
+ resourceProcessLiveness,
34
+ } from "./service-status.ts";
26
35
  import { resourcePaths, writePrivateJsonAtomic } from "./storage.ts";
27
36
 
28
37
  export const RESOURCE_SERVICE_DEFAULT_INTERVAL_MS = 2_000;
@@ -46,102 +55,47 @@ export interface RunResourceServiceInput {
46
55
  wait?: (milliseconds: number) => Promise<void>;
47
56
  }
48
57
 
49
- export type EnsureResourceServiceResult =
50
- | { state: "running" | "started"; status: ResourceServiceStatus }
51
- | { state: "unsupported" | "unavailable"; status: ResourceServiceStatus; error: string };
52
-
53
- export async function spawnResourceService(
54
- coordRootRaw: string,
55
- options: { intervalMs?: number } = {},
56
- ): Promise<ResourceServiceStatus> {
57
- const coordRoot = resolve(coordRootRaw);
58
- const current = readResourceServiceStatus(coordRoot);
59
- if (current.running) {
60
- throw new Error(`resource observer is already running under pid ${current.record?.pid}`);
61
- }
62
- const intervalMs = normalizeInterval(options.intervalMs);
63
- const paths = resourcePaths(coordRoot);
64
- mkdirSync(paths.root, { recursive: true, mode: stateDirMode() });
65
- rmSync(paths.stop, { force: true });
66
- const harnBin = new URL("../../../bin/harn", import.meta.url).pathname;
67
- if (!existsSync(harnBin)) throw new Error(`cannot find harn executable at ${harnBin}`);
68
- let spawnError: Error | undefined;
69
- const child = spawn(
70
- harnBin,
71
- ["resources", "service", "daemon", "--root", coordRoot, "--interval-ms", String(intervalMs)],
72
- {
73
- cwd: coordRoot,
74
- detached: true,
75
- stdio: ["ignore", "ignore", "ignore"],
76
- env: {
77
- ...process.env,
78
- HARNERY_COORD_ROOT_OVERRIDE: coordRoot,
79
- HARNERY_OUTPUT_SESSION_TEE: "0",
80
- },
81
- },
82
- );
83
- child.once("error", (error) => {
84
- spawnError = error;
85
- });
86
- child.unref();
87
- const deadline = Date.now() + 5_000;
88
- while (Date.now() < deadline) {
89
- await delay(50);
90
- if (spawnError) throw spawnError;
91
- const status = readResourceServiceStatus(coordRoot);
92
- if (status.running && status.record?.pid === child.pid) return status;
93
- if (child.exitCode !== null) break;
94
- }
95
- throw new Error(`resource observer failed to start; inspect ${paths.service}`);
96
- }
97
-
98
- export async function ensureResourceServiceRunning(
99
- coordRoot: string,
100
- ): Promise<EnsureResourceServiceResult> {
101
- const status = readResourceServiceStatus(coordRoot);
102
- if (status.running) return { state: "running", status };
103
- if (platform() !== "linux") {
104
- return {
105
- state: "unsupported",
106
- status,
107
- error: `resource observer does not yet support ${platform()}`,
108
- };
109
- }
110
- try {
111
- const started = await spawnResourceService(coordRoot);
112
- return { state: "started", status: started };
113
- } catch (error) {
114
- return {
115
- state: "unavailable",
116
- status: readResourceServiceStatus(coordRoot),
117
- error: error instanceof Error ? error.message : String(error),
118
- };
119
- }
58
+ export interface ResourceServiceStopResult extends ResourceServiceStatus {
59
+ signal_sent: boolean;
60
+ signal_verdict: ProcessGenerationVerdict;
120
61
  }
121
62
 
122
- export function requestResourceServiceStop(coordRootRaw: string): ResourceServiceStatus {
63
+ export function requestResourceServiceStop(
64
+ coordRootRaw: string,
65
+ dependencies: ResourceProcessDependencies & ProcessGenerationSignalDependencies = {},
66
+ ): ResourceServiceStopResult {
123
67
  const coordRoot = resolve(coordRootRaw);
124
68
  const paths = resourcePaths(coordRoot);
125
- const status = readResourceServiceStatus(coordRoot);
69
+ const status = readResourceServiceStatus(coordRoot, Date.now(), dependencies);
126
70
  mkdirSync(paths.root, { recursive: true, mode: stateDirMode() });
127
71
  writePrivateJsonAtomic(paths.stop, {
128
72
  requested_at: new Date().toISOString(),
129
73
  requested_by_pid: process.pid,
130
74
  });
131
- if (status.running && status.record?.host === hostname()) {
132
- try {
133
- process.kill(status.record.pid, "SIGTERM");
134
- } catch {
135
- // The durable stop file handles a racing exit.
136
- }
137
- }
138
- return readResourceServiceStatus(coordRoot);
75
+ const signal =
76
+ status.liveness === "running" && status.record?.host === hostname()
77
+ ? signalIfSameGeneration(
78
+ status.record.pid,
79
+ status.record.start_token,
80
+ "SIGTERM",
81
+ dependencies,
82
+ )
83
+ : { sent: false, verdict: status.generation };
84
+ return {
85
+ ...readResourceServiceStatus(coordRoot, Date.now(), dependencies),
86
+ signal_sent: signal.sent,
87
+ signal_verdict: signal.verdict,
88
+ };
139
89
  }
140
90
 
141
91
  export async function runResourceService(
142
92
  input: RunResourceServiceInput,
143
93
  ): Promise<ResourceServiceStatusRecord> {
144
94
  const coordRoot = resolve(input.coordRoot);
95
+ const current = readResourceServiceStatus(coordRoot);
96
+ if (current.liveness === "unknown") throw unknownResourceOwnerError();
97
+ if (current.liveness === "running")
98
+ throw new Error(`resource observer is already running under pid ${current.record?.pid}`);
145
99
  const paths = resourcePaths(coordRoot);
146
100
  const intervalMs = normalizeInterval(input.intervalMs);
147
101
  const now = input.now ?? (() => new Date());
@@ -253,7 +207,9 @@ function acquireLease(coordRoot: string, now: Date): ResourceServiceLease {
253
207
  } catch (error) {
254
208
  if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
255
209
  const existing = readLease(paths.lease);
256
- if (existing && leaseIsLive(existing)) {
210
+ const liveness = existing ? leaseIsLive(existing) : "unknown";
211
+ if (liveness === "unknown") throw unknownResourceOwnerError();
212
+ if (existing && liveness === "running") {
257
213
  throw new Error(`resource observer lease is held by pid ${existing.pid}`);
258
214
  }
259
215
  try {
@@ -279,7 +235,13 @@ function releaseLease(path: string, nonce: string): void {
279
235
  function readLease(path: string): ResourceServiceLease | undefined {
280
236
  try {
281
237
  const value = JSON.parse(readFileSync(path, "utf8")) as ResourceServiceLease;
282
- return value && Number.isInteger(value.pid) && typeof value.nonce === "string"
238
+ return value &&
239
+ Number.isSafeInteger(value.pid) &&
240
+ value.pid > 0 &&
241
+ typeof value.host === "string" &&
242
+ value.host.length > 0 &&
243
+ typeof value.nonce === "string" &&
244
+ value.nonce.length > 0
283
245
  ? value
284
246
  : undefined;
285
247
  } catch {
@@ -287,14 +249,14 @@ function readLease(path: string): ResourceServiceLease | undefined {
287
249
  }
288
250
  }
289
251
 
290
- function leaseIsLive(lease: ResourceServiceLease): boolean {
291
- if (lease.host !== hostname()) return true;
292
- try {
293
- process.kill(lease.pid, 0);
294
- } catch (error) {
295
- if ((error as NodeJS.ErrnoException).code !== "EPERM") return false;
296
- }
297
- return checkPidToken(lease.pid, lease.start_token) !== "mismatch";
252
+ function leaseIsLive(lease: ResourceServiceLease): ResourceServiceStatus["liveness"] {
253
+ return resourceProcessLiveness(lease).liveness;
254
+ }
255
+
256
+ function unknownResourceOwnerError(): Error {
257
+ return new Error(
258
+ `resource observer process identity is unknown; run ${resolveBinName()} supervisor stop to write the durable stop request before starting again`,
259
+ );
298
260
  }
299
261
 
300
262
  function log(
@@ -10,8 +10,9 @@ import {
10
10
  SUPERVISOR_PRESSURE_SCHEMA_VERSION,
11
11
  type SupervisorCapability,
12
12
  type SupervisorPressureRecord,
13
+ type SupervisorStatus,
13
14
  } from "../supervisor/contract.ts";
14
- import { readSupervisorStatus } from "../supervisor/status.ts";
15
+ import { installedSupervisorBuild, readSupervisorStatus } from "../supervisor/status.ts";
15
16
  import { supervisorPaths } from "../supervisor/storage.ts";
16
17
  import {
17
18
  RESOURCE_SNAPSHOT_SCHEMA_VERSION,
@@ -43,7 +44,17 @@ export interface ResourceStatus {
43
44
  platform: NodeJS.Platform | null;
44
45
  namespace: ResourceSnapshot["namespace"];
45
46
  support: ResourceSnapshot["support"] | null;
46
- writer: { running: boolean; stale: boolean };
47
+ writer: Pick<
48
+ SupervisorStatus,
49
+ | "running"
50
+ | "stale"
51
+ | "generation"
52
+ | "liveness"
53
+ | "running_build"
54
+ | "installed_build"
55
+ | "restart_needed"
56
+ | "build_status"
57
+ >;
47
58
  /**
48
59
  * The one published pressure assessment, read from the observer's record so
49
60
  * this surface, the prompt notice, the dashboard, and a diagnostic bundle all
@@ -222,12 +233,16 @@ export function formatResourceSummary(status: ResourceStatus): string {
222
233
  ? "unknown"
223
234
  : `${formatBytes(m.memory_available_bytes)} available`;
224
235
  const diskText = disk ? `${formatBytes(disk.available_bytes as number)} available` : "unknown";
225
- return `${lead} ${status.namespace}/${status.platform}: CPU ${cpu}, RAM ${ram}, disk ${diskText}; ${Math.round((status.sample_age_ms ?? 0) / 1_000)}s old${status.support?.state === "partial" ? "; partial" : ""}${!status.writer.running ? "; writer stopped" : ""}.`;
236
+ return `${lead} ${status.namespace}/${status.platform}: CPU ${cpu}, RAM ${ram}, disk ${diskText}; ${Math.round((status.sample_age_ms ?? 0) / 1_000)}s old${status.support?.state === "partial" ? "; partial" : ""}${!status.writer.running ? `; writer ${status.writer.liveness === "unknown" ? "unknown" : status.writer.stale ? "stale" : "stopped"}` : ""}.`;
226
237
  }
227
238
 
228
239
  export function formatResourceStatus(status: ResourceStatus, bin: string): string {
229
240
  const a = status.assessment;
230
241
  const lines = [formatResourceSummary(status)];
242
+ const writer = status.writer;
243
+ lines.push(
244
+ `Writer: ${writer.liveness}; generation ${writer.generation}; running build ${writer.running_build ? `${writer.running_build.version} (${writer.running_build.revision})` : "unknown"}; installed build ${writer.installed_build.version} (${writer.installed_build.revision}); ${writer.build_status}.`,
245
+ );
231
246
  lines.push(
232
247
  `Assessment: ${a.state} at ${a.scope} scope; limiting resource ${a.limiting_resource}; trend ${a.trend}; evidence ${a.evidence_state}; recommended action ${a.recommended_action}.`,
233
248
  );
@@ -247,7 +262,11 @@ export function formatResourceStatus(status: ResourceStatus, bin: string): strin
247
262
  lines.push(
248
263
  `Unattributed memory: ${Math.round(a.unattributed_memory_percent)}% of machine memory has no validated owner.`,
249
264
  );
250
- if (status.state !== "fresh" || !status.writer.running)
265
+ if (status.writer.liveness === "unknown")
266
+ lines.push(
267
+ `Inspect the writer with ${bin} supervisor status. Run ${bin} supervisor stop to write its durable stop request before starting again.`,
268
+ );
269
+ else if (status.state !== "fresh" || !status.writer.running)
251
270
  lines.push(
252
271
  `Start or inspect the writer with ${bin} supervisor start --keep-alive and ${bin} supervisor status.`,
253
272
  );
@@ -345,7 +364,15 @@ export function readPublishedPressure(
345
364
  });
346
365
  // A record can only be trusted while its writer is alive. Every surface makes
347
366
  // the same check here so none of them reports a state the others do not.
348
- if (!(writer ?? readWriterState(coordRoot, nowMs)).running) {
367
+ const observer = writer ?? readWriterState(coordRoot, nowMs);
368
+ if (observer.liveness === "unknown") {
369
+ return gap(
370
+ "error",
371
+ "pressure_observer_unknown",
372
+ "Local resource pressure cannot be determined because the resource observer's process identity is unknown.",
373
+ );
374
+ }
375
+ if (!observer.running) {
349
376
  return gap(
350
377
  "expired",
351
378
  "pressure_observer_not_running",
@@ -407,7 +434,7 @@ function readWriterState(coordRoot: string, nowMs: number): ResourceStatus["writ
407
434
  // Keep malformed service metadata from making the read expensive. The shared
408
435
  // status reader retains ownership of PID/start-token liveness checks.
409
436
  if (statSync(supervisorPaths(coordRoot).service).size > 65_536) {
410
- return { running: false, stale: false };
437
+ return unknownWriterState();
411
438
  }
412
439
  const writer = readSupervisorStatus(coordRoot, nowMs);
413
440
  const futureHeartbeat =
@@ -415,13 +442,35 @@ function readWriterState(coordRoot: string, nowMs: number): ResourceStatus["writ
415
442
  return {
416
443
  running: writer.running && !futureHeartbeat,
417
444
  stale: writer.stale || !!futureHeartbeat,
445
+ generation: writer.generation,
446
+ liveness: writer.liveness,
447
+ running_build: writer.running_build,
448
+ installed_build: writer.installed_build,
449
+ restart_needed: writer.restart_needed,
450
+ build_status: writer.build_status,
418
451
  };
419
- } catch {
452
+ } catch (error) {
420
453
  // A missing writer must not break normal coordination.
421
- return { running: false, stale: false };
454
+ return {
455
+ ...unknownWriterState(),
456
+ liveness: (error as NodeJS.ErrnoException).code === "ENOENT" ? "stopped" : "unknown",
457
+ };
422
458
  }
423
459
  }
424
460
 
461
+ function unknownWriterState(): ResourceStatus["writer"] {
462
+ return {
463
+ running: false,
464
+ stale: false,
465
+ generation: "unknown",
466
+ liveness: "unknown",
467
+ running_build: null,
468
+ installed_build: installedSupervisorBuild(),
469
+ restart_needed: true,
470
+ build_status: "running build unknown; restart needed",
471
+ };
472
+ }
473
+
425
474
  function validPressureRecord(v: unknown): v is SupervisorPressureRecord {
426
475
  if (
427
476
  !object(v) ||
@@ -225,19 +225,13 @@ export async function ensureSemanticServiceRunning(
225
225
 
226
226
  export function requestSemanticServiceStop(coordRootRaw: string): SemanticServiceStatus {
227
227
  const coordRoot = resolve(coordRootRaw);
228
- const status = readSemanticServiceStatus(coordRoot);
229
228
  mkdirSync(semanticPaths(coordRoot).root, { recursive: true, mode: stateDirMode() });
230
229
  writePrivateJsonAtomic(semanticPaths(coordRoot).stop, {
231
230
  requested_at: new Date().toISOString(),
232
231
  requested_by_pid: process.pid,
233
232
  });
234
- if (status.running && status.record?.host === hostname()) {
235
- try {
236
- process.kill(status.record.pid, "SIGTERM");
237
- } catch {
238
- // The durable stop request remains for a racing or restarted daemon.
239
- }
240
- }
233
+ // This record has no generation witness. The durable request is the stop
234
+ // authority; a numeric PID cannot authorize an additional signal.
241
235
  return readSemanticServiceStatus(coordRoot);
242
236
  }
243
237