harnery 0.36.0 → 0.38.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 (432) hide show
  1. package/dist/commander.d.ts +9 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +10 -1
  4. package/dist/commands/admission.d.ts +21 -0
  5. package/dist/commands/admission.d.ts.map +1 -0
  6. package/dist/commands/admission.js +565 -0
  7. package/dist/commands/agents.d.ts +7 -0
  8. package/dist/commands/agents.d.ts.map +1 -1
  9. package/dist/commands/agents.js +148 -47
  10. package/dist/commands/artifacts.d.ts.map +1 -1
  11. package/dist/commands/artifacts.js +73 -2
  12. package/dist/commands/browse-ai.d.ts +2 -2
  13. package/dist/commands/browse-ai.d.ts.map +1 -1
  14. package/dist/commands/browse-ai.js +6 -4
  15. package/dist/commands/browse.d.ts.map +1 -1
  16. package/dist/commands/browse.js +360 -21
  17. package/dist/commands/diagnostics.d.ts +4 -0
  18. package/dist/commands/diagnostics.d.ts.map +1 -0
  19. package/dist/commands/diagnostics.js +229 -0
  20. package/dist/commands/fetch.js +1 -0
  21. package/dist/commands/files.d.ts +12 -0
  22. package/dist/commands/files.d.ts.map +1 -1
  23. package/dist/commands/files.js +54 -1
  24. package/dist/commands/governor.d.ts.map +1 -1
  25. package/dist/commands/governor.js +4 -0
  26. package/dist/commands/ledger-v3.d.ts.map +1 -1
  27. package/dist/commands/ledger-v3.js +71 -2
  28. package/dist/commands/qa-record.d.ts +144 -0
  29. package/dist/commands/qa-record.d.ts.map +1 -0
  30. package/dist/commands/qa-record.js +0 -0
  31. package/dist/commands/qa-run.d.ts +8 -0
  32. package/dist/commands/qa-run.d.ts.map +1 -0
  33. package/dist/commands/qa-run.js +396 -0
  34. package/dist/commands/qa-status.d.ts +71 -0
  35. package/dist/commands/qa-status.d.ts.map +1 -0
  36. package/dist/commands/qa-status.js +490 -0
  37. package/dist/commands/qa-verify.d.ts +40 -0
  38. package/dist/commands/qa-verify.d.ts.map +1 -0
  39. package/dist/commands/qa-verify.js +180 -0
  40. package/dist/commands/resources.d.ts +4 -0
  41. package/dist/commands/resources.d.ts.map +1 -0
  42. package/dist/commands/resources.js +29 -0
  43. package/dist/commands/review-pack.d.ts +4 -0
  44. package/dist/commands/review-pack.d.ts.map +1 -0
  45. package/dist/commands/review-pack.js +1001 -0
  46. package/dist/commands/supervisor.d.ts +4 -0
  47. package/dist/commands/supervisor.d.ts.map +1 -0
  48. package/dist/commands/supervisor.js +101 -0
  49. package/dist/commands/web.d.ts.map +1 -1
  50. package/dist/commands/web.js +17 -0
  51. package/dist/commands/workflow.d.ts.map +1 -1
  52. package/dist/commands/workflow.js +4 -0
  53. package/dist/core/agents/cli.js +7 -3
  54. package/dist/core/agents/health.d.ts +17 -0
  55. package/dist/core/agents/health.d.ts.map +1 -0
  56. package/dist/core/agents/health.js +109 -0
  57. package/dist/core/agents/mailbox.d.ts +92 -0
  58. package/dist/core/agents/mailbox.d.ts.map +1 -0
  59. package/dist/core/agents/mailbox.js +325 -0
  60. package/dist/core/agents/qa-signal.d.ts +111 -0
  61. package/dist/core/agents/qa-signal.d.ts.map +1 -0
  62. package/dist/core/agents/qa-signal.js +231 -0
  63. package/dist/core/agents/reconcile-coordination-v3.d.ts +44 -0
  64. package/dist/core/agents/reconcile-coordination-v3.d.ts.map +1 -0
  65. package/dist/core/agents/reconcile-coordination-v3.js +58 -0
  66. package/dist/core/agents/render/prompt-context.d.ts +5 -0
  67. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  68. package/dist/core/agents/render/prompt-context.js +34 -13
  69. package/dist/core/agents/render/session-context.d.ts +8 -0
  70. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  71. package/dist/core/agents/render/session-context.js +34 -3
  72. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  73. package/dist/core/agents/rules/commit-conflict.js +50 -1
  74. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  75. package/dist/core/agents/rules/stop-hook.js +6 -10
  76. package/dist/core/agents/session-finalizer-v3.js +4 -1
  77. package/dist/core/agents/session-name-display.d.ts +20 -5
  78. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  79. package/dist/core/agents/session-name-display.js +67 -7
  80. package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
  81. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  82. package/dist/core/agents/state/heartbeat-writer.d.ts +11 -0
  83. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  84. package/dist/core/agents/state/heartbeat-writer.js +17 -0
  85. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  86. package/dist/core/agents/state/live-coordination-view.js +1 -0
  87. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  88. package/dist/core/artifacts/constants.d.ts +1 -1
  89. package/dist/core/artifacts/constants.js +1 -1
  90. package/dist/core/artifacts/index.d.ts +82 -6
  91. package/dist/core/artifacts/index.d.ts.map +1 -1
  92. package/dist/core/artifacts/index.js +410 -15
  93. package/dist/core/config.d.ts +20 -0
  94. package/dist/core/config.d.ts.map +1 -1
  95. package/dist/core/config.js +59 -0
  96. package/dist/core/diagnostics/advice.d.ts +9 -0
  97. package/dist/core/diagnostics/advice.d.ts.map +1 -0
  98. package/dist/core/diagnostics/advice.js +112 -0
  99. package/dist/core/diagnostics/bundle.d.ts +51 -0
  100. package/dist/core/diagnostics/bundle.d.ts.map +1 -0
  101. package/dist/core/diagnostics/bundle.js +968 -0
  102. package/dist/core/diagnostics/comparison.d.ts +5 -0
  103. package/dist/core/diagnostics/comparison.d.ts.map +1 -0
  104. package/dist/core/diagnostics/comparison.js +444 -0
  105. package/dist/core/diagnostics/contract.d.ts +245 -0
  106. package/dist/core/diagnostics/contract.d.ts.map +1 -0
  107. package/dist/core/diagnostics/contract.js +18 -0
  108. package/dist/core/diagnostics/identity.d.ts +3 -0
  109. package/dist/core/diagnostics/identity.d.ts.map +1 -0
  110. package/dist/core/diagnostics/identity.js +10 -0
  111. package/dist/core/diagnostics/index.d.ts +8 -0
  112. package/dist/core/diagnostics/index.d.ts.map +1 -0
  113. package/dist/core/diagnostics/index.js +7 -0
  114. package/dist/core/diagnostics/replay.d.ts +8 -0
  115. package/dist/core/diagnostics/replay.d.ts.map +1 -0
  116. package/dist/core/diagnostics/replay.js +213 -0
  117. package/dist/core/diagnostics/sanitize.d.ts +9 -0
  118. package/dist/core/diagnostics/sanitize.d.ts.map +1 -0
  119. package/dist/core/diagnostics/sanitize.js +84 -0
  120. package/dist/core/events/legacy-storage/compression.d.ts +13 -0
  121. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -0
  122. package/dist/core/events/legacy-storage/compression.js +107 -0
  123. package/dist/core/events/legacy-storage/index.d.ts +1 -0
  124. package/dist/core/events/legacy-storage/index.d.ts.map +1 -1
  125. package/dist/core/events/legacy-storage/index.js +1 -0
  126. package/dist/core/events/v3/archive-retention.d.ts +33 -0
  127. package/dist/core/events/v3/archive-retention.d.ts.map +1 -0
  128. package/dist/core/events/v3/archive-retention.js +195 -0
  129. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  130. package/dist/core/events/v3/bootstrap.js +10 -0
  131. package/dist/core/events/v3/capabilities.d.ts +1 -1
  132. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  133. package/dist/core/events/v3/capabilities.js +1 -0
  134. package/dist/core/events/v3/coordination-view.d.ts +3 -0
  135. package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
  136. package/dist/core/events/v3/coordination-view.js +69 -8
  137. package/dist/core/events/v3/index.d.ts +1 -0
  138. package/dist/core/events/v3/index.d.ts.map +1 -1
  139. package/dist/core/events/v3/index.js +1 -0
  140. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  141. package/dist/core/events/v3/live-routing.js +1 -0
  142. package/dist/core/events/v3/producers/hook-base.d.ts +1 -1
  143. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  144. package/dist/core/events/v3/producers/hook-base.js +13 -2
  145. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  146. package/dist/core/events/v3/producers/intake.js +10 -2
  147. package/dist/core/events/v3/producers/recorder.d.ts +39 -0
  148. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  149. package/dist/core/events/v3/producers/recorder.js +350 -17
  150. package/dist/core/hooks/adapter/output.d.ts +8 -0
  151. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  152. package/dist/core/hooks/adapter/output.js +11 -1
  153. package/dist/core/hooks/cli.js +303 -219
  154. package/dist/core/hooks/health.d.ts +59 -0
  155. package/dist/core/hooks/health.d.ts.map +1 -0
  156. package/dist/core/hooks/health.js +84 -0
  157. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  158. package/dist/core/hooks/resolve/transcript.js +10 -3
  159. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  160. package/dist/core/hooks/session-name-presence.js +4 -1
  161. package/dist/core/qa-artifacts.d.ts +20 -0
  162. package/dist/core/qa-artifacts.d.ts.map +1 -0
  163. package/dist/core/qa-artifacts.js +110 -0
  164. package/dist/core/resources/contract.d.ts +96 -0
  165. package/dist/core/resources/contract.d.ts.map +1 -0
  166. package/dist/core/resources/contract.js +2 -0
  167. package/dist/core/resources/index.d.ts +6 -0
  168. package/dist/core/resources/index.d.ts.map +1 -0
  169. package/dist/core/resources/index.js +5 -0
  170. package/dist/core/resources/sampler.d.ts +34 -0
  171. package/dist/core/resources/sampler.d.ts.map +1 -0
  172. package/dist/core/resources/sampler.js +458 -0
  173. package/dist/core/resources/service-status.d.ts +3 -0
  174. package/dist/core/resources/service-status.d.ts.map +1 -0
  175. package/dist/core/resources/service-status.js +48 -0
  176. package/dist/core/resources/service.d.ts +26 -0
  177. package/dist/core/resources/service.d.ts.map +1 -0
  178. package/dist/core/resources/service.js +292 -0
  179. package/dist/core/resources/storage.d.ts +13 -0
  180. package/dist/core/resources/storage.d.ts.map +1 -0
  181. package/dist/core/resources/storage.js +30 -0
  182. package/dist/core/storage/atomic-json.d.ts +3 -0
  183. package/dist/core/storage/atomic-json.d.ts.map +1 -0
  184. package/dist/core/storage/atomic-json.js +15 -0
  185. package/dist/core/storage/builtins.d.ts.map +1 -1
  186. package/dist/core/storage/builtins.js +45 -6
  187. package/dist/core/storage/logger.d.ts +2 -0
  188. package/dist/core/storage/logger.d.ts.map +1 -1
  189. package/dist/core/storage/logger.js +2 -0
  190. package/dist/core/storage/query.d.ts +14 -0
  191. package/dist/core/storage/query.d.ts.map +1 -1
  192. package/dist/core/storage/query.js +51 -0
  193. package/dist/core/supervisor/activity.d.ts +4 -0
  194. package/dist/core/supervisor/activity.d.ts.map +1 -0
  195. package/dist/core/supervisor/activity.js +141 -0
  196. package/dist/core/supervisor/contract.d.ts +268 -0
  197. package/dist/core/supervisor/contract.d.ts.map +1 -0
  198. package/dist/core/supervisor/contract.js +35 -0
  199. package/dist/core/supervisor/explanations.d.ts +4 -0
  200. package/dist/core/supervisor/explanations.d.ts.map +1 -0
  201. package/dist/core/supervisor/explanations.js +71 -0
  202. package/dist/core/supervisor/findings.d.ts +20 -0
  203. package/dist/core/supervisor/findings.d.ts.map +1 -0
  204. package/dist/core/supervisor/findings.js +399 -0
  205. package/dist/core/supervisor/history.d.ts +9 -0
  206. package/dist/core/supervisor/history.d.ts.map +1 -0
  207. package/dist/core/supervisor/history.js +46 -0
  208. package/dist/core/supervisor/hook-health-alerts.d.ts +14 -0
  209. package/dist/core/supervisor/hook-health-alerts.d.ts.map +1 -0
  210. package/dist/core/supervisor/hook-health-alerts.js +70 -0
  211. package/dist/core/supervisor/hook-health-storage.d.ts +5 -0
  212. package/dist/core/supervisor/hook-health-storage.d.ts.map +1 -0
  213. package/dist/core/supervisor/hook-health-storage.js +24 -0
  214. package/dist/core/supervisor/hook-health.d.ts +72 -0
  215. package/dist/core/supervisor/hook-health.d.ts.map +1 -0
  216. package/dist/core/supervisor/hook-health.js +203 -0
  217. package/dist/core/supervisor/hooks.d.ts +10 -0
  218. package/dist/core/supervisor/hooks.d.ts.map +1 -0
  219. package/dist/core/supervisor/hooks.js +28 -0
  220. package/dist/core/supervisor/index.d.ts +15 -0
  221. package/dist/core/supervisor/index.d.ts.map +1 -0
  222. package/dist/core/supervisor/index.js +14 -0
  223. package/dist/core/supervisor/log-feed.d.ts +9 -0
  224. package/dist/core/supervisor/log-feed.d.ts.map +1 -0
  225. package/dist/core/supervisor/log-feed.js +103 -0
  226. package/dist/core/supervisor/service.d.ts +29 -0
  227. package/dist/core/supervisor/service.d.ts.map +1 -0
  228. package/dist/core/supervisor/service.js +450 -0
  229. package/dist/core/supervisor/services.d.ts +11 -0
  230. package/dist/core/supervisor/services.d.ts.map +1 -0
  231. package/dist/core/supervisor/services.js +155 -0
  232. package/dist/core/supervisor/status.d.ts +3 -0
  233. package/dist/core/supervisor/status.d.ts.map +1 -0
  234. package/dist/core/supervisor/status.js +48 -0
  235. package/dist/core/supervisor/storage.d.ts +30 -0
  236. package/dist/core/supervisor/storage.d.ts.map +1 -0
  237. package/dist/core/supervisor/storage.js +73 -0
  238. package/dist/core/supervisor/timeline.d.ts +3 -0
  239. package/dist/core/supervisor/timeline.d.ts.map +1 -0
  240. package/dist/core/supervisor/timeline.js +87 -0
  241. package/dist/core/workflow/admission.d.ts +23 -0
  242. package/dist/core/workflow/admission.d.ts.map +1 -0
  243. package/dist/core/workflow/admission.js +136 -0
  244. package/dist/core/workflow/engine.d.ts.map +1 -1
  245. package/dist/core/workflow/engine.js +77 -2
  246. package/dist/core/workflow/index.d.ts +3 -2
  247. package/dist/core/workflow/index.d.ts.map +1 -1
  248. package/dist/core/workflow/index.js +2 -1
  249. package/dist/core/workflow/proof.d.ts +2 -1
  250. package/dist/core/workflow/proof.d.ts.map +1 -1
  251. package/dist/core/workflow/proof.js +39 -1
  252. package/dist/core/workflow/run-state.d.ts +3 -2
  253. package/dist/core/workflow/run-state.d.ts.map +1 -1
  254. package/dist/core/workflow/run-state.js +8 -1
  255. package/dist/core/workflow/types.d.ts +33 -1
  256. package/dist/core/workflow/types.d.ts.map +1 -1
  257. package/dist/core/workflow/types.js +2 -1
  258. package/dist/lib/admission.d.ts +71 -0
  259. package/dist/lib/admission.d.ts.map +1 -0
  260. package/dist/lib/admission.js +264 -0
  261. package/dist/lib/agent-browser/client.d.ts +1 -1
  262. package/dist/lib/agent-browser/client.d.ts.map +1 -1
  263. package/dist/lib/agent-browser/client.js +1 -5
  264. package/dist/lib/browser/capture-fidelity.d.ts +39 -0
  265. package/dist/lib/browser/capture-fidelity.d.ts.map +1 -0
  266. package/dist/lib/browser/capture-fidelity.js +84 -0
  267. package/dist/lib/browser/client.d.ts +46 -1
  268. package/dist/lib/browser/client.d.ts.map +1 -1
  269. package/dist/lib/browser/client.js +168 -9
  270. package/dist/lib/browser/critique.d.ts +38 -1
  271. package/dist/lib/browser/critique.d.ts.map +1 -1
  272. package/dist/lib/browser/critique.js +34 -6
  273. package/dist/lib/browser/index.d.ts +5 -1
  274. package/dist/lib/browser/index.d.ts.map +1 -1
  275. package/dist/lib/browser/index.js +4 -0
  276. package/dist/lib/browser/page-review-judge.d.ts +64 -0
  277. package/dist/lib/browser/page-review-judge.d.ts.map +1 -0
  278. package/dist/lib/browser/page-review-judge.js +270 -0
  279. package/dist/lib/browser/page-review-pack.d.ts +613 -0
  280. package/dist/lib/browser/page-review-pack.d.ts.map +1 -0
  281. package/dist/lib/browser/page-review-pack.js +1751 -0
  282. package/dist/lib/browser/qa-run-contracts.d.ts +377 -0
  283. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -0
  284. package/dist/lib/browser/qa-run-contracts.js +361 -0
  285. package/dist/lib/browser/qa-run.d.ts +141 -0
  286. package/dist/lib/browser/qa-run.d.ts.map +1 -0
  287. package/dist/lib/browser/qa-run.js +1283 -0
  288. package/dist/lib/browser/request-diagnostics.d.ts +13 -0
  289. package/dist/lib/browser/request-diagnostics.d.ts.map +1 -0
  290. package/dist/lib/browser/request-diagnostics.js +18 -0
  291. package/dist/lib/browser/tiling.d.ts +19 -0
  292. package/dist/lib/browser/tiling.d.ts.map +1 -1
  293. package/dist/lib/browser/tiling.js +28 -0
  294. package/dist/lib/cookies/client.d.ts +9 -0
  295. package/dist/lib/cookies/client.d.ts.map +1 -1
  296. package/dist/lib/cookies/client.js +197 -44
  297. package/dist/lib/cookies/extra.d.ts +18 -0
  298. package/dist/lib/cookies/extra.d.ts.map +1 -0
  299. package/dist/lib/cookies/extra.js +14 -0
  300. package/dist/lib/cookies/index.d.ts +2 -1
  301. package/dist/lib/cookies/index.d.ts.map +1 -1
  302. package/dist/lib/cookies/index.js +2 -1
  303. package/dist/lib/coord-root-id.d.ts +9 -0
  304. package/dist/lib/coord-root-id.d.ts.map +1 -0
  305. package/dist/lib/coord-root-id.js +17 -0
  306. package/dist/lib/durable-job.d.ts +124 -0
  307. package/dist/lib/durable-job.d.ts.map +1 -0
  308. package/dist/lib/durable-job.js +296 -0
  309. package/dist/lib/http/client.d.ts +7 -1
  310. package/dist/lib/http/client.d.ts.map +1 -1
  311. package/dist/lib/http/client.js +2 -0
  312. package/dist/lib/instructions/apply.d.ts +3 -1
  313. package/dist/lib/instructions/apply.d.ts.map +1 -1
  314. package/dist/lib/instructions/apply.js +74 -3
  315. package/dist/lib/instructions/templates.d.ts.map +1 -1
  316. package/dist/lib/instructions/templates.js +11 -2
  317. package/package.json +19 -2
  318. package/src/commander.ts +75 -1
  319. package/src/commands/admission.ts +699 -0
  320. package/src/commands/agents.ts +189 -50
  321. package/src/commands/artifacts.ts +132 -18
  322. package/src/commands/browse-ai.ts +10 -5
  323. package/src/commands/browse.ts +496 -20
  324. package/src/commands/diagnostics.ts +271 -0
  325. package/src/commands/fetch.ts +1 -0
  326. package/src/commands/files.ts +102 -2
  327. package/src/commands/governor.ts +8 -0
  328. package/src/commands/ledger-v3.ts +82 -0
  329. package/src/commands/qa-record.ts +682 -0
  330. package/src/commands/qa-run.ts +503 -0
  331. package/src/commands/qa-status.ts +608 -0
  332. package/src/commands/qa-verify.ts +238 -0
  333. package/src/commands/resources.ts +38 -0
  334. package/src/commands/review-pack.ts +1281 -0
  335. package/src/commands/supervisor.ts +141 -0
  336. package/src/commands/web.ts +19 -0
  337. package/src/commands/workflow.ts +8 -0
  338. package/src/core/agents/cli.ts +7 -3
  339. package/src/core/agents/health.ts +142 -0
  340. package/src/core/agents/mailbox.ts +373 -0
  341. package/src/core/agents/qa-signal.ts +261 -0
  342. package/src/core/agents/reconcile-coordination-v3.ts +77 -0
  343. package/src/core/agents/render/prompt-context.ts +42 -12
  344. package/src/core/agents/render/session-context.ts +37 -3
  345. package/src/core/agents/rules/commit-conflict.ts +58 -1
  346. package/src/core/agents/rules/stop-hook.ts +6 -12
  347. package/src/core/agents/session-finalizer-v3.ts +4 -1
  348. package/src/core/agents/session-name-display.ts +78 -7
  349. package/src/core/agents/state/heartbeat-reader.ts +7 -0
  350. package/src/core/agents/state/heartbeat-writer.ts +23 -0
  351. package/src/core/agents/state/live-coordination-view.ts +1 -0
  352. package/src/core/agents/state/live-coordination-writer.ts +5 -0
  353. package/src/core/artifacts/constants.ts +1 -1
  354. package/src/core/artifacts/index.ts +565 -30
  355. package/src/core/config.ts +134 -2
  356. package/src/core/diagnostics/advice.ts +147 -0
  357. package/src/core/diagnostics/bundle.ts +1250 -0
  358. package/src/core/diagnostics/comparison.ts +565 -0
  359. package/src/core/diagnostics/contract.ts +311 -0
  360. package/src/core/diagnostics/identity.ts +11 -0
  361. package/src/core/diagnostics/index.ts +7 -0
  362. package/src/core/diagnostics/replay.ts +314 -0
  363. package/src/core/diagnostics/sanitize.ts +97 -0
  364. package/src/core/events/legacy-storage/compression.ts +130 -0
  365. package/src/core/events/legacy-storage/index.ts +1 -0
  366. package/src/core/events/v3/archive-retention.ts +251 -0
  367. package/src/core/events/v3/bootstrap.ts +10 -0
  368. package/src/core/events/v3/capabilities.ts +2 -0
  369. package/src/core/events/v3/coordination-view.ts +100 -11
  370. package/src/core/events/v3/index.ts +9 -0
  371. package/src/core/events/v3/live-routing.ts +1 -0
  372. package/src/core/events/v3/producers/hook-base.ts +14 -2
  373. package/src/core/events/v3/producers/intake.ts +10 -2
  374. package/src/core/events/v3/producers/recorder.ts +420 -17
  375. package/src/core/hooks/adapter/output.ts +12 -1
  376. package/src/core/hooks/cli.ts +332 -247
  377. package/src/core/hooks/health.ts +146 -0
  378. package/src/core/hooks/resolve/transcript.ts +10 -3
  379. package/src/core/hooks/session-name-presence.ts +6 -1
  380. package/src/core/qa-artifacts.ts +126 -0
  381. package/src/core/resources/contract.ts +105 -0
  382. package/src/core/resources/index.ts +5 -0
  383. package/src/core/resources/sampler.ts +590 -0
  384. package/src/core/resources/service-status.ts +62 -0
  385. package/src/core/resources/service.ts +337 -0
  386. package/src/core/resources/storage.ts +44 -0
  387. package/src/core/storage/atomic-json.ts +15 -0
  388. package/src/core/storage/builtins.ts +62 -6
  389. package/src/core/storage/logger.ts +2 -0
  390. package/src/core/storage/query.ts +58 -0
  391. package/src/core/supervisor/activity.ts +193 -0
  392. package/src/core/supervisor/contract.ts +312 -0
  393. package/src/core/supervisor/explanations.ts +104 -0
  394. package/src/core/supervisor/findings.ts +788 -0
  395. package/src/core/supervisor/history.ts +61 -0
  396. package/src/core/supervisor/hook-health-alerts.ts +108 -0
  397. package/src/core/supervisor/hook-health-storage.ts +25 -0
  398. package/src/core/supervisor/hook-health.ts +301 -0
  399. package/src/core/supervisor/hooks.ts +35 -0
  400. package/src/core/supervisor/index.ts +14 -0
  401. package/src/core/supervisor/log-feed.ts +131 -0
  402. package/src/core/supervisor/service.ts +511 -0
  403. package/src/core/supervisor/services.ts +203 -0
  404. package/src/core/supervisor/status.ts +59 -0
  405. package/src/core/supervisor/storage.ts +126 -0
  406. package/src/core/supervisor/timeline.ts +133 -0
  407. package/src/core/workflow/admission.ts +215 -0
  408. package/src/core/workflow/engine.ts +93 -1
  409. package/src/core/workflow/index.ts +10 -0
  410. package/src/core/workflow/proof.ts +54 -1
  411. package/src/core/workflow/run-state.ts +13 -1
  412. package/src/core/workflow/types.ts +39 -1
  413. package/src/lib/admission.ts +347 -0
  414. package/src/lib/agent-browser/client.ts +2 -10
  415. package/src/lib/browser/capture-fidelity.ts +98 -0
  416. package/src/lib/browser/client.ts +212 -10
  417. package/src/lib/browser/critique.ts +62 -7
  418. package/src/lib/browser/index.ts +65 -0
  419. package/src/lib/browser/page-review-judge.ts +360 -0
  420. package/src/lib/browser/page-review-pack.ts +2384 -0
  421. package/src/lib/browser/qa-run-contracts.ts +743 -0
  422. package/src/lib/browser/qa-run.ts +1462 -0
  423. package/src/lib/browser/request-diagnostics.ts +27 -0
  424. package/src/lib/browser/tiling.ts +32 -0
  425. package/src/lib/cookies/client.ts +228 -42
  426. package/src/lib/cookies/extra.ts +28 -0
  427. package/src/lib/cookies/index.ts +2 -0
  428. package/src/lib/coord-root-id.ts +23 -0
  429. package/src/lib/durable-job.ts +407 -0
  430. package/src/lib/http/client.ts +13 -1
  431. package/src/lib/instructions/apply.ts +81 -2
  432. package/src/lib/instructions/templates.ts +11 -2
@@ -0,0 +1,590 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, readdirSync, readFileSync, readlinkSync } from "node:fs";
3
+ import { cpus, loadavg, platform, release } from "node:os";
4
+ import { basename, join } from "node:path";
5
+ import { performance } from "node:perf_hooks";
6
+ import { readLiveCoordinationRows } from "../agents/state/live-coordination-view.ts";
7
+ import { parsePidmapRow } from "../agents/state/pidmap.ts";
8
+ import { checkPidToken } from "../agents/state/proc-start.ts";
9
+ import {
10
+ RESOURCE_SNAPSHOT_SCHEMA_VERSION,
11
+ type ResourceProcessGroup,
12
+ type ResourceProcessOwnerProof,
13
+ type ResourceProcessSample,
14
+ type ResourceSampleResult,
15
+ type ResourceSamplerState,
16
+ type ResourceSnapshot,
17
+ } from "./contract.ts";
18
+
19
+ const DEFAULT_CLOCK_TICKS = 100;
20
+ const DEFAULT_PAGE_SIZE = 4_096;
21
+ const DEFAULT_UNATTRIBUTED_CPU_FLOOR = 0.5;
22
+ const DEFAULT_UNATTRIBUTED_RSS_FLOOR = 64 * 1_024 * 1_024;
23
+ const DEFAULT_MAX_PROCESSES = 500;
24
+ const MAX_COMMAND_CHARS = 240;
25
+
26
+ interface LinuxProcessReading {
27
+ pid: number;
28
+ ppid: number;
29
+ state: string;
30
+ name: string;
31
+ command: string;
32
+ ticks: number;
33
+ startTicks: number;
34
+ rssBytes: number;
35
+ ageSeconds: number;
36
+ }
37
+
38
+ interface LinuxCpuReading {
39
+ total: number;
40
+ idle: number;
41
+ }
42
+
43
+ interface ResourceSamplerOptions {
44
+ procRoot?: string;
45
+ nowMs?: number;
46
+ clockTicks?: number;
47
+ pageSize?: number;
48
+ services?: readonly { pid: number; id: string }[];
49
+ sessionOwners?: readonly SessionOwner[];
50
+ unattributedCpuFloor?: number;
51
+ unattributedRssFloor?: number;
52
+ maxProcesses?: number;
53
+ }
54
+
55
+ interface SessionOwner {
56
+ nativeSessionId: string;
57
+ instanceId: string;
58
+ platform: string;
59
+ kind: string;
60
+ }
61
+
62
+ let cachedClockTicks: number | undefined;
63
+ let cachedPageSize: number | undefined;
64
+
65
+ export function sampleResources(
66
+ coordRoot: string,
67
+ previous?: ResourceSamplerState,
68
+ options: ResourceSamplerOptions = {},
69
+ ): ResourceSampleResult {
70
+ const started = performance.now();
71
+ const cpuBefore = process.cpuUsage();
72
+ const nowMs = options.nowMs ?? Date.now();
73
+ const currentPlatform = platform();
74
+ if (currentPlatform !== "linux") {
75
+ return {
76
+ snapshot: unsupportedSnapshot(currentPlatform, nowMs, started, cpuBefore),
77
+ };
78
+ }
79
+
80
+ const procRoot = options.procRoot ?? "/proc";
81
+ if (!existsSync(join(procRoot, "stat"))) {
82
+ return {
83
+ snapshot: unsupportedSnapshot(
84
+ currentPlatform,
85
+ nowMs,
86
+ started,
87
+ cpuBefore,
88
+ "procfs_unavailable",
89
+ ),
90
+ };
91
+ }
92
+
93
+ const clockTicks = options.clockTicks ?? systemClockTicks();
94
+ const pageSize = options.pageSize ?? systemPageSize();
95
+ const uptimeSeconds = parseUptime(readFileSync(join(procRoot, "uptime"), "utf8"));
96
+ const cpu = parseLinuxCpu(readFileSync(join(procRoot, "stat"), "utf8"));
97
+ const memory = parseMeminfo(readFileSync(join(procRoot, "meminfo"), "utf8"));
98
+ const readings = readLinuxProcesses(procRoot, clockTicks, pageSize, uptimeSeconds);
99
+ const liveSessionOwners = uniqueLiveCodexSessionOwners(
100
+ options.sessionOwners ??
101
+ readLiveCoordinationRows(coordRoot).map((row) => ({
102
+ nativeSessionId: row.native_session_id ?? "",
103
+ instanceId: row.instance_id,
104
+ platform: row.platform ?? "",
105
+ kind: row.kind ?? "",
106
+ })),
107
+ );
108
+ const ownership = readOwnershipAnchors(
109
+ coordRoot,
110
+ procRoot,
111
+ readings,
112
+ options.services ?? [],
113
+ liveSessionOwners,
114
+ previous?.process_owners,
115
+ );
116
+ const anchors = ownership.anchors;
117
+ const byPid = new Map(readings.map((reading) => [reading.pid, reading]));
118
+ const processTicks = new Map<string, number>();
119
+ const intervalMs = previous ? Math.max(0, nowMs - previous.sampled_at_ms) : null;
120
+ const totalTickDelta = previous ? Math.max(0, cpu.total - previous.cpu_total_ticks) : 0;
121
+ const logicalCpus = Math.max(1, cpus().length);
122
+ const processes = readings.map((reading): ResourceProcessSample => {
123
+ const identity = `${reading.pid}:${reading.startTicks}`;
124
+ processTicks.set(identity, reading.ticks);
125
+ const priorTicks = previous?.process_ticks.get(identity);
126
+ const processDelta =
127
+ priorTicks === undefined ? undefined : Math.max(0, reading.ticks - priorTicks);
128
+ const cpuPercent =
129
+ processDelta === undefined || totalTickDelta <= 0
130
+ ? null
131
+ : round1((processDelta / totalTickDelta) * logicalCpus * 100);
132
+ const ownership = resolveOwnership(reading.pid, byPid, anchors);
133
+ return {
134
+ pid: reading.pid,
135
+ ppid: reading.ppid,
136
+ start_id: identity,
137
+ state: reading.state,
138
+ name: reading.name,
139
+ command: reading.command,
140
+ cpu_percent: cpuPercent,
141
+ rss_bytes: reading.rssBytes,
142
+ age_seconds: round1(reading.ageSeconds),
143
+ owner_kind: ownership?.kind ?? "unattributed",
144
+ owner_id: ownership?.id ?? null,
145
+ owner_root_pid: ownership?.rootPid ?? null,
146
+ ...(ownership ? { owner_source: ownership.source } : {}),
147
+ };
148
+ });
149
+ const visible = processes
150
+ .filter(
151
+ (row) =>
152
+ row.owner_kind !== "unattributed" ||
153
+ (row.cpu_percent ?? 0) >=
154
+ (options.unattributedCpuFloor ?? DEFAULT_UNATTRIBUTED_CPU_FLOOR) ||
155
+ row.rss_bytes >= (options.unattributedRssFloor ?? DEFAULT_UNATTRIBUTED_RSS_FLOOR),
156
+ )
157
+ .sort(processSort)
158
+ .slice(0, options.maxProcesses ?? DEFAULT_MAX_PROCESSES);
159
+ const groups = aggregateGroups(visible);
160
+ const cpuPercent =
161
+ previous && totalTickDelta > 0
162
+ ? round1(
163
+ ((totalTickDelta - Math.max(0, cpu.idle - previous.cpu_idle_ticks)) / totalTickDelta) *
164
+ 100,
165
+ )
166
+ : null;
167
+ const memoryTotal = memory.MemTotal ?? null;
168
+ const memoryAvailable = memory.MemAvailable ?? memory.MemFree ?? null;
169
+ const memoryUsed =
170
+ memoryTotal !== null && memoryAvailable !== null
171
+ ? Math.max(0, memoryTotal - memoryAvailable)
172
+ : null;
173
+ const swapTotal = memory.SwapTotal ?? null;
174
+ const swapFree = memory.SwapFree ?? null;
175
+ const swapUsed =
176
+ swapTotal !== null && swapFree !== null ? Math.max(0, swapTotal - swapFree) : null;
177
+ const collectorCpu = process.cpuUsage(cpuBefore);
178
+ const snapshot: ResourceSnapshot = {
179
+ schema_version: RESOURCE_SNAPSHOT_SCHEMA_VERSION,
180
+ sampled_at: new Date(nowMs).toISOString(),
181
+ interval_ms: intervalMs,
182
+ sample_duration_ms: round1(performance.now() - started),
183
+ collector_cpu_ms: round1((collectorCpu.user + collectorCpu.system) / 1_000),
184
+ platform: currentPlatform,
185
+ namespace: /microsoft|wsl/i.test(release()) ? "wsl" : "host",
186
+ support: { state: "supported", sampler: "procfs" },
187
+ machine: {
188
+ cpu_percent: cpuPercent,
189
+ cpu_logical_count: logicalCpus,
190
+ load_average: loadavg() as [number, number, number],
191
+ memory_total_bytes: memoryTotal,
192
+ memory_available_bytes: memoryAvailable,
193
+ memory_used_bytes: memoryUsed,
194
+ memory_percent:
195
+ memoryUsed !== null && memoryTotal ? round1((memoryUsed / memoryTotal) * 100) : null,
196
+ swap_total_bytes: swapTotal,
197
+ swap_used_bytes: swapUsed,
198
+ process_count: readings.length,
199
+ },
200
+ groups,
201
+ processes: visible,
202
+ visible_process_count: visible.length,
203
+ omitted_process_count: Math.max(0, readings.length - visible.length),
204
+ unattributed_process_count: visible.filter((row) => row.owner_kind === "unattributed").length,
205
+ };
206
+ return {
207
+ snapshot,
208
+ state: {
209
+ sampled_at_ms: nowMs,
210
+ cpu_total_ticks: cpu.total,
211
+ cpu_idle_ticks: cpu.idle,
212
+ process_ticks: processTicks,
213
+ process_owners: ownership.processOwners,
214
+ },
215
+ };
216
+ }
217
+
218
+ export function parseLinuxProcessStat(line: string): {
219
+ pid: number;
220
+ name: string;
221
+ state: string;
222
+ ppid: number;
223
+ ticks: number;
224
+ startTicks: number;
225
+ rssPages: number;
226
+ } | null {
227
+ const open = line.indexOf("(");
228
+ const close = line.lastIndexOf(")");
229
+ if (open <= 0 || close <= open) return null;
230
+ const pid = Number(line.slice(0, open).trim());
231
+ const fields = line
232
+ .slice(close + 1)
233
+ .trim()
234
+ .split(/\s+/);
235
+ const state = fields[0] ?? "?";
236
+ const ppid = Number(fields[1]);
237
+ const userTicks = Number(fields[11]);
238
+ const systemTicks = Number(fields[12]);
239
+ const startTicks = Number(fields[19]);
240
+ const rssPages = Number(fields[21]);
241
+ if (![pid, ppid, userTicks, systemTicks, startTicks, rssPages].every(Number.isFinite))
242
+ return null;
243
+ return {
244
+ pid,
245
+ name: line.slice(open + 1, close),
246
+ state,
247
+ ppid,
248
+ ticks: userTicks + systemTicks,
249
+ startTicks,
250
+ rssPages: Math.max(0, rssPages),
251
+ };
252
+ }
253
+
254
+ export function redactCommand(args: readonly string[]): string {
255
+ const secretName =
256
+ /(?:^|[-_])(token|secret|password|passwd|api[-_]?key|authorization)(?:$|[-_])/i;
257
+ const output: string[] = [];
258
+ let redactNext = false;
259
+ for (const raw of args) {
260
+ let value = raw;
261
+ if (redactNext) {
262
+ output.push("<redacted>");
263
+ redactNext = false;
264
+ continue;
265
+ }
266
+ const equals = value.indexOf("=");
267
+ if (equals > 0 && secretName.test(value.slice(0, equals))) {
268
+ value = `${value.slice(0, equals + 1)}<redacted>`;
269
+ } else if (secretName.test(value.replace(/^--?/, ""))) {
270
+ redactNext = true;
271
+ }
272
+ value = value.replace(/(https?:\/\/)[^:@\s/]+:[^@\s/]+@/gi, "$1<redacted>@");
273
+ output.push(value.replace(/[\r\n\t]+/g, " "));
274
+ }
275
+ const joined = output.join(" ").trim();
276
+ return joined.length <= MAX_COMMAND_CHARS ? joined : `${joined.slice(0, MAX_COMMAND_CHARS - 1)}…`;
277
+ }
278
+
279
+ function readLinuxProcesses(
280
+ procRoot: string,
281
+ clockTicks: number,
282
+ pageSize: number,
283
+ uptimeSeconds: number,
284
+ ): LinuxProcessReading[] {
285
+ const rows: LinuxProcessReading[] = [];
286
+ for (const entry of readdirSync(procRoot, { withFileTypes: true })) {
287
+ if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) continue;
288
+ const pidRoot = join(procRoot, entry.name);
289
+ try {
290
+ const parsed = parseLinuxProcessStat(readFileSync(join(pidRoot, "stat"), "utf8"));
291
+ if (!parsed) continue;
292
+ const args = readFileSync(join(pidRoot, "cmdline"), "utf8").split("\0").filter(Boolean);
293
+ let name = parsed.name;
294
+ try {
295
+ name = basename(readlinkSync(join(pidRoot, "exe"))) || name;
296
+ } catch {
297
+ // Kernel threads and racing processes may have no readable executable.
298
+ }
299
+ rows.push({
300
+ pid: parsed.pid,
301
+ ppid: parsed.ppid,
302
+ state: parsed.state,
303
+ name,
304
+ command: redactCommand(args.length > 0 ? args : [parsed.name]),
305
+ ticks: parsed.ticks,
306
+ startTicks: parsed.startTicks,
307
+ rssBytes: parsed.rssPages * pageSize,
308
+ ageSeconds: Math.max(0, uptimeSeconds - parsed.startTicks / clockTicks),
309
+ });
310
+ } catch {
311
+ // Process exits and permission boundaries are normal during a table scan.
312
+ }
313
+ }
314
+ return rows;
315
+ }
316
+
317
+ interface OwnershipAnchor {
318
+ kind: "agent" | "service";
319
+ id: string;
320
+ source: "pid-map" | "session-environment" | "service";
321
+ }
322
+
323
+ function readOwnershipAnchors(
324
+ coordRoot: string,
325
+ procRoot: string,
326
+ readings: readonly LinuxProcessReading[],
327
+ services: readonly { pid: number; id: string }[],
328
+ liveSessionOwners: ReadonlyMap<string, string>,
329
+ previousOwners?: ReadonlyMap<string, ResourceProcessOwnerProof>,
330
+ ): {
331
+ anchors: Map<number, OwnershipAnchor>;
332
+ processOwners: Map<string, ResourceProcessOwnerProof>;
333
+ } {
334
+ const byPid = new Set(readings.map((row) => row.pid));
335
+ const directory = join(coordRoot, ".harnery", "pid-map");
336
+ const anchors = new Map<number, OwnershipAnchor>();
337
+ if (existsSync(directory)) {
338
+ for (const entry of readdirSync(directory)) {
339
+ if (!/^\d+$/.test(entry)) continue;
340
+ const pid = Number(entry);
341
+ if (!byPid.has(pid)) continue;
342
+ try {
343
+ const row = parsePidmapRow(readFileSync(join(directory, entry), "utf8"));
344
+ if (!row.instanceId || checkPidToken(pid, row.startToken) === "mismatch") continue;
345
+ anchors.set(pid, { kind: "agent", id: row.instanceId, source: "pid-map" });
346
+ } catch {
347
+ // An unreadable or racing row cannot prove ownership.
348
+ }
349
+ }
350
+ }
351
+
352
+ const processOwners = readBridgeProcessOwners(
353
+ procRoot,
354
+ readings,
355
+ liveSessionOwners,
356
+ previousOwners,
357
+ );
358
+ const byReadingPid = new Map(readings.map((row) => [row.pid, row]));
359
+ for (const reading of readings) {
360
+ const identity = `${reading.pid}:${reading.startTicks}`;
361
+ const owner = processOwners.get(identity);
362
+ if (!owner || anchors.has(reading.pid)) continue;
363
+ const parent = byReadingPid.get(reading.ppid);
364
+ const parentOwner = parent
365
+ ? processOwners.get(`${parent.pid}:${parent.startTicks}`)
366
+ : undefined;
367
+ if (parentOwner?.instance_id === owner.instance_id) continue;
368
+ anchors.set(reading.pid, {
369
+ kind: "agent",
370
+ id: owner.instance_id,
371
+ source: "session-environment",
372
+ });
373
+ }
374
+
375
+ for (const service of services) {
376
+ if (byPid.has(service.pid) && service.id.trim()) {
377
+ anchors.set(service.pid, { kind: "service", id: service.id.trim(), source: "service" });
378
+ }
379
+ }
380
+ return { anchors, processOwners };
381
+ }
382
+
383
+ function uniqueLiveCodexSessionOwners(rows: readonly SessionOwner[]): Map<string, string> {
384
+ const candidates = new Map<string, Set<string>>();
385
+ for (const row of rows) {
386
+ const sessionId = row.nativeSessionId.trim();
387
+ const instanceId = row.instanceId.trim();
388
+ if (row.platform !== "codex" || row.kind !== "session" || !sessionId || !instanceId) continue;
389
+ const owners = candidates.get(sessionId) ?? new Set<string>();
390
+ owners.add(instanceId);
391
+ candidates.set(sessionId, owners);
392
+ }
393
+ const result = new Map<string, string>();
394
+ for (const [sessionId, owners] of candidates) {
395
+ if (owners.size === 1) result.set(sessionId, [...owners][0]!);
396
+ }
397
+ return result;
398
+ }
399
+
400
+ function readBridgeProcessOwners(
401
+ procRoot: string,
402
+ readings: readonly LinuxProcessReading[],
403
+ liveSessionOwners: ReadonlyMap<string, string>,
404
+ previousOwners?: ReadonlyMap<string, ResourceProcessOwnerProof>,
405
+ ): Map<string, ResourceProcessOwnerProof> {
406
+ const result = new Map<string, ResourceProcessOwnerProof>();
407
+ if (liveSessionOwners.size === 0) return result;
408
+ for (const reading of readings) {
409
+ const identity = `${reading.pid}:${reading.startTicks}`;
410
+ const previous = previousOwners?.get(identity);
411
+ if (previous && liveSessionOwners.get(previous.session_id) === previous.instance_id) {
412
+ result.set(identity, previous);
413
+ continue;
414
+ }
415
+ const sessionId = readCodexWslBridgeSessionId(procRoot, reading.pid);
416
+ if (!sessionId) continue;
417
+ const instanceId = liveSessionOwners.get(sessionId);
418
+ if (instanceId) result.set(identity, { session_id: sessionId, instance_id: instanceId });
419
+ }
420
+ return result;
421
+ }
422
+
423
+ function readCodexWslBridgeSessionId(procRoot: string, pid: number): string | undefined {
424
+ try {
425
+ const entries = readFileSync(join(procRoot, String(pid), "environ"), "utf8").split("\0");
426
+ let bridge: string | undefined;
427
+ let platform: string | undefined;
428
+ let coordinationSessionId: string | undefined;
429
+ let codexThreadId: string | undefined;
430
+ for (const entry of entries) {
431
+ if (entry.startsWith("HARNERY_AGENT_COORD_BRIDGE=")) {
432
+ bridge = entry.slice("HARNERY_AGENT_COORD_BRIDGE=".length);
433
+ } else if (entry.startsWith("HARNERY_AGENT_COORD_PLATFORM=")) {
434
+ platform = entry.slice("HARNERY_AGENT_COORD_PLATFORM=".length);
435
+ } else if (entry.startsWith("HARNERY_AGENT_COORD_SESSION_ID=")) {
436
+ coordinationSessionId = entry.slice("HARNERY_AGENT_COORD_SESSION_ID=".length);
437
+ } else if (entry.startsWith("CODEX_THREAD_ID=")) {
438
+ codexThreadId = entry.slice("CODEX_THREAD_ID=".length);
439
+ }
440
+ }
441
+ if (
442
+ bridge === "codex-wsl" &&
443
+ platform === "codex" &&
444
+ coordinationSessionId &&
445
+ coordinationSessionId === codexThreadId
446
+ ) {
447
+ return coordinationSessionId;
448
+ }
449
+ } catch {
450
+ // An unreadable or racing environment cannot prove ownership.
451
+ }
452
+ return undefined;
453
+ }
454
+
455
+ function resolveOwnership(
456
+ pid: number,
457
+ byPid: ReadonlyMap<number, LinuxProcessReading>,
458
+ anchors: ReadonlyMap<number, OwnershipAnchor>,
459
+ ): (OwnershipAnchor & { rootPid: number }) | undefined {
460
+ const seen = new Set<number>();
461
+ let cursor = pid;
462
+ for (let hops = 0; hops < 64 && cursor > 0 && !seen.has(cursor); hops++) {
463
+ seen.add(cursor);
464
+ const owner = anchors.get(cursor);
465
+ if (owner) return { ...owner, rootPid: cursor };
466
+ cursor = byPid.get(cursor)?.ppid ?? 0;
467
+ }
468
+ return undefined;
469
+ }
470
+
471
+ function aggregateGroups(processes: readonly ResourceProcessSample[]): ResourceProcessGroup[] {
472
+ const groups = new Map<string, ResourceProcessGroup>();
473
+ for (const row of processes) {
474
+ const id = row.owner_id ?? "unattributed";
475
+ const key = `${row.owner_kind}:${id}`;
476
+ const group = groups.get(key) ?? {
477
+ kind: row.owner_kind,
478
+ id,
479
+ process_count: 0,
480
+ cpu_percent: null,
481
+ rss_bytes: 0,
482
+ root_pids: [],
483
+ };
484
+ group.process_count += 1;
485
+ group.rss_bytes += row.rss_bytes;
486
+ if (row.cpu_percent !== null)
487
+ group.cpu_percent = round1((group.cpu_percent ?? 0) + row.cpu_percent);
488
+ if (row.owner_root_pid !== null && !group.root_pids.includes(row.owner_root_pid)) {
489
+ group.root_pids.push(row.owner_root_pid);
490
+ }
491
+ groups.set(key, group);
492
+ }
493
+ return [...groups.values()].sort(
494
+ (left, right) =>
495
+ (right.cpu_percent ?? -1) - (left.cpu_percent ?? -1) ||
496
+ right.rss_bytes - left.rss_bytes ||
497
+ left.id.localeCompare(right.id),
498
+ );
499
+ }
500
+
501
+ function processSort(left: ResourceProcessSample, right: ResourceProcessSample): number {
502
+ return (
503
+ (right.cpu_percent ?? -1) - (left.cpu_percent ?? -1) ||
504
+ right.rss_bytes - left.rss_bytes ||
505
+ left.pid - right.pid
506
+ );
507
+ }
508
+
509
+ function parseLinuxCpu(content: string): LinuxCpuReading {
510
+ const fields = content.split("\n")[0]?.trim().split(/\s+/).slice(1).map(Number) ?? [];
511
+ if (fields.length < 4 || fields.some((value) => !Number.isFinite(value))) {
512
+ throw new Error("procfs_cpu_invalid");
513
+ }
514
+ return {
515
+ total: fields.reduce((sum, value) => sum + value, 0),
516
+ idle: (fields[3] ?? 0) + (fields[4] ?? 0),
517
+ };
518
+ }
519
+
520
+ function parseMeminfo(content: string): Record<string, number> {
521
+ const result: Record<string, number> = {};
522
+ for (const line of content.split("\n")) {
523
+ const match = /^([A-Za-z_()]+):\s+(\d+)\s+kB$/.exec(line.trim());
524
+ if (match) result[match[1]!] = Number(match[2]) * 1_024;
525
+ }
526
+ return result;
527
+ }
528
+
529
+ function parseUptime(content: string): number {
530
+ const value = Number(content.trim().split(/\s+/, 1)[0]);
531
+ if (!Number.isFinite(value) || value < 0) throw new Error("procfs_uptime_invalid");
532
+ return value;
533
+ }
534
+
535
+ function systemClockTicks(): number {
536
+ cachedClockTicks ??= positiveGetconf("CLK_TCK", DEFAULT_CLOCK_TICKS);
537
+ return cachedClockTicks;
538
+ }
539
+
540
+ function systemPageSize(): number {
541
+ cachedPageSize ??= positiveGetconf("PAGESIZE", DEFAULT_PAGE_SIZE);
542
+ return cachedPageSize;
543
+ }
544
+
545
+ function positiveGetconf(key: string, fallback: number): number {
546
+ const result = spawnSync("getconf", [key], { encoding: "utf8", timeout: 1_000 });
547
+ const value = result.status === 0 ? Number(result.stdout.trim()) : Number.NaN;
548
+ return Number.isFinite(value) && value > 0 ? value : fallback;
549
+ }
550
+
551
+ function unsupportedSnapshot(
552
+ currentPlatform: NodeJS.Platform,
553
+ nowMs: number,
554
+ started: number,
555
+ cpuBefore: NodeJS.CpuUsage,
556
+ reason = "platform_unsupported",
557
+ ): ResourceSnapshot {
558
+ const collectorCpu = process.cpuUsage(cpuBefore);
559
+ return {
560
+ schema_version: RESOURCE_SNAPSHOT_SCHEMA_VERSION,
561
+ sampled_at: new Date(nowMs).toISOString(),
562
+ interval_ms: null,
563
+ sample_duration_ms: round1(performance.now() - started),
564
+ collector_cpu_ms: round1((collectorCpu.user + collectorCpu.system) / 1_000),
565
+ platform: currentPlatform,
566
+ namespace: "unknown",
567
+ support: { state: "unsupported", sampler: "unsupported", reason },
568
+ machine: {
569
+ cpu_percent: null,
570
+ cpu_logical_count: cpus().length,
571
+ load_average: null,
572
+ memory_total_bytes: null,
573
+ memory_available_bytes: null,
574
+ memory_used_bytes: null,
575
+ memory_percent: null,
576
+ swap_total_bytes: null,
577
+ swap_used_bytes: null,
578
+ process_count: 0,
579
+ },
580
+ groups: [],
581
+ processes: [],
582
+ visible_process_count: 0,
583
+ omitted_process_count: 0,
584
+ unattributed_process_count: 0,
585
+ };
586
+ }
587
+
588
+ function round1(value: number): number {
589
+ return Math.round(value * 10) / 10;
590
+ }
@@ -0,0 +1,62 @@
1
+ import { hostname } from "node:os";
2
+ import { checkPidToken } from "../agents/state/proc-start.ts";
3
+ import {
4
+ RESOURCE_SERVICE_STATUS_SCHEMA_VERSION,
5
+ type ResourceServiceStatus,
6
+ type ResourceServiceStatusRecord,
7
+ } from "./contract.ts";
8
+ import { readResourceServiceRecord, resourcePaths } from "./storage.ts";
9
+
10
+ const STALE_HEARTBEAT_MS = 15_000;
11
+
12
+ export function readResourceServiceStatus(
13
+ coordRoot: string,
14
+ nowMs = Date.now(),
15
+ ): ResourceServiceStatus {
16
+ const paths = resourcePaths(coordRoot);
17
+ const record = readResourceServiceRecord(coordRoot);
18
+ if (!validRecord(record)) {
19
+ return {
20
+ running: false,
21
+ stale: false,
22
+ status_path: paths.service,
23
+ snapshot_path: paths.snapshot,
24
+ };
25
+ }
26
+ const heartbeatAt = Date.parse(record.heartbeat_at);
27
+ const stale = !Number.isFinite(heartbeatAt) || nowMs - heartbeatAt > STALE_HEARTBEAT_MS;
28
+ const local = record.host === hostname();
29
+ const running =
30
+ local &&
31
+ !stale &&
32
+ (record.state === "starting" || record.state === "running" || record.state === "stopping") &&
33
+ pidStillMatches(record.pid, record.start_token);
34
+ return {
35
+ running,
36
+ stale,
37
+ record,
38
+ status_path: paths.service,
39
+ snapshot_path: paths.snapshot,
40
+ };
41
+ }
42
+
43
+ function validRecord(
44
+ value: ResourceServiceStatusRecord | undefined,
45
+ ): value is ResourceServiceStatusRecord {
46
+ return (
47
+ value?.schema_version === RESOURCE_SERVICE_STATUS_SCHEMA_VERSION &&
48
+ Number.isInteger(value.pid) &&
49
+ value.pid > 0 &&
50
+ typeof value.host === "string" &&
51
+ typeof value.heartbeat_at === "string"
52
+ );
53
+ }
54
+
55
+ function pidStillMatches(pid: number, startToken: string | undefined): boolean {
56
+ try {
57
+ process.kill(pid, 0);
58
+ } catch (error) {
59
+ if ((error as NodeJS.ErrnoException).code !== "EPERM") return false;
60
+ }
61
+ return checkPidToken(pid, startToken) !== "mismatch";
62
+ }