harnery 0.38.0 → 0.40.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 (378) hide show
  1. package/dist/commands/agents.d.ts.map +1 -1
  2. package/dist/commands/agents.js +32 -8
  3. package/dist/commands/artifacts.d.ts.map +1 -1
  4. package/dist/commands/artifacts.js +97 -6
  5. package/dist/commands/backup.d.ts +16 -0
  6. package/dist/commands/backup.d.ts.map +1 -1
  7. package/dist/commands/backup.js +239 -33
  8. package/dist/commands/browse.d.ts +8 -0
  9. package/dist/commands/browse.d.ts.map +1 -1
  10. package/dist/commands/browse.js +466 -231
  11. package/dist/commands/diagnostics.d.ts.map +1 -1
  12. package/dist/commands/diagnostics.js +6 -3
  13. package/dist/commands/ledger-v3.d.ts.map +1 -1
  14. package/dist/commands/ledger-v3.js +20 -4
  15. package/dist/commands/qa-run.d.ts.map +1 -1
  16. package/dist/commands/qa-run.js +26 -3
  17. package/dist/commands/qa-status.d.ts.map +1 -1
  18. package/dist/commands/qa-status.js +7 -2
  19. package/dist/commands/resources.d.ts.map +1 -1
  20. package/dist/commands/resources.js +23 -3
  21. package/dist/commands/review-pack.d.ts.map +1 -1
  22. package/dist/commands/review-pack.js +98 -23
  23. package/dist/commands/tunnel.d.ts.map +1 -1
  24. package/dist/commands/tunnel.js +2 -2
  25. package/dist/core/adapters/profiles.d.ts +1 -1
  26. package/dist/core/agents/canonical-emit.d.ts +3 -2
  27. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  28. package/dist/core/agents/canonical-emit.js +2 -0
  29. package/dist/core/agents/cli.js +13 -6
  30. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  31. package/dist/core/agents/live-authority-v3.js +5 -3
  32. package/dist/core/agents/live-lifecycle-v3.d.ts +2 -2
  33. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  34. package/dist/core/agents/render/prompt-context.d.ts +7 -5
  35. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  36. package/dist/core/agents/render/prompt-context.js +63 -8
  37. package/dist/core/agents/session-finalizer-v3.d.ts +2 -2
  38. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
  39. package/dist/core/agents/state/live-coordination-view.d.ts +3 -3
  40. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  41. package/dist/core/agents/state/live-coordination-view.js +2 -1
  42. package/dist/core/agents/state/names.d.ts +35 -2
  43. package/dist/core/agents/state/names.d.ts.map +1 -1
  44. package/dist/core/agents/state/names.js +80 -5
  45. package/dist/core/agents/state/stale-sweep.d.ts +2 -1
  46. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  47. package/dist/core/agents/state/stale-sweep.js +23 -6
  48. package/dist/core/agents/turn-elapsed.d.ts +51 -0
  49. package/dist/core/agents/turn-elapsed.d.ts.map +1 -0
  50. package/dist/core/agents/turn-elapsed.js +91 -0
  51. package/dist/core/artifacts/activity.d.ts +14 -0
  52. package/dist/core/artifacts/activity.d.ts.map +1 -0
  53. package/dist/core/artifacts/activity.js +52 -0
  54. package/dist/core/artifacts/delivery-card.d.ts +39 -0
  55. package/dist/core/artifacts/delivery-card.d.ts.map +1 -0
  56. package/dist/core/artifacts/delivery-card.js +282 -0
  57. package/dist/core/artifacts/index.d.ts +42 -7
  58. package/dist/core/artifacts/index.d.ts.map +1 -1
  59. package/dist/core/artifacts/index.js +288 -76
  60. package/dist/core/artifacts/mutation-lock.d.ts +2 -0
  61. package/dist/core/artifacts/mutation-lock.d.ts.map +1 -0
  62. package/dist/core/artifacts/mutation-lock.js +91 -0
  63. package/dist/core/backup/host-snapshot.d.ts +18 -0
  64. package/dist/core/backup/host-snapshot.d.ts.map +1 -0
  65. package/dist/core/backup/host-snapshot.js +64 -0
  66. package/dist/core/config.d.ts +20 -5
  67. package/dist/core/config.d.ts.map +1 -1
  68. package/dist/core/config.js +44 -10
  69. package/dist/core/diagnostics/advice.d.ts +38 -3
  70. package/dist/core/diagnostics/advice.d.ts.map +1 -1
  71. package/dist/core/diagnostics/advice.js +103 -99
  72. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  73. package/dist/core/diagnostics/bundle.js +111 -47
  74. package/dist/core/diagnostics/comparison.d.ts.map +1 -1
  75. package/dist/core/diagnostics/comparison.js +3 -2
  76. package/dist/core/diagnostics/contract.d.ts +15 -31
  77. package/dist/core/diagnostics/contract.d.ts.map +1 -1
  78. package/dist/core/diagnostics/contract.js +2 -5
  79. package/dist/core/diagnostics/index.d.ts +2 -0
  80. package/dist/core/diagnostics/index.d.ts.map +1 -1
  81. package/dist/core/diagnostics/index.js +2 -0
  82. package/dist/core/diagnostics/pressure-contract.d.ts +260 -0
  83. package/dist/core/diagnostics/pressure-contract.d.ts.map +1 -0
  84. package/dist/core/diagnostics/pressure-contract.js +70 -0
  85. package/dist/core/diagnostics/pressure.d.ts +25 -0
  86. package/dist/core/diagnostics/pressure.d.ts.map +1 -0
  87. package/dist/core/diagnostics/pressure.js +1138 -0
  88. package/dist/core/diagnostics/replay.d.ts.map +1 -1
  89. package/dist/core/diagnostics/replay.js +90 -10
  90. package/dist/core/events/v3/adapter-id.d.ts +6 -0
  91. package/dist/core/events/v3/adapter-id.d.ts.map +1 -0
  92. package/dist/core/events/v3/adapter-id.js +7 -0
  93. package/dist/core/events/v3/archive-retention.d.ts.map +1 -1
  94. package/dist/core/events/v3/archive-retention.js +3 -2
  95. package/dist/core/events/v3/bootstrap.d.ts +41 -4
  96. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  97. package/dist/core/events/v3/bootstrap.js +120 -17
  98. package/dist/core/events/v3/capabilities.d.ts +16 -9
  99. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  100. package/dist/core/events/v3/capabilities.js +37 -2
  101. package/dist/core/events/v3/capability-drift.d.ts +2 -2
  102. package/dist/core/events/v3/capability-drift.d.ts.map +1 -1
  103. package/dist/core/events/v3/control-witness.d.ts +48 -5
  104. package/dist/core/events/v3/control-witness.d.ts.map +1 -1
  105. package/dist/core/events/v3/control-witness.js +91 -36
  106. package/dist/core/events/v3/control.d.ts.map +1 -1
  107. package/dist/core/events/v3/control.js +22 -2
  108. package/dist/core/events/v3/index.d.ts +2 -1
  109. package/dist/core/events/v3/index.d.ts.map +1 -1
  110. package/dist/core/events/v3/index.js +2 -1
  111. package/dist/core/events/v3/live-feed.d.ts +7 -1
  112. package/dist/core/events/v3/live-feed.d.ts.map +1 -1
  113. package/dist/core/events/v3/live-feed.js +21 -3
  114. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  115. package/dist/core/events/v3/live-routing.js +10 -2
  116. package/dist/core/events/v3/producers/command-recorder.d.ts +2 -2
  117. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  118. package/dist/core/events/v3/producers/command-recorder.js +2 -1
  119. package/dist/core/events/v3/producers/coordination-recorder.d.ts +2 -2
  120. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  121. package/dist/core/events/v3/producers/coordination-recorder.js +2 -1
  122. package/dist/core/events/v3/producers/hook-base.d.ts +2 -2
  123. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  124. package/dist/core/events/v3/producers/hook-base.js +15 -5
  125. package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
  126. package/dist/core/events/v3/producers/hook.js +17 -4
  127. package/dist/core/events/v3/producers/intake.d.ts +4 -4
  128. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  129. package/dist/core/events/v3/producers/intake.js +3 -2
  130. package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -1
  131. package/dist/core/events/v3/producers/producer-state-observer.js +2 -1
  132. package/dist/core/events/v3/producers/recorder.d.ts +16 -5
  133. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  134. package/dist/core/events/v3/producers/recorder.js +31 -9
  135. package/dist/core/events/v3/reader.d.ts.map +1 -1
  136. package/dist/core/events/v3/reader.js +10 -13
  137. package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
  138. package/dist/core/events/v3/runtime-build.js +3 -2
  139. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +2 -2
  140. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -1
  141. package/dist/core/events/v3/turn-telemetry.d.ts +2 -2
  142. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
  143. package/dist/core/hooks/adapter/output.d.ts +16 -14
  144. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  145. package/dist/core/hooks/adapter/output.js +36 -38
  146. package/dist/core/hooks/backup-schedule.d.ts +34 -0
  147. package/dist/core/hooks/backup-schedule.d.ts.map +1 -0
  148. package/dist/core/hooks/backup-schedule.js +114 -0
  149. package/dist/core/hooks/cli.js +61 -18
  150. package/dist/core/hooks/codex-wsl-bridge.d.ts +2 -0
  151. package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -1
  152. package/dist/core/hooks/codex-wsl-bridge.js +18 -4
  153. package/dist/core/hooks/index.d.ts +1 -0
  154. package/dist/core/hooks/index.d.ts.map +1 -1
  155. package/dist/core/hooks/index.js +1 -0
  156. package/dist/core/hooks/prompt-context/contract.d.ts +61 -0
  157. package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -0
  158. package/dist/core/hooks/prompt-context/contract.js +102 -0
  159. package/dist/core/hooks/prompt-context/index.d.ts +5 -0
  160. package/dist/core/hooks/prompt-context/index.d.ts.map +1 -0
  161. package/dist/core/hooks/prompt-context/index.js +2 -0
  162. package/dist/core/hooks/prompt-context/runner.d.ts +24 -0
  163. package/dist/core/hooks/prompt-context/runner.d.ts.map +1 -0
  164. package/dist/core/hooks/prompt-context/runner.js +249 -0
  165. package/dist/core/qa-artifacts.d.ts +3 -0
  166. package/dist/core/qa-artifacts.d.ts.map +1 -1
  167. package/dist/core/qa-artifacts.js +25 -3
  168. package/dist/core/resources/contract.d.ts +69 -3
  169. package/dist/core/resources/contract.d.ts.map +1 -1
  170. package/dist/core/resources/contract.js +1 -1
  171. package/dist/core/resources/index.d.ts +1 -0
  172. package/dist/core/resources/index.d.ts.map +1 -1
  173. package/dist/core/resources/index.js +1 -0
  174. package/dist/core/resources/native.d.ts +36 -0
  175. package/dist/core/resources/native.d.ts.map +1 -0
  176. package/dist/core/resources/native.js +418 -0
  177. package/dist/core/resources/sampler.d.ts +2 -0
  178. package/dist/core/resources/sampler.d.ts.map +1 -1
  179. package/dist/core/resources/sampler.js +31 -1
  180. package/dist/core/resources/status.d.ts +61 -0
  181. package/dist/core/resources/status.d.ts.map +1 -0
  182. package/dist/core/resources/status.js +536 -0
  183. package/dist/core/resources/system.d.ts +27 -0
  184. package/dist/core/resources/system.d.ts.map +1 -0
  185. package/dist/core/resources/system.js +439 -0
  186. package/dist/core/semantic/contract.d.ts +13 -13
  187. package/dist/core/semantic/service.d.ts +2 -0
  188. package/dist/core/semantic/service.d.ts.map +1 -1
  189. package/dist/core/semantic/service.js +27 -4
  190. package/dist/core/storage/builtins.js +41 -0
  191. package/dist/core/supervisor/contract.d.ts +41 -3
  192. package/dist/core/supervisor/contract.d.ts.map +1 -1
  193. package/dist/core/supervisor/contract.js +3 -2
  194. package/dist/core/supervisor/findings.d.ts +3 -0
  195. package/dist/core/supervisor/findings.d.ts.map +1 -1
  196. package/dist/core/supervisor/findings.js +88 -0
  197. package/dist/core/supervisor/history.d.ts.map +1 -1
  198. package/dist/core/supervisor/history.js +14 -0
  199. package/dist/core/supervisor/ledger-state.d.ts +55 -0
  200. package/dist/core/supervisor/ledger-state.d.ts.map +1 -0
  201. package/dist/core/supervisor/ledger-state.js +0 -0
  202. package/dist/core/supervisor/service.d.ts.map +1 -1
  203. package/dist/core/supervisor/service.js +120 -7
  204. package/dist/core/supervisor/storage.d.ts +3 -1
  205. package/dist/core/supervisor/storage.d.ts.map +1 -1
  206. package/dist/core/supervisor/storage.js +4 -0
  207. package/dist/core/workflow/admission.d.ts +3 -0
  208. package/dist/core/workflow/admission.d.ts.map +1 -1
  209. package/dist/core/workflow/admission.js +28 -16
  210. package/dist/core/workflow/engine.js +2 -2
  211. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  212. package/dist/core/workflow/live-session-v3.js +6 -1
  213. package/dist/core/workflow/proof.d.ts.map +1 -1
  214. package/dist/core/workflow/proof.js +4 -3
  215. package/dist/lib/browser/capture-fidelity.d.ts +7 -0
  216. package/dist/lib/browser/capture-fidelity.d.ts.map +1 -1
  217. package/dist/lib/browser/capture-fidelity.js +17 -0
  218. package/dist/lib/browser/client.d.ts +9 -0
  219. package/dist/lib/browser/client.d.ts.map +1 -1
  220. package/dist/lib/browser/client.js +85 -37
  221. package/dist/lib/browser/index.d.ts +3 -1
  222. package/dist/lib/browser/index.d.ts.map +1 -1
  223. package/dist/lib/browser/index.js +2 -1
  224. package/dist/lib/browser/page-review-budget.d.ts +13 -0
  225. package/dist/lib/browser/page-review-budget.d.ts.map +1 -0
  226. package/dist/lib/browser/page-review-budget.js +251 -0
  227. package/dist/lib/browser/page-review-contracts.d.ts +127 -0
  228. package/dist/lib/browser/page-review-contracts.d.ts.map +1 -0
  229. package/dist/lib/browser/page-review-contracts.js +5 -0
  230. package/dist/lib/browser/page-review-evidence.d.ts +48 -0
  231. package/dist/lib/browser/page-review-evidence.d.ts.map +1 -0
  232. package/dist/lib/browser/page-review-evidence.js +340 -0
  233. package/dist/lib/browser/page-review-judge.d.ts +1 -1
  234. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  235. package/dist/lib/browser/page-review-judge.js +46 -38
  236. package/dist/lib/browser/page-review-pack.d.ts +44 -22
  237. package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
  238. package/dist/lib/browser/page-review-pack.js +116 -36
  239. package/dist/lib/browser/page-review-source.d.ts +12 -0
  240. package/dist/lib/browser/page-review-source.d.ts.map +1 -0
  241. package/dist/lib/browser/page-review-source.js +22 -0
  242. package/dist/lib/browser/qa-run-contracts.d.ts +35 -9
  243. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  244. package/dist/lib/browser/qa-run-contracts.js +48 -14
  245. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  246. package/dist/lib/browser/qa-run.js +215 -68
  247. package/dist/lib/browser/qa-snapshot.d.ts +6 -0
  248. package/dist/lib/browser/qa-snapshot.d.ts.map +1 -1
  249. package/dist/lib/browser/qa-snapshot.js +259 -48
  250. package/dist/lib/browser/review-capture-transaction.d.ts +13 -0
  251. package/dist/lib/browser/review-capture-transaction.d.ts.map +1 -0
  252. package/dist/lib/browser/review-capture-transaction.js +79 -0
  253. package/dist/lib/browser/standalone-html.d.ts.map +1 -1
  254. package/dist/lib/browser/standalone-html.js +4 -2
  255. package/dist/lib/browser/thumbnail-association.d.ts +34 -0
  256. package/dist/lib/browser/thumbnail-association.d.ts.map +1 -0
  257. package/dist/lib/browser/thumbnail-association.js +158 -0
  258. package/dist/lib/browser/tiling.d.ts +30 -0
  259. package/dist/lib/browser/tiling.d.ts.map +1 -1
  260. package/dist/lib/browser/tiling.js +38 -0
  261. package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
  262. package/dist/lib/docs-metadata-managed.js +4 -1
  263. package/dist/lib/identities/assume.d.ts.map +1 -1
  264. package/dist/lib/identities/assume.js +7 -1
  265. package/dist/lib/instructions/templates.d.ts.map +1 -1
  266. package/dist/lib/instructions/templates.js +1 -0
  267. package/dist/lib/local-file-url.d.ts +12 -0
  268. package/dist/lib/local-file-url.d.ts.map +1 -1
  269. package/dist/lib/local-file-url.js +16 -2
  270. package/dist/lib/tunnel/state.d.ts +19 -4
  271. package/dist/lib/tunnel/state.d.ts.map +1 -1
  272. package/dist/lib/tunnel/state.js +53 -13
  273. package/package.json +7 -4
  274. package/schemas/config.schema.json +69 -2
  275. package/src/commands/agents.ts +42 -9
  276. package/src/commands/artifacts.ts +130 -6
  277. package/src/commands/backup.ts +287 -34
  278. package/src/commands/browse.ts +564 -267
  279. package/src/commands/diagnostics.ts +6 -3
  280. package/src/commands/ledger-v3.ts +20 -3
  281. package/src/commands/qa-run.ts +37 -3
  282. package/src/commands/qa-status.ts +11 -2
  283. package/src/commands/resources.ts +23 -3
  284. package/src/commands/review-pack.ts +165 -62
  285. package/src/commands/tunnel.ts +2 -1
  286. package/src/core/agents/canonical-emit.ts +4 -2
  287. package/src/core/agents/cli.ts +14 -6
  288. package/src/core/agents/live-authority-v3.ts +7 -3
  289. package/src/core/agents/live-lifecycle-v3.ts +2 -2
  290. package/src/core/agents/render/prompt-context.ts +68 -14
  291. package/src/core/agents/session-finalizer-v3.ts +2 -2
  292. package/src/core/agents/state/live-coordination-view.ts +4 -4
  293. package/src/core/agents/state/names.ts +92 -7
  294. package/src/core/agents/state/stale-sweep.ts +22 -8
  295. package/src/core/agents/turn-elapsed.ts +135 -0
  296. package/src/core/artifacts/activity.ts +71 -0
  297. package/src/core/artifacts/delivery-card.ts +375 -0
  298. package/src/core/artifacts/index.ts +385 -91
  299. package/src/core/artifacts/mutation-lock.ts +106 -0
  300. package/src/core/backup/host-snapshot.ts +76 -0
  301. package/src/core/config.ts +66 -13
  302. package/src/core/diagnostics/advice.ts +148 -120
  303. package/src/core/diagnostics/bundle.ts +138 -52
  304. package/src/core/diagnostics/comparison.ts +6 -2
  305. package/src/core/diagnostics/contract.ts +39 -42
  306. package/src/core/diagnostics/index.ts +2 -0
  307. package/src/core/diagnostics/pressure-contract.ts +294 -0
  308. package/src/core/diagnostics/pressure.ts +1361 -0
  309. package/src/core/diagnostics/replay.ts +116 -11
  310. package/src/core/events/v3/adapter-id.ts +12 -0
  311. package/src/core/events/v3/archive-retention.ts +3 -6
  312. package/src/core/events/v3/bootstrap.ts +163 -16
  313. package/src/core/events/v3/capabilities.ts +75 -25
  314. package/src/core/events/v3/capability-drift.ts +2 -2
  315. package/src/core/events/v3/control-witness.ts +172 -41
  316. package/src/core/events/v3/control.ts +34 -2
  317. package/src/core/events/v3/index.ts +3 -0
  318. package/src/core/events/v3/live-feed.ts +23 -5
  319. package/src/core/events/v3/live-routing.ts +14 -2
  320. package/src/core/events/v3/producers/command-recorder.ts +4 -4
  321. package/src/core/events/v3/producers/coordination-recorder.ts +4 -4
  322. package/src/core/events/v3/producers/hook-base.ts +21 -16
  323. package/src/core/events/v3/producers/hook.ts +21 -4
  324. package/src/core/events/v3/producers/intake.ts +6 -6
  325. package/src/core/events/v3/producers/producer-state-observer.ts +2 -1
  326. package/src/core/events/v3/producers/recorder.ts +50 -21
  327. package/src/core/events/v3/reader.ts +14 -13
  328. package/src/core/events/v3/runtime-build.ts +3 -2
  329. package/src/core/events/v3/runtime-telemetry-capabilities.ts +3 -3
  330. package/src/core/events/v3/turn-telemetry.ts +4 -4
  331. package/src/core/hooks/adapter/output.ts +50 -36
  332. package/src/core/hooks/backup-schedule.ts +147 -0
  333. package/src/core/hooks/cli.ts +65 -14
  334. package/src/core/hooks/codex-wsl-bridge.ts +21 -5
  335. package/src/core/hooks/index.ts +1 -0
  336. package/src/core/hooks/prompt-context/contract.ts +177 -0
  337. package/src/core/hooks/prompt-context/index.ts +22 -0
  338. package/src/core/hooks/prompt-context/runner.ts +303 -0
  339. package/src/core/qa-artifacts.ts +29 -3
  340. package/src/core/resources/contract.ts +76 -3
  341. package/src/core/resources/index.ts +1 -0
  342. package/src/core/resources/native.ts +481 -0
  343. package/src/core/resources/sampler.ts +46 -1
  344. package/src/core/resources/status.ts +717 -0
  345. package/src/core/resources/system.ts +530 -0
  346. package/src/core/semantic/service.ts +37 -3
  347. package/src/core/storage/builtins.ts +41 -0
  348. package/src/core/supervisor/contract.ts +45 -3
  349. package/src/core/supervisor/findings.ts +159 -0
  350. package/src/core/supervisor/history.ts +15 -0
  351. package/src/core/supervisor/ledger-state.ts +0 -0
  352. package/src/core/supervisor/service.ts +133 -5
  353. package/src/core/supervisor/storage.ts +7 -0
  354. package/src/core/workflow/admission.ts +33 -17
  355. package/src/core/workflow/engine.ts +2 -2
  356. package/src/core/workflow/live-session-v3.ts +6 -8
  357. package/src/core/workflow/proof.ts +4 -3
  358. package/src/lib/browser/capture-fidelity.ts +23 -0
  359. package/src/lib/browser/client.ts +96 -44
  360. package/src/lib/browser/index.ts +15 -1
  361. package/src/lib/browser/page-review-budget.ts +309 -0
  362. package/src/lib/browser/page-review-contracts.ts +129 -0
  363. package/src/lib/browser/page-review-evidence.ts +465 -0
  364. package/src/lib/browser/page-review-judge.ts +56 -40
  365. package/src/lib/browser/page-review-pack.ts +181 -43
  366. package/src/lib/browser/page-review-source.ts +30 -0
  367. package/src/lib/browser/qa-run-contracts.ts +62 -18
  368. package/src/lib/browser/qa-run.ts +259 -71
  369. package/src/lib/browser/qa-snapshot.ts +289 -48
  370. package/src/lib/browser/review-capture-transaction.ts +106 -0
  371. package/src/lib/browser/standalone-html.ts +4 -2
  372. package/src/lib/browser/thumbnail-association.ts +190 -0
  373. package/src/lib/browser/tiling.ts +63 -0
  374. package/src/lib/docs-metadata-managed.ts +4 -1
  375. package/src/lib/identities/assume.ts +12 -4
  376. package/src/lib/instructions/templates.ts +1 -0
  377. package/src/lib/local-file-url.ts +17 -2
  378. package/src/lib/tunnel/state.ts +70 -13
@@ -0,0 +1,106 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ lstatSync,
4
+ mkdirSync,
5
+ readdirSync,
6
+ readFileSync,
7
+ rmdirSync,
8
+ rmSync,
9
+ unlinkSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import { hostname } from "node:os";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { processStartToken } from "../agents/state/proc-start.ts";
15
+
16
+ interface LockOwner {
17
+ version: 1;
18
+ host: string;
19
+ pid: number;
20
+ process_start: string | null;
21
+ acquired_at: string;
22
+ }
23
+
24
+ /** Age is never authority to evict an owner. Unknown owners fail closed. */
25
+ function reclaimDeadOwner(lock: string): boolean {
26
+ try {
27
+ const stat = lstatSync(lock);
28
+ if (!stat.isDirectory() || stat.isSymbolicLink()) return false;
29
+ const names = readdirSync(lock);
30
+ if (names.length !== 1 || !/^owner-[0-9a-f-]{36}\.json$/.test(names[0]!)) return false;
31
+ const ownerPath = join(lock, names[0]!);
32
+ const ownerStat = lstatSync(ownerPath);
33
+ if (!ownerStat.isFile() || ownerStat.isSymbolicLink() || ownerStat.size > 4096) return false;
34
+ const owner = JSON.parse(readFileSync(ownerPath, "utf8")) as LockOwner;
35
+ if (
36
+ owner.version !== 1 ||
37
+ owner.host !== hostname() ||
38
+ !Number.isSafeInteger(owner.pid) ||
39
+ owner.pid <= 0 ||
40
+ !(owner.process_start === null || typeof owner.process_start === "string")
41
+ )
42
+ return false;
43
+ let dead = false;
44
+ try {
45
+ process.kill(owner.pid, 0);
46
+ const current = processStartToken(owner.pid);
47
+ // Wall-clock ps lstart output can drift even for a live process. Only
48
+ // boot-scoped procfs ticks establish PID reuse; other probes need ESRCH.
49
+ const bootScoped = /^l[0-9a-f]{8}\.\d+$/;
50
+ dead =
51
+ typeof owner.process_start === "string" &&
52
+ bootScoped.test(owner.process_start) &&
53
+ typeof current === "string" &&
54
+ bootScoped.test(current) &&
55
+ current !== owner.process_start;
56
+ } catch (error) {
57
+ dead = (error as NodeJS.ErrnoException).code === "ESRCH";
58
+ }
59
+ if (!dead) return false;
60
+ // Only the contender that removes this unique owner's file may remove the
61
+ // directory. A losing reaper must not unlink a replacement owner's lock.
62
+ unlinkSync(ownerPath);
63
+ rmdirSync(lock);
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ export function withArtifactLock<T>(repoRoot: string, action: () => T): T {
71
+ // Probe before mkdir so a slow platform process probe cannot hold an
72
+ // ownerless lock open while other writers are trying to acquire it.
73
+ const owner: LockOwner = {
74
+ version: 1,
75
+ host: hostname(),
76
+ pid: process.pid,
77
+ process_start: processStartToken(process.pid),
78
+ acquired_at: new Date().toISOString(),
79
+ };
80
+ const lock = join(resolve(repoRoot), ".harnery/artifacts-mutation.lock");
81
+ mkdirSync(dirname(lock), { recursive: true });
82
+ try {
83
+ mkdirSync(lock);
84
+ } catch {
85
+ if (!reclaimDeadOwner(lock)) {
86
+ throw new Error(
87
+ "artifact store mutation lock unavailable; a live or unverifiable owner holds it. " +
88
+ "Dead local owners recover automatically; an empty or invalid lock requires explicit inspection.",
89
+ );
90
+ }
91
+ // Another caller may win after recovery. Never retry by removing its lock.
92
+ mkdirSync(lock);
93
+ }
94
+ const ownerPath = join(lock, `owner-${randomUUID()}.json`);
95
+ try {
96
+ writeFileSync(ownerPath, JSON.stringify(owner), { flag: "wx" });
97
+ return action();
98
+ } finally {
99
+ // A crash before publication or between these two calls leaves an empty
100
+ // directory. It cannot be distinguished safely from an initializing owner.
101
+ // Publication may have failed before creating a file. Remove only our
102
+ // unique file, then the empty directory; never recursively delete contents.
103
+ rmSync(ownerPath, { force: true });
104
+ rmdirSync(lock);
105
+ }
106
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Freshness helpers shared by `harn backup snapshot` and the session-start
3
+ * scheduler. The host cache is a local, best-effort record of this machine's
4
+ * newest known snapshot: the scheduler reads it to skip a remote restic round
5
+ * trip when the host is plainly current, and restic stays the authority
6
+ * whenever the cache is missing, stale, or written by another host.
7
+ */
8
+
9
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
10
+ import { hostname } from "node:os";
11
+ import path from "node:path";
12
+
13
+ export function parseBackupDuration(value: string): number | null {
14
+ const match = /^([1-9][0-9]*)(ms|s|m|h|d)$/.exec(value.trim());
15
+ if (!match) return null;
16
+ const scalar = Number(match[1]);
17
+ const factor = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[
18
+ match[2] as "ms" | "s" | "m" | "h" | "d"
19
+ ];
20
+ const duration = scalar * factor;
21
+ return Number.isSafeInteger(duration) ? duration : null;
22
+ }
23
+
24
+ /** Newest snapshot time (epoch ms) in `restic snapshots --json` output, or null. */
25
+ export function newestSnapshotTime(output: string): number | null {
26
+ try {
27
+ const rows = JSON.parse(output) as Array<{ time?: string }>;
28
+ const newest = rows.reduce((latest, row) => {
29
+ const time = typeof row.time === "string" ? Date.parse(row.time) : Number.NaN;
30
+ return Number.isFinite(time) ? Math.max(latest, time) : latest;
31
+ }, Number.NEGATIVE_INFINITY);
32
+ return Number.isFinite(newest) ? newest : null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ export interface HostSnapshotCache {
39
+ host: string;
40
+ snapshotAt: number;
41
+ }
42
+
43
+ export function hostSnapshotCachePath(harneryDir: string): string {
44
+ return path.join(harneryDir, "logs", "backup-host-snapshot.json");
45
+ }
46
+
47
+ export function readHostSnapshotCache(harneryDir: string): HostSnapshotCache | null {
48
+ try {
49
+ const parsed = JSON.parse(readFileSync(hostSnapshotCachePath(harneryDir), "utf8")) as {
50
+ host?: unknown;
51
+ snapshot_at?: unknown;
52
+ };
53
+ const snapshotAt =
54
+ typeof parsed.snapshot_at === "string" ? Date.parse(parsed.snapshot_at) : Number.NaN;
55
+ if (typeof parsed.host !== "string" || !Number.isFinite(snapshotAt)) return null;
56
+ return { host: parsed.host, snapshotAt };
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ export function recordHostSnapshot(harneryDir: string, snapshotAt: number): void {
63
+ try {
64
+ const file = hostSnapshotCachePath(harneryDir);
65
+ mkdirSync(path.dirname(file), { recursive: true });
66
+ const body = `${JSON.stringify({
67
+ host: hostname(),
68
+ snapshot_at: new Date(snapshotAt).toISOString(),
69
+ recorded_at: new Date().toISOString(),
70
+ })}\n`;
71
+ writeFileSync(`${file}.tmp`, body, "utf8");
72
+ renameSync(`${file}.tmp`, file);
73
+ } catch {
74
+ // The cache is an optimisation; a failed write only costs a restic query later.
75
+ }
76
+ }
@@ -7,7 +7,7 @@
7
7
  * 2. `<project-root>/.harnery/config.jsonc` — project override (authoritative)
8
8
  *
9
9
  * Fields owned here: `binName` (host CLI name for agent-facing strings),
10
- * `hooksSetupHint`, `agents`, `instructions`, `tools`, `workflow`, `skills`, `presence`, plus the tunable
10
+ * `hooksSetupHint`, `hooks`, `agents`, `instructions`, `tools`, `workflow`, `skills`, `presence`, plus the tunable
11
11
  * `coord` (heartbeat freshness), `logs` (structured-log storage budgets),
12
12
  * `artifacts` (working-file retention),
13
13
  * `backup` (restic repo/password/prune policy), `sync` (rclone
@@ -21,10 +21,14 @@
21
21
 
22
22
  import { readFileSync, statSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
- import { join } from "node:path";
24
+ import { isAbsolute, join, resolve } from "node:path";
25
25
  import { coordEnv } from "../lib/env.ts";
26
26
  import { DEFAULT_WEB_PORT } from "../lib/local-file-url.ts";
27
27
  import { resolveEventLedgerRotateActiveBytesV3 } from "./events/v3/rotation-config.ts";
28
+ import {
29
+ type PromptContextConfig,
30
+ parsePromptContextConfig,
31
+ } from "./hooks/prompt-context/contract.ts";
28
32
  import { findCoordRoot } from "./hooks/resolve/coord-root.ts";
29
33
 
30
34
  export { DEFAULT_WEB_PORT } from "../lib/local-file-url.ts";
@@ -73,6 +77,10 @@ interface HarneryConfig {
73
77
  * declares it here (e.g. "scripts/setup-hooks.sh"). Unset → a generic hint.
74
78
  */
75
79
  hooksSetupHint?: string;
80
+ /** Optional host prompt-context extension. Project config only. */
81
+ hooks?: {
82
+ promptContext?: unknown;
83
+ };
76
84
  /**
77
85
  * Agent-ritual policy owned by the host project. Git finalization is opt-in:
78
86
  * standalone Harnery and embedding hosts keep the ordinary status ritual
@@ -163,12 +171,18 @@ interface HarneryConfig {
163
171
  auto_clean?: boolean;
164
172
  };
165
173
  /**
166
- * `harn backup` (restic) defaults: `repo` path/URL, `password_file`, and the
167
- * `keep_daily`/`keep_weekly`/`keep_monthly` prune policy. Read via `backupConfig()`.
174
+ * `harn backup` (restic) defaults. Read via `backupConfig()`.
168
175
  */
169
176
  backup?: {
170
177
  repo?: string;
171
178
  password_file?: string;
179
+ include?: string[];
180
+ exclude?: string[];
181
+ max_bytes?: number;
182
+ schedule?: {
183
+ if_stale?: string;
184
+ tags?: string[];
185
+ };
172
186
  keep_daily?: number;
173
187
  keep_weekly?: number;
174
188
  keep_monthly?: number;
@@ -482,6 +496,18 @@ export function hostPromptReminder(coordRoot?: string | null): string | null {
482
496
  return reminder;
483
497
  }
484
498
 
499
+ /**
500
+ * Project-owned prompt-context provider settings. The user-global config is
501
+ * ignored because one project's executable and data policy must not become a
502
+ * default for another project. Invalid values fail closed to `null`; callers
503
+ * treat that the same as a disabled provider and keep the prompt hook usable.
504
+ */
505
+ export function hostPromptContextConfig(coordRoot?: string | null): PromptContextConfig | null {
506
+ const root = coordRoot ?? findCoordRoot();
507
+ if (!root) return null;
508
+ return parsePromptContextConfig(readProjectConfig(root).hooks?.promptContext);
509
+ }
510
+
485
511
  /**
486
512
  * Whether the host requires the guarded Git check at the end of tool-using
487
513
  * turns. Default false: Harnery exposes `agents status --end-turn` as a capability
@@ -795,7 +821,7 @@ export function artifactMaxUnitBytes(coordRoot?: string | null): number {
795
821
  }
796
822
 
797
823
  /**
798
- * Whether the daily SessionStart sweep of expired artifact workspaces runs.
824
+ * Whether opportunistic cleanup of expired artifact workspaces runs.
799
825
  * Precedence: `HARNERY_ARTIFACT_AUTO_CLEAN` (0/false disables) ->
800
826
  * `artifacts.auto_clean` -> enabled. The sweep only ever deletes
801
827
  * `managed-expired` entries via the same guarded classifier as
@@ -826,9 +852,8 @@ export function reviewPackAutoCleanEnabled(coordRoot?: string | null): boolean {
826
852
 
827
853
  /**
828
854
  * Sweep cadence in hours. Env-only (`HARNERY_ARTIFACT_AUTO_CLEAN_INTERVAL_HOURS`,
829
- * mainly for tests): the cadence is an implementation detail, not policy, and
830
- * retention itself is quantized in whole days, so the 24h default is not a
831
- * config surface.
855
+ * mainly for tests). Session starts and new artifact work share the hourly
856
+ * throttle so minute-scale retention is useful within long sessions.
832
857
  */
833
858
  export function artifactAutoCleanIntervalHours(): number {
834
859
  const env = coordEnv("ARTIFACT_AUTO_CLEAN_INTERVAL_HOURS");
@@ -836,7 +861,7 @@ export function artifactAutoCleanIntervalHours(): number {
836
861
  const n = Number(env);
837
862
  if (Number.isFinite(n) && n > 0 && n <= 24 * 365) return n;
838
863
  }
839
- return 24;
864
+ return 1;
840
865
  }
841
866
 
842
867
  function integerSetting(
@@ -869,6 +894,10 @@ function booleanSetting(
869
894
  export interface BackupConfig {
870
895
  repo: string;
871
896
  passwordFile: string;
897
+ include: readonly string[];
898
+ exclude: readonly string[];
899
+ maxBytes: number;
900
+ schedule: { ifStale: string; tags: readonly string[] } | null;
872
901
  keepDaily: number;
873
902
  keepWeekly: number;
874
903
  keepMonthly: number;
@@ -878,6 +907,7 @@ export interface BackupConfig {
878
907
  * `harn backup` (restic) defaults. Per field, precedence is env → config → built-in:
879
908
  * repo: `HARNERY_RESTIC_REPO` → `backup.repo` → `~/.cache/harnery/restic-repo`
880
909
  * passwordFile: `HARNERY_RESTIC_PASSWORD_FILE` → `backup.password_file` → `~/.config/harnery/restic-password`
910
+ * include/exclude/maxBytes/schedule: `backup.*` → catalog defaults / 50 MiB / disabled
881
911
  * keepDaily/Weekly/Monthly: `backup.keep_*` → 7 / 4 / 6
882
912
  * `coordRoot` is resolved via `findCoordRoot()` when not passed.
883
913
  */
@@ -889,13 +919,36 @@ export function backupConfig(coordRoot?: string | null): BackupConfig {
889
919
  typeof v === "string" && v.trim() ? v.trim() : undefined;
890
920
  const repo =
891
921
  coordEnv("RESTIC_REPO") ?? cfgStr(b.repo) ?? join(home, ".cache", "harnery", "restic-repo");
892
- const passwordFile =
893
- coordEnv("RESTIC_PASSWORD_FILE") ??
894
- cfgStr(b.password_file) ??
895
- join(home, ".config", "harnery", "restic-password");
922
+ const envPasswordFile = coordEnv("RESTIC_PASSWORD_FILE");
923
+ const configuredPasswordFile = cfgStr(b.password_file);
924
+ const passwordFile = envPasswordFile
925
+ ? envPasswordFile
926
+ : configuredPasswordFile
927
+ ? root && !isAbsolute(configuredPasswordFile)
928
+ ? resolve(root, configuredPasswordFile)
929
+ : configuredPasswordFile
930
+ : join(home, ".config", "harnery", "restic-password");
931
+ const stringList = (value: unknown): readonly string[] =>
932
+ Array.isArray(value)
933
+ ? value.filter(
934
+ (entry): entry is string => typeof entry === "string" && entry.trim().length > 0,
935
+ )
936
+ : [];
937
+ const schedule = isPlainObject(b.schedule)
938
+ ? (() => {
939
+ const ifStale = cfgStr(b.schedule?.if_stale);
940
+ return ifStale
941
+ ? { ifStale, tags: stringList(b.schedule?.tags).map((tag) => tag.trim()) }
942
+ : null;
943
+ })()
944
+ : null;
896
945
  return {
897
946
  repo,
898
947
  passwordFile,
948
+ include: stringList(b.include).map((entry) => entry.trim()),
949
+ exclude: stringList(b.exclude).map((entry) => entry.trim()),
950
+ maxBytes: posIntOr(b.max_bytes, 50 * 1_024 * 1_024),
951
+ schedule,
899
952
  keepDaily: posIntOr(b.keep_daily, 7),
900
953
  keepWeekly: posIntOr(b.keep_weekly, 4),
901
954
  keepMonthly: posIntOr(b.keep_monthly, 6),
@@ -1,147 +1,175 @@
1
- import type { SupervisorCapability, SupervisorFinding } from "../supervisor/contract.ts";
1
+ import type { SupervisorCapability, SupervisorHistoryPoint } from "../supervisor/contract.ts";
2
2
  import {
3
- DIAGNOSTIC_ADVICE_LIMITS,
4
3
  DIAGNOSTIC_ADVICE_SCHEMA_VERSION,
5
4
  type DiagnosticAdvice,
6
- type DiagnosticAdviceFinding,
7
- type DiagnosticAdviceReason,
5
+ PRESSURE_ASSESSMENT_SCHEMA_VERSION,
6
+ PRESSURE_POLICY,
7
+ type PressureAssessment,
8
+ type PressureDimension,
9
+ type PressureEvidenceDimension,
10
+ type PressureHysteresisState,
11
+ type PressureReason,
12
+ type PressureReasonCode,
13
+ type PressureScope,
14
+ type PressureWorkloadGuidance,
8
15
  } from "./contract.ts";
16
+ import type { PressureHistorySample } from "./pressure-contract.ts";
9
17
 
18
+ /**
19
+ * Advice is an envelope around one already-computed assessment. It maps
20
+ * nothing: severity does not become pressure here, and no numeric threshold is
21
+ * read. `assessPressure` owns the judgement, the observer publishes it, and
22
+ * every surface renders this envelope so all of them agree.
23
+ */
10
24
  export interface BuildDiagnosticAdviceInput {
11
- findings: readonly SupervisorFinding[];
25
+ assessment: PressureAssessment;
26
+ /** The state the previous sample carried forward, so a replay can reproduce a transition. */
27
+ priorHysteresis: PressureHysteresisState | null;
28
+ /** Where the assessment came from, and why it is missing when it is. */
12
29
  sourceCapability: SupervisorCapability;
30
+ activeFindingCount: number;
13
31
  evaluatedAt: string;
14
32
  }
15
33
 
16
- const SEVERITY_RANK: Readonly<Record<SupervisorFinding["severity"], number>> = {
17
- info: 0,
18
- warning: 1,
19
- critical: 2,
20
- };
21
-
22
34
  export function buildDiagnosticAdvice(input: BuildDiagnosticAdviceInput): DiagnosticAdvice {
23
- const sourceAvailable = input.sourceCapability.state === "supported";
24
- const active = sourceAvailable
25
- ? input.findings.filter((finding) => finding.state === "opened").sort(compareFindings)
26
- : [];
27
- const pressureFindings = active.filter(
28
- (finding): finding is SupervisorFinding & { severity: "warning" | "critical" } =>
29
- finding.severity === "warning" || finding.severity === "critical",
30
- );
31
- const critical = pressureFindings.filter((finding) => finding.severity === "critical");
32
- const warning = pressureFindings.filter((finding) => finding.severity === "warning");
33
- const pressure = critical.length
34
- ? "critical"
35
- : warning.length
36
- ? "elevated"
37
- : sourceAvailable
38
- ? "normal"
39
- : "unknown";
40
- const fanOutRecommendation =
41
- pressure === "critical"
42
- ? "avoid-new-fan-out"
43
- : pressure === "elevated"
44
- ? "use-caution"
45
- : pressure === "normal"
46
- ? "proceed"
47
- : "unknown";
48
- const contributing = pressureFindings.slice(
49
- 0,
50
- DIAGNOSTIC_ADVICE_LIMITS.max_contributing_findings,
51
- );
52
35
  return {
53
36
  schema_version: DIAGNOSTIC_ADVICE_SCHEMA_VERSION,
54
37
  evaluated_at: input.evaluatedAt,
55
- pressure,
56
- fan_out_recommendation: fanOutRecommendation,
57
38
  observer_only: true,
58
- summary: summaryFor(pressure),
39
+ assessment: input.assessment,
40
+ prior_hysteresis: input.priorHysteresis,
59
41
  source_capability: input.sourceCapability,
60
- active_finding_count: active.length,
61
- contributing_finding_count: pressureFindings.length,
62
- omitted_contributing_finding_count: Math.max(0, pressureFindings.length - contributing.length),
63
- contributing_findings: contributing.map(adviceFinding),
64
- reasons: reasonsFor(critical, warning, input.sourceCapability),
42
+ active_finding_count: Math.max(0, Math.trunc(input.activeFindingCount)),
43
+ summary: input.assessment.summary,
65
44
  };
66
45
  }
67
46
 
68
- function adviceFinding(
69
- finding: SupervisorFinding & { severity: "warning" | "critical" },
70
- ): DiagnosticAdviceFinding {
71
- return {
72
- finding_id: finding.id,
73
- finding_kind: finding.finding_kind,
74
- severity: finding.severity,
75
- summary: finding.summary,
76
- scope_kind: finding.scope_kind,
77
- scope_id: finding.scope_id,
78
- occurrence_count: finding.occurrence_count,
79
- owner_kind: finding.attribution?.owner_kind,
80
- owner_id: finding.attribution?.owner_id,
81
- workload_relationship: finding.workload_context?.relationship,
82
- };
83
- }
47
+ export const PRESSURE_DIMENSIONS: readonly PressureDimension[] = [
48
+ "memory_stall",
49
+ "io_stall",
50
+ "cpu_stall",
51
+ "oom_kills",
52
+ "swap_activity",
53
+ "direct_reclaim",
54
+ "memory_available",
55
+ "disk_available",
56
+ "host_memory",
57
+ ];
84
58
 
85
- function reasonsFor(
86
- critical: readonly SupervisorFinding[],
87
- warning: readonly SupervisorFinding[],
88
- capability: SupervisorCapability,
89
- ): DiagnosticAdviceReason[] {
90
- const reasons: DiagnosticAdviceReason[] = [];
91
- if (critical.length) {
92
- reasons.push({
93
- code: "critical_findings_active",
94
- summary: `${critical.length} active critical finding${critical.length === 1 ? "" : "s"} ${critical.length === 1 ? "requires" : "require"} attention.`,
95
- finding_ids: boundedIds(critical),
96
- });
97
- }
98
- if (warning.length) {
99
- reasons.push({
100
- code: "warning_findings_active",
101
- summary: `${warning.length} active warning finding${warning.length === 1 ? "" : "s"} ${warning.length === 1 ? "indicates" : "indicate"} elevated pressure.`,
102
- finding_ids: boundedIds(warning),
103
- });
104
- }
105
- if (capability.state !== "supported") {
106
- reasons.push({
107
- code: "findings_source_unavailable",
108
- summary: `The findings source is ${capability.state}${capability.reason_code ? ` (${capability.reason_code})` : ""}.`,
109
- finding_ids: [],
110
- });
111
- }
112
- if (!critical.length && !warning.length && capability.state === "supported") {
113
- reasons.push({
114
- code: "no_active_pressure_findings",
115
- summary: "No active warning or critical findings were observed.",
116
- finding_ids: [],
117
- });
118
- }
119
- return reasons.slice(0, DIAGNOSTIC_ADVICE_LIMITS.max_reasons);
59
+ export interface UnknownPressureAssessmentInput {
60
+ observedAt: string;
61
+ /** A stable code from the frozen union, normally `evidence_unavailable` or `snapshot_stale`. */
62
+ reasonCode: PressureReasonCode;
63
+ /** One plain sentence naming what is missing. Becomes the assessment summary. */
64
+ summary: string;
65
+ scope?: PressureScope;
66
+ sampleAgeMs?: number | null;
67
+ prior?: PressureHysteresisState | null;
68
+ observerGeneration?: string | null;
120
69
  }
121
70
 
122
- function boundedIds(findings: readonly SupervisorFinding[]): string[] {
123
- return findings
124
- .slice(0, DIAGNOSTIC_ADVICE_LIMITS.max_contributing_findings)
125
- .map((finding) => finding.id);
71
+ /**
72
+ * The assessment to publish when the source is absent, stale, or malformed.
73
+ * Every dimension reads `unavailable` and no owner is named, because a gap in
74
+ * the evidence is not a measurement of health.
75
+ */
76
+ export function unknownPressureAssessment(
77
+ input: UnknownPressureAssessmentInput,
78
+ ): PressureAssessment {
79
+ const reason: PressureReason = {
80
+ code: input.reasonCode,
81
+ dimension: null,
82
+ summary: input.summary,
83
+ observed_value: null,
84
+ threshold_value: null,
85
+ unit: null,
86
+ sample_count: 0,
87
+ contributes_to: "unknown",
88
+ };
89
+ return {
90
+ schema_version: PRESSURE_ASSESSMENT_SCHEMA_VERSION,
91
+ observer_only: true,
92
+ state: "unknown",
93
+ scope: input.scope ?? "native",
94
+ limiting_resource: "unknown",
95
+ trend: "unknown",
96
+ observed_at: input.observedAt,
97
+ sample_age_ms: input.sampleAgeMs ?? null,
98
+ evidence_state: "unavailable",
99
+ evidence: PRESSURE_DIMENSIONS.map(
100
+ (dimension): PressureEvidenceDimension => ({
101
+ dimension,
102
+ state: "unavailable",
103
+ observed_value: null,
104
+ unit: null,
105
+ sample_count: 0,
106
+ reason_code: input.reasonCode,
107
+ }),
108
+ ).slice(0, PRESSURE_POLICY.limits.max_evidence),
109
+ reasons: [reason],
110
+ contributors: [],
111
+ omitted_contributor_count: 0,
112
+ unattributed_memory_percent: null,
113
+ recommended_action: "unknown",
114
+ summary: input.summary,
115
+ guidance: unknownGuidance(),
116
+ hysteresis: {
117
+ state: "unknown",
118
+ state_since: input.observedAt,
119
+ consecutive_clear_samples: 0,
120
+ dimension_streaks: {},
121
+ oom_baseline_total_kills: input.prior?.oom_baseline_total_kills ?? null,
122
+ oom_hold_until: null,
123
+ observer_generation: input.observerGeneration ?? input.prior?.observer_generation ?? null,
124
+ },
125
+ policy_version: PRESSURE_POLICY.policy_version,
126
+ };
126
127
  }
127
128
 
128
- function summaryFor(pressure: DiagnosticAdvice["pressure"]): string {
129
- switch (pressure) {
130
- case "critical":
131
- return "Critical local pressure is active. Avoid starting additional agent work until the findings change.";
132
- case "elevated":
133
- return "Elevated local pressure is active. Limit new fan-out and inspect the contributing findings.";
134
- case "normal":
135
- return "No active warning or critical findings were observed. Diagnostics do not restrict new fan-out.";
136
- case "unknown":
137
- return "Local pressure cannot be determined because the findings source is unavailable.";
138
- }
129
+ function unknownGuidance(): PressureWorkloadGuidance[] {
130
+ return [
131
+ {
132
+ workload_class: "lightweight",
133
+ recommendation: "unknown",
134
+ summary:
135
+ "Reads and small edits cost almost nothing, so continue them while the measurements are missing.",
136
+ },
137
+ {
138
+ workload_class: "cpu-heavy",
139
+ recommendation: "unknown",
140
+ summary:
141
+ "Builds and test runs cannot be judged from here. Start one only if you already know the machine is idle.",
142
+ },
143
+ {
144
+ workload_class: "memory-heavy",
145
+ recommendation: "unknown",
146
+ summary:
147
+ "Browser captures and page QA cannot be judged from here. Run one at a time until the measurements return.",
148
+ },
149
+ {
150
+ workload_class: "storage-heavy",
151
+ recommendation: "unknown",
152
+ summary:
153
+ "Large writes and exports cannot be judged from here. Check free disk space yourself before starting one.",
154
+ },
155
+ ];
139
156
  }
140
157
 
141
- function compareFindings(left: SupervisorFinding, right: SupervisorFinding): number {
142
- return (
143
- SEVERITY_RANK[right.severity] - SEVERITY_RANK[left.severity] ||
144
- right.observed_at.localeCompare(left.observed_at) ||
145
- left.id.localeCompare(right.id)
146
- );
158
+ /**
159
+ * Project bounded supervisor history into the trend input the assessment reads.
160
+ * The observer and a bundle replay both call this, so the trend they compute
161
+ * from the same stored history is the same trend.
162
+ */
163
+ export function pressureHistoryFromSupervisor(
164
+ points: readonly SupervisorHistoryPoint[],
165
+ ): PressureHistorySample[] {
166
+ return points.slice(-PRESSURE_POLICY.max_history_samples).map((point) => ({
167
+ sampled_at: point.sampled_at,
168
+ memory_full_avg10: point.pressure.memory_full_avg10,
169
+ io_full_avg10: point.pressure.io_full_avg10,
170
+ cpu_some_avg60: point.pressure.cpu_some_avg60,
171
+ memory_available_percent:
172
+ point.machine.memory_percent === null ? null : 100 - point.machine.memory_percent,
173
+ swap_out_bytes_per_second: point.pressure.swap_out_bytes_per_second,
174
+ }));
147
175
  }