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,530 @@
1
+ import { closeSync, openSync, readdirSync, readSync, statfsSync, statSync } from "node:fs";
2
+ import { platform, tmpdir } from "node:os";
3
+ import { join, resolve } from "node:path";
4
+ import { performance } from "node:perf_hooks";
5
+ import type {
6
+ ResourceDiskSample,
7
+ ResourceIoSample,
8
+ ResourceOomSample,
9
+ ResourcePressureSample,
10
+ ResourcePressureWindow,
11
+ ResourceSupportState,
12
+ ResourceVmstatSample,
13
+ } from "./contract.ts";
14
+
15
+ interface FileSystemCapacity {
16
+ bsize: number | bigint;
17
+ blocks: number | bigint;
18
+ bavail: number | bigint;
19
+ bfree: number | bigint;
20
+ }
21
+
22
+ export interface SystemResourceOptions {
23
+ platform?: NodeJS.Platform;
24
+ procRoot?: string;
25
+ sysRoot?: string;
26
+ nowMs?: number;
27
+ namespace?: string;
28
+ tmpPath?: string;
29
+ statfs?: (path: string) => FileSystemCapacity;
30
+ filesystemId?: (path: string) => string | number | bigint;
31
+ }
32
+
33
+ interface IoCounters {
34
+ read: number;
35
+ write: number;
36
+ }
37
+
38
+ interface IoBaseline {
39
+ sampledAt: number;
40
+ devices: Map<string, IoCounters>;
41
+ }
42
+
43
+ /** The `/proc/vmstat` counters the reclaim rates are derived from, in report order. */
44
+ const VMSTAT_COUNTERS = ["pswpin", "pswpout", "pgscan_direct", "pgmajfault"] as const;
45
+
46
+ type VmstatCounterName = (typeof VMSTAT_COUNTERS)[number];
47
+ type VmstatCounters = Record<VmstatCounterName, number | null>;
48
+
49
+ interface VmstatBaseline {
50
+ sampledAt: number;
51
+ counters: VmstatCounters;
52
+ }
53
+
54
+ /**
55
+ * Linux reports `pswpin` and `pswpout` in pages, and the kernel does not
56
+ * publish the page size in `/proc/vmstat`. Every architecture Harnery runs on
57
+ * uses a 4096 byte base page, so that value converts pages to bytes here. A
58
+ * platform with a different base page would need its page size supplied.
59
+ */
60
+ const VMSTAT_PAGE_SIZE_BYTES = 4_096;
61
+
62
+ const ioBaselines = new Map<string, IoBaseline>();
63
+ const oomBaselines = new Map<
64
+ string,
65
+ { sampledAt: number; kills: number; lastKillAt: number | null }
66
+ >();
67
+ const vmstatBaselines = new Map<string, VmstatBaseline>();
68
+ const MAX_BASELINES = 64;
69
+ const MAX_BLOCK_DEVICES = 1_024;
70
+
71
+ /** Collect only the workspace and temporary volume; never walk their contents. */
72
+ export function collectSystemResources(
73
+ coordRoot: string,
74
+ options: SystemResourceOptions = {},
75
+ ): {
76
+ disks: ResourceDiskSample[];
77
+ pressure: ResourcePressureSample;
78
+ io: ResourceIoSample;
79
+ oom: ResourceOomSample;
80
+ vmstat: ResourceVmstatSample;
81
+ } {
82
+ const currentPlatform = options.platform ?? platform();
83
+ const procRoot = options.procRoot ?? "/proc";
84
+ const sysRoot = options.sysRoot ?? "/sys";
85
+ const disks = collectDisks(coordRoot, options);
86
+ if (currentPlatform !== "linux") {
87
+ return {
88
+ disks,
89
+ pressure: {
90
+ state: "unsupported",
91
+ cpu: null,
92
+ memory: null,
93
+ io: null,
94
+ memory_full: null,
95
+ io_full: null,
96
+ reason: `Linux pressure stall information is unavailable on ${currentPlatform}.`,
97
+ },
98
+ io: unavailableIo("unsupported", `Disk I/O rates are not collected on ${currentPlatform}.`),
99
+ oom: {
100
+ state: "unsupported",
101
+ total_kills: null,
102
+ kills_since_last_sample: null,
103
+ last_kill_age_ms: null,
104
+ reason: `Kernel OOM kill counters are not collected on ${currentPlatform}.`,
105
+ },
106
+ vmstat: unavailableVmstat(
107
+ "unsupported",
108
+ `Kernel memory reclaim counters are not collected on ${currentPlatform}.`,
109
+ false,
110
+ ),
111
+ };
112
+ }
113
+ const key = JSON.stringify([
114
+ resolve(coordRoot),
115
+ options.namespace ?? "linux",
116
+ resolve(procRoot),
117
+ resolve(sysRoot),
118
+ ]);
119
+ return {
120
+ disks,
121
+ pressure: collectPressure(procRoot),
122
+ io: collectIo(procRoot, sysRoot, options.nowMs ?? performance.now(), key),
123
+ oom: collectOom(procRoot, options.nowMs ?? performance.now(), key),
124
+ vmstat: collectVmstat(procRoot, options.nowMs ?? performance.now(), key),
125
+ };
126
+ }
127
+
128
+ function collectDisks(coordRoot: string, options: SystemResourceOptions): ResourceDiskSample[] {
129
+ const inspect = options.statfs ?? statfsSync;
130
+ const filesystemId = options.filesystemId ?? ((path: string) => statSync(path).dev);
131
+ const seen = new Set<string | number | bigint>();
132
+ const results: ResourceDiskSample[] = [];
133
+ for (const path of new Set([resolve(coordRoot), resolve(options.tmpPath ?? tmpdir())])) {
134
+ try {
135
+ const stat = inspect(path);
136
+ const blockSize = Number(stat.bsize);
137
+ const total = blockSize * Number(stat.blocks);
138
+ const available = blockSize * Number(stat.bavail);
139
+ const free = blockSize * Number(stat.bfree);
140
+ if (
141
+ ![blockSize, total, available, free].every(Number.isFinite) ||
142
+ blockSize <= 0 ||
143
+ total <= 0 ||
144
+ free < 0 ||
145
+ free > total ||
146
+ available > total
147
+ ) {
148
+ throw new Error("Invalid filesystem capacity counters.");
149
+ }
150
+ // st_dev identifies the mounted filesystem, unlike coincidentally equal capacity values.
151
+ try {
152
+ const id = filesystemId(path);
153
+ if (seen.has(id)) continue;
154
+ seen.add(id);
155
+ } catch {
156
+ // Capacity is still useful when stat cannot identify the filesystem.
157
+ }
158
+ results.push({
159
+ path,
160
+ state: "supported",
161
+ total_bytes: total,
162
+ available_bytes: Math.max(0, available),
163
+ used_percent: round(((total - free) / total) * 100),
164
+ });
165
+ } catch (error) {
166
+ results.push({
167
+ path,
168
+ state: unsupportedError(error) ? "unsupported" : "error",
169
+ total_bytes: null,
170
+ available_bytes: null,
171
+ used_percent: null,
172
+ reason: errorReason(error),
173
+ });
174
+ }
175
+ }
176
+ return results;
177
+ }
178
+
179
+ function collectPressure(procRoot: string): ResourcePressureSample {
180
+ const sample: ResourcePressureSample = {
181
+ state: "supported",
182
+ cpu: null,
183
+ memory: null,
184
+ io: null,
185
+ memory_full: null,
186
+ io_full: null,
187
+ };
188
+ const reasons: string[] = [];
189
+ let supported = 0;
190
+ let failures = 0;
191
+ for (const resource of ["cpu", "memory", "io"] as const) {
192
+ try {
193
+ const raw = readBounded(join(procRoot, "pressure", resource), 4_096);
194
+ const some = parsePressure(raw, "some");
195
+ // System-wide CPU full is undefined. Memory and I/O full measure all non-idle tasks stalled.
196
+ if (resource !== "cpu") sample[`${resource}_full`] = parsePressure(raw, "full");
197
+ sample[resource] = some;
198
+ supported++;
199
+ } catch (error) {
200
+ if (!missingError(error) && !unsupportedError(error)) failures++;
201
+ reasons.push(`${resource}: ${errorReason(error)}`);
202
+ }
203
+ }
204
+ sample.state =
205
+ supported === 3
206
+ ? "supported"
207
+ : supported > 0
208
+ ? "partial"
209
+ : failures > 0
210
+ ? "error"
211
+ : "unsupported";
212
+ if (reasons.length > 0) sample.reason = reasons.join("; ");
213
+ return sample;
214
+ }
215
+
216
+ function parsePressure(source: string, kind: "some" | "full"): ResourcePressureWindow {
217
+ const lines = source
218
+ .trim()
219
+ .split(/\r?\n/)
220
+ .filter((line) => line.startsWith(`${kind} `));
221
+ if (lines.length !== 1) throw new Error(`Malformed PSI: expected one ${kind} row.`);
222
+ const fields = new Map<string, string>();
223
+ for (const token of lines[0]!.trim().split(/\s+/).slice(1)) {
224
+ const [name, value, extra] = token.split("=");
225
+ if (!name || !value || extra !== undefined || fields.has(name))
226
+ throw new Error("Malformed PSI fields.");
227
+ fields.set(name, value);
228
+ }
229
+ const values = ["avg10", "avg60", "avg300"].map((name) => {
230
+ const raw = fields.get(name);
231
+ const value = Number(raw);
232
+ if (!raw || !/^\d+(?:\.\d+)?$/.test(raw) || !Number.isFinite(value) || value > 100) {
233
+ throw new Error(`Malformed PSI ${name}.`);
234
+ }
235
+ return value;
236
+ });
237
+ if (!/^\d+$/.test(fields.get("total") ?? "")) throw new Error("Malformed PSI total.");
238
+ return { avg10: values[0]!, avg60: values[1]!, avg300: values[2]! };
239
+ }
240
+
241
+ function collectOom(procRoot: string, now: number, key: string): ResourceOomSample {
242
+ try {
243
+ const rows = readBounded(join(procRoot, "vmstat"), 65_536)
244
+ .split(/\r?\n/)
245
+ .filter((line) => /^oom_kill\s/.test(line));
246
+ if (rows.length === 0) {
247
+ oomBaselines.delete(key);
248
+ return {
249
+ state: "unsupported",
250
+ total_kills: null,
251
+ kills_since_last_sample: null,
252
+ last_kill_age_ms: null,
253
+ reason: "Kernel does not expose oom_kill.",
254
+ };
255
+ }
256
+ const match = rows.length === 1 ? /^oom_kill\s+(\d+)\s*$/.exec(rows[0]!) : null;
257
+ const kills = match ? Number(match[1]) : NaN;
258
+ if (!Number.isSafeInteger(kills) || kills < 0 || !Number.isFinite(now))
259
+ throw new Error("Malformed OOM kill counter or sample time.");
260
+ const previous = oomBaselines.get(key);
261
+ const consecutive = previous && now > previous.sampledAt && kills >= previous.kills;
262
+ const delta = consecutive ? kills - previous.kills : null;
263
+ const lastKillAt = delta !== null && delta > 0 ? now : consecutive ? previous.lastKillAt : null;
264
+ oomBaselines.delete(key);
265
+ oomBaselines.set(key, { sampledAt: now, kills, lastKillAt });
266
+ if (oomBaselines.size > MAX_BASELINES) oomBaselines.delete(oomBaselines.keys().next().value!);
267
+ return {
268
+ state: "supported",
269
+ total_kills: kills,
270
+ kills_since_last_sample: delta,
271
+ last_kill_age_ms: lastKillAt === null ? null : now - lastKillAt,
272
+ ...(delta === null
273
+ ? { reason: "Recent OOM kills need two consecutive counters; baseline started." }
274
+ : {}),
275
+ };
276
+ } catch (error) {
277
+ oomBaselines.delete(key);
278
+ return {
279
+ state: missingError(error) || unsupportedError(error) ? "unsupported" : "error",
280
+ total_kills: null,
281
+ kills_since_last_sample: null,
282
+ last_kill_age_ms: null,
283
+ reason: errorReason(error),
284
+ };
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Read the kernel reclaim counters and turn them into per-second rates. Only a
290
+ * pair of consecutive reads can express a rate, so the first read after a
291
+ * start, a restart, a stalled clock, or a counter reset reports
292
+ * `counters_reset` with null rates instead of inventing a baseline.
293
+ */
294
+ function collectVmstat(procRoot: string, now: number, key: string): ResourceVmstatSample {
295
+ try {
296
+ if (!Number.isFinite(now)) throw new Error("Malformed reclaim sample time.");
297
+ const counters = parseVmstatCounters(readBounded(join(procRoot, "vmstat"), 65_536));
298
+ const missing = VMSTAT_COUNTERS.filter((name) => counters[name] === null);
299
+ if (missing.length === VMSTAT_COUNTERS.length) {
300
+ vmstatBaselines.delete(key);
301
+ return unavailableVmstat(
302
+ "unsupported",
303
+ "Kernel does not expose the memory reclaim counters.",
304
+ false,
305
+ );
306
+ }
307
+ const state: ResourceSupportState = missing.length > 0 ? "partial" : "supported";
308
+ const partialReason =
309
+ missing.length > 0 ? `Kernel does not expose ${missing.join(", ")}.` : undefined;
310
+ const previous = vmstatBaselines.get(key);
311
+ vmstatBaselines.delete(key);
312
+ vmstatBaselines.set(key, { sampledAt: now, counters });
313
+ if (vmstatBaselines.size > MAX_BASELINES)
314
+ vmstatBaselines.delete(vmstatBaselines.keys().next().value!);
315
+ const elapsedSeconds = previous ? (now - previous.sampledAt) / 1_000 : 0;
316
+ if (!previous || !Number.isFinite(elapsedSeconds) || elapsedSeconds <= 0) {
317
+ return unavailableVmstat(
318
+ state,
319
+ joinReasons(
320
+ partialReason,
321
+ previous
322
+ ? "The reclaim sample interval did not advance, so a new baseline started."
323
+ : "Reclaim rates need two consecutive counters, so a baseline started.",
324
+ ),
325
+ );
326
+ }
327
+ const decreased = VMSTAT_COUNTERS.some((name) => {
328
+ const current = counters[name];
329
+ const last = previous.counters[name];
330
+ return current !== null && last !== null && current < last;
331
+ });
332
+ if (decreased) {
333
+ return unavailableVmstat(
334
+ state,
335
+ joinReasons(partialReason, "Kernel reclaim counters decreased, so a new baseline started."),
336
+ );
337
+ }
338
+ const perSecond = (name: VmstatCounterName): number | null => {
339
+ const current = counters[name];
340
+ const last = previous.counters[name];
341
+ if (current === null || last === null) return null;
342
+ return (current - last) / elapsedSeconds;
343
+ };
344
+ const swapIn = perSecond("pswpin");
345
+ const swapOut = perSecond("pswpout");
346
+ return {
347
+ state,
348
+ swap_in_bytes_per_second: swapIn === null ? null : round(swapIn * VMSTAT_PAGE_SIZE_BYTES),
349
+ swap_out_bytes_per_second: swapOut === null ? null : round(swapOut * VMSTAT_PAGE_SIZE_BYTES),
350
+ direct_reclaim_pages_per_second: nullableRound(perSecond("pgscan_direct")),
351
+ major_faults_per_second: nullableRound(perSecond("pgmajfault")),
352
+ counters_reset: false,
353
+ ...(partialReason ? { reason: partialReason } : {}),
354
+ };
355
+ } catch (error) {
356
+ vmstatBaselines.delete(key);
357
+ return unavailableVmstat(
358
+ missingError(error) || unsupportedError(error) ? "unsupported" : "error",
359
+ errorReason(error),
360
+ false,
361
+ );
362
+ }
363
+ }
364
+
365
+ function parseVmstatCounters(source: string): VmstatCounters {
366
+ const counters: VmstatCounters = {
367
+ pswpin: null,
368
+ pswpout: null,
369
+ pgscan_direct: null,
370
+ pgmajfault: null,
371
+ };
372
+ for (const name of VMSTAT_COUNTERS) {
373
+ // Anchor on whitespace so pgscan_direct never absorbs pgscan_direct_throttle.
374
+ const rows = source.split(/\r?\n/).filter((line) => line.startsWith(`${name} `));
375
+ if (rows.length === 0) continue;
376
+ if (rows.length > 1) throw new Error(`Duplicate vmstat row for ${name}.`);
377
+ const match = new RegExp(`^${name}\\s+(\\d+)\\s*$`).exec(rows[0]!);
378
+ const value = match ? Number(match[1]) : NaN;
379
+ if (!Number.isSafeInteger(value) || value < 0)
380
+ throw new Error(`Malformed vmstat counter for ${name}.`);
381
+ counters[name] = value;
382
+ }
383
+ return counters;
384
+ }
385
+
386
+ function unavailableVmstat(
387
+ state: ResourceSupportState,
388
+ reason: string,
389
+ countersReset = true,
390
+ ): ResourceVmstatSample {
391
+ return {
392
+ state,
393
+ swap_in_bytes_per_second: null,
394
+ swap_out_bytes_per_second: null,
395
+ direct_reclaim_pages_per_second: null,
396
+ major_faults_per_second: null,
397
+ counters_reset: countersReset,
398
+ reason,
399
+ };
400
+ }
401
+
402
+ function joinReasons(...parts: (string | undefined)[]): string {
403
+ return parts.filter((part): part is string => Boolean(part)).join(" ");
404
+ }
405
+
406
+ function collectIo(procRoot: string, sysRoot: string, now: number, key: string): ResourceIoSample {
407
+ try {
408
+ const devices = readIoCounters(procRoot, sysRoot);
409
+ const previous = ioBaselines.get(key);
410
+ ioBaselines.delete(key);
411
+ ioBaselines.set(key, { sampledAt: now, devices });
412
+ if (ioBaselines.size > MAX_BASELINES) ioBaselines.delete(ioBaselines.keys().next().value!);
413
+ if (!previous) return unavailableIo("supported", "Disk I/O rates need two samples.");
414
+ const elapsed = (now - previous.sampledAt) / 1_000;
415
+ if (!Number.isFinite(elapsed) || elapsed <= 0)
416
+ return unavailableIo("supported", "Disk I/O sample interval did not advance.");
417
+ if (devices.size !== previous.devices.size)
418
+ return unavailableIo("supported", "Block devices changed; disk I/O baseline restarted.");
419
+ let read = 0;
420
+ let write = 0;
421
+ for (const [id, current] of devices) {
422
+ const last = previous.devices.get(id);
423
+ if (!last || current.read < last.read || current.write < last.write) {
424
+ return unavailableIo(
425
+ "supported",
426
+ "Block device counters reset or devices changed; disk I/O baseline restarted.",
427
+ );
428
+ }
429
+ read += current.read - last.read;
430
+ write += current.write - last.write;
431
+ }
432
+ // Linux diskstats sectors are always 512 bytes, independent of device block size.
433
+ return {
434
+ state: "supported",
435
+ read_bytes_per_second: round((read * 512) / elapsed),
436
+ write_bytes_per_second: round((write * 512) / elapsed),
437
+ };
438
+ } catch (error) {
439
+ ioBaselines.delete(key);
440
+ return unavailableIo(
441
+ missingError(error) || unsupportedError(error) ? "unsupported" : "error",
442
+ errorReason(error),
443
+ );
444
+ }
445
+ }
446
+
447
+ function readIoCounters(procRoot: string, sysRoot: string): Map<string, IoCounters> {
448
+ const entries = readdirSync(join(sysRoot, "block"));
449
+ if (entries.length > MAX_BLOCK_DEVICES)
450
+ throw new Error("Block device inventory exceeds the collector limit.");
451
+ const selected = new Set<string>();
452
+ for (const device of entries) {
453
+ // Whole devices only. Exclude memory-backed disks and stacked devices so
454
+ // device-mapper/RAID activity is counted once, at the underlying disks.
455
+ if (/^(?:loop|ram|zram)\d+$/.test(device)) continue;
456
+ if (readdirSync(join(sysRoot, "block", device, "slaves")).length > 0) continue;
457
+ selected.add(device);
458
+ }
459
+ if (selected.size === 0)
460
+ throw Object.assign(new Error("No underlying block devices are visible."), { code: "ENOTSUP" });
461
+ const results = new Map<string, IoCounters>();
462
+ for (const line of readBounded(join(procRoot, "diskstats"), 1_048_576).trim().split(/\r?\n/)) {
463
+ const fields = line.trim().split(/\s+/);
464
+ const device = fields[2];
465
+ if (!device || !selected.has(device)) continue;
466
+ if (
467
+ fields.length < 14 ||
468
+ ![...fields.slice(0, 2), ...fields.slice(3)].every((value) => /^\d+$/.test(value))
469
+ ) {
470
+ throw new Error(`Malformed diskstats for ${device}.`);
471
+ }
472
+ const read = Number(fields[5]);
473
+ const write = Number(fields[9]);
474
+ if (![read, write].every(Number.isSafeInteger))
475
+ throw new Error(`Invalid diskstats counters for ${device}.`);
476
+ const id = `${fields[0]}:${fields[1]}:${device}`;
477
+ if (results.has(id)) throw new Error(`Duplicate diskstats for ${device}.`);
478
+ results.set(id, { read, write });
479
+ }
480
+ if (results.size !== selected.size)
481
+ throw new Error("Block device inventory changed while collecting diskstats.");
482
+ return results;
483
+ }
484
+
485
+ function readBounded(path: string, limit: number): string {
486
+ const fd = openSync(path, "r");
487
+ try {
488
+ const buffer = Buffer.alloc(limit + 1);
489
+ let offset = 0;
490
+ while (offset < buffer.length) {
491
+ const count = readSync(fd, buffer, offset, buffer.length - offset, null);
492
+ if (count === 0) break;
493
+ offset += count;
494
+ }
495
+ if (offset > limit) throw new Error("Resource counter file exceeds the collector limit.");
496
+ return buffer.toString("utf8", 0, offset);
497
+ } finally {
498
+ closeSync(fd);
499
+ }
500
+ }
501
+
502
+ function errorCode(error: unknown): string | undefined {
503
+ return error && typeof error === "object" && "code" in error ? String(error.code) : undefined;
504
+ }
505
+
506
+ function missingError(error: unknown): boolean {
507
+ return ["ENOENT", "ENOTDIR"].includes(errorCode(error) ?? "");
508
+ }
509
+
510
+ function unsupportedError(error: unknown): boolean {
511
+ return ["ENOSYS", "ENOTSUP", "EOPNOTSUPP"].includes(errorCode(error) ?? "");
512
+ }
513
+
514
+ function errorReason(error: unknown): string {
515
+ return (
516
+ errorCode(error) ?? (error instanceof Error ? error.message : "Resource collection failed.")
517
+ );
518
+ }
519
+
520
+ function unavailableIo(state: ResourceSupportState, reason: string): ResourceIoSample {
521
+ return { state, read_bytes_per_second: null, write_bytes_per_second: null, reason };
522
+ }
523
+
524
+ function round(value: number): number {
525
+ return Math.round(value * 100) / 100;
526
+ }
527
+
528
+ function nullableRound(value: number | null): number | null {
529
+ return value === null ? null : round(value);
530
+ }
@@ -65,6 +65,11 @@ export {
65
65
  export const SEMANTIC_SERVICE_DEFAULT_DEBOUNCE_MS = 5_000;
66
66
  export const SEMANTIC_SERVICE_DEFAULT_WAKE_MS = 1_000;
67
67
  export const SEMANTIC_SERVICE_DEFAULT_HEARTBEAT_MS = 5_000;
68
+ // Ceiling for the wake timer while sweeps find nothing. The timer is only the fallback
69
+ // behind fs.watch on the active ledger, so backing it off delays nothing that writes an
70
+ // event; it stops an idle daemon from re-reading the manifest and agent documents once a
71
+ // second all night. Observed 2026-09-06: 142,637 sweeps in 70 h, 10,291 of them passes.
72
+ export const SEMANTIC_SERVICE_DEFAULT_IDLE_WAKE_MAX_MS = 30_000;
68
73
  const FOREIGN_STATUS_STALE_MS = 2 * 60_000;
69
74
  const MAX_FILE_BYTES = 512 * 1024;
70
75
  const MAX_LOG_BYTES = 512 * 1024;
@@ -96,6 +101,7 @@ export interface RunSemanticServiceDaemonInput {
96
101
  callsPerHour?: number;
97
102
  debounceMs?: number;
98
103
  wakeIntervalMs?: number;
104
+ idleWakeMaxMs?: number;
99
105
  heartbeatIntervalMs?: number;
100
106
  maxSweeps?: number;
101
107
  now?: () => Date;
@@ -251,6 +257,13 @@ export async function runSemanticServiceDaemon(
251
257
  input.heartbeatIntervalMs ?? SEMANTIC_SERVICE_DEFAULT_HEARTBEAT_MS,
252
258
  "heartbeat interval",
253
259
  );
260
+ const idleWakeMaxMs = Math.max(
261
+ wakeIntervalMs,
262
+ positiveInterval(
263
+ input.idleWakeMaxMs ?? SEMANTIC_SERVICE_DEFAULT_IDLE_WAKE_MAX_MS,
264
+ "idle wake ceiling",
265
+ ),
266
+ );
254
267
  const readSince = input.readSince ?? readLedgerV3Since;
255
268
  const runOnce =
256
269
  input.runOnce ??
@@ -279,6 +292,8 @@ export async function runSemanticServiceDaemon(
279
292
  };
280
293
  let stopRequested = false;
281
294
  let dirtySince: number | undefined;
295
+ let idleSweeps = 0;
296
+ let wakeEarly: (() => void) | undefined;
282
297
  let lastLoggedErrorCode: string | undefined;
283
298
  let lastLoggedErrorAt = Number.NEGATIVE_INFINITY;
284
299
  const writeStatus = (): void => {
@@ -289,6 +304,8 @@ export async function runSemanticServiceDaemon(
289
304
  stopRequested = true;
290
305
  status.state = "stopping";
291
306
  writeStatus();
307
+ // A backed-off wait must not hold a stop request for up to the idle ceiling.
308
+ wakeEarly?.();
292
309
  };
293
310
  process.on("SIGINT", requestStop);
294
311
  process.on("SIGTERM", requestStop);
@@ -306,6 +323,7 @@ export async function runSemanticServiceDaemon(
306
323
  while (!stopRequested && !existsSync(paths.stop)) {
307
324
  const sweepAt = now();
308
325
  status.sweep_count += 1;
326
+ let sawWork = false;
309
327
  try {
310
328
  const before = safeManifest(coordRoot);
311
329
  let read = readSince(coordRoot, before?.cursor, { authority: "active" });
@@ -313,6 +331,7 @@ export async function runSemanticServiceDaemon(
313
331
  read = readSince(coordRoot, undefined, { authority: "active" });
314
332
  }
315
333
  requireCompleteLedger(read);
334
+ if (read.events.length > 0) sawWork = true;
316
335
  if (read.events.length > 0 || !before?.cursor) {
317
336
  dirtySince ??= sweepAt.getTime();
318
337
  }
@@ -342,6 +361,7 @@ export async function runSemanticServiceDaemon(
342
361
  shouldStop: () => stopRequested || existsSync(paths.stop),
343
362
  });
344
363
  status.pass_count += 1;
364
+ sawWork = true;
345
365
  status.model_calls += report.model_calls;
346
366
  status.cache_hits += report.cache_hits;
347
367
  status.process_usage = mergeSemanticUsageAggregates(
@@ -392,10 +412,19 @@ export async function runSemanticServiceDaemon(
392
412
  }
393
413
  status.last_sweep_at = now().toISOString();
394
414
  writeStatus();
415
+ idleSweeps = sawWork ? 0 : idleSweeps + 1;
395
416
  if (input.maxSweeps !== undefined && status.sweep_count >= input.maxSweeps) break;
396
417
  if (!stopRequested && !existsSync(paths.stop)) {
397
- if (input.waitForWake) await input.waitForWake(wakeIntervalMs);
398
- else await waitForLedgerWake(eventV3ActiveWatchPath(coordRoot), wakeIntervalMs);
418
+ // Double the fallback timer per idle sweep, up to the ceiling; any sweep that
419
+ // sees events or runs a pass snaps it back to the configured interval.
420
+ const waitMs = Math.min(idleWakeMaxMs, wakeIntervalMs * 2 ** Math.min(idleSweeps, 20));
421
+ if (input.waitForWake) await input.waitForWake(waitMs);
422
+ else {
423
+ await waitForLedgerWake(eventV3ActiveWatchPath(coordRoot), waitMs, (wake) => {
424
+ wakeEarly = wake;
425
+ });
426
+ wakeEarly = undefined;
427
+ }
399
428
  }
400
429
  }
401
430
  } finally {
@@ -566,7 +595,11 @@ function pidAlive(pid: number): boolean {
566
595
  }
567
596
  }
568
597
 
569
- async function waitForLedgerWake(path: string, milliseconds: number): Promise<void> {
598
+ async function waitForLedgerWake(
599
+ path: string,
600
+ milliseconds: number,
601
+ onWait?: (wake: () => void) => void,
602
+ ): Promise<void> {
570
603
  await new Promise<void>((done) => {
571
604
  let settled = false;
572
605
  let watcher: ReturnType<typeof watch> | undefined;
@@ -583,6 +616,7 @@ async function waitForLedgerWake(path: string, milliseconds: number): Promise<vo
583
616
  } catch {
584
617
  // The timer is the polling fallback when the active file does not exist yet.
585
618
  }
619
+ onWait?.(finish);
586
620
  });
587
621
  }
588
622
 
@@ -395,6 +395,42 @@ function durableHistoryFamilies(): HarneryStorageFamily[] {
395
395
  );
396
396
  return [
397
397
  ...histories,
398
+ family({
399
+ id: "agent-mailbox-history",
400
+ owner: "agent mailbox service",
401
+ storage_class: "durable-object-history",
402
+ roots: (context) => [subtree(context, ".harnery/mailbox")],
403
+ format: "jsonl",
404
+ durability: "crash-safe",
405
+ writer_model: "multi-process",
406
+ policy: ownerHistoryPolicy("agent-mailbox-history-v1"),
407
+ consumers: ["agent mailbox service", "prompt delivery", "storage inventory"],
408
+ provider: filesystemProvider("agent-mailbox-provider", "agent mailbox lifecycle"),
409
+ }),
410
+ family({
411
+ id: "workflow-approval-history",
412
+ owner: "workflow approval service",
413
+ storage_class: "durable-object-history",
414
+ roots: (context) => [subtree(context, ".harnery/approvals")],
415
+ format: "json",
416
+ durability: "crash-safe",
417
+ writer_model: "object-owned",
418
+ policy: ownerHistoryPolicy("workflow-approval-history-v1"),
419
+ consumers: ["workflow approval service", "governor service", "storage inventory"],
420
+ provider: filesystemProvider("workflow-approval-provider", "workflow approval lifecycle"),
421
+ }),
422
+ family({
423
+ id: "agent-name-history",
424
+ owner: "agent identity service",
425
+ storage_class: "durable-object-history",
426
+ roots: (context) => [exact(context, ".harnery/.name-history", "file")],
427
+ format: "jsonl",
428
+ durability: "crash-safe",
429
+ writer_model: "multi-process",
430
+ policy: ownerHistoryPolicy("agent-name-history-v1"),
431
+ consumers: ["agent identity service", "session rendering", "storage inventory"],
432
+ provider: filesystemProvider("agent-name-history-provider", "agent identity lifecycle"),
433
+ }),
398
434
  family({
399
435
  id: "project-configuration",
400
436
  owner: "Harnery configuration service",
@@ -432,6 +468,11 @@ function durableHistoryFamilies(): HarneryStorageFamily[] {
432
468
  function cacheFamilies(): HarneryStorageFamily[] {
433
469
  const definitions = [
434
470
  ["active-agent-projection", ".harnery/active", "Event Ledger V3 and native heartbeats"],
471
+ [
472
+ "storage-dashboard-cache",
473
+ ".harnery/cache/storage-footprint",
474
+ "canonical metadata-only storage inventory",
475
+ ],
435
476
  ["pid-map-cache", ".harnery/pid-map", "active native process observations"],
436
477
  ["remote-presence-cache", ".harnery/presence/remote", "presence relay observations"],
437
478
  ["event-v3-live-display", ".harnery/live/v3", "Event Ledger V3 canonical events"],