humanish 0.106.1 → 0.108.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 (668) hide show
  1. package/ARCHITECTURE.md +38 -32
  2. package/CHANGELOG.md +28 -0
  3. package/CONTEXT.md +51 -41
  4. package/README.md +152 -174
  5. package/SECURITY.md +16 -26
  6. package/TELEMETRY.md +8 -6
  7. package/dist/actors/affordance.d.ts +4 -4
  8. package/dist/actors/affordance.js +12 -12
  9. package/dist/actors/agent-session.d.ts +2 -2
  10. package/dist/actors/agent-session.js +7 -7
  11. package/dist/actors/codex/app-server-actor-trace.d.ts +1 -1
  12. package/dist/actors/codex/app-server-actor-trace.js +1 -1
  13. package/dist/actors/codex/app-server-client.js +2 -1
  14. package/dist/actors/codex/app-server-trace.d.ts +0 -1
  15. package/dist/actors/codex/app-server-trace.js +1 -3
  16. package/dist/actors/codex/app-server-ui.js +1 -1
  17. package/dist/actors/codex/app-server.js +2 -1
  18. package/dist/actors/codex/codex-admission.d.ts +91 -0
  19. package/dist/actors/codex/codex-admission.js +150 -0
  20. package/dist/actors/codex/protocol-compat.d.ts +27 -0
  21. package/dist/actors/codex/protocol-compat.js +357 -0
  22. package/dist/actors/codex/protocol-contract.d.ts +61 -0
  23. package/dist/actors/codex/protocol-contract.js +471 -0
  24. package/dist/actors/codex/restricted-executable.d.ts +28 -4
  25. package/dist/actors/codex/restricted-executable.js +104 -29
  26. package/dist/actors/codex/restricted-launch.d.ts +9 -2
  27. package/dist/actors/codex/restricted-launch.js +43 -4
  28. package/dist/actors/codex/restricted-notifications.d.ts +101 -0
  29. package/dist/actors/codex/restricted-notifications.js +277 -0
  30. package/dist/actors/codex/restricted-participant.d.ts +30 -0
  31. package/dist/actors/codex/restricted-participant.js +66 -6
  32. package/dist/actors/codex/restricted-policy.d.ts +11 -3
  33. package/dist/actors/codex/restricted-policy.js +2 -2
  34. package/dist/actors/codex/restricted-session.d.ts +16 -3
  35. package/dist/actors/codex/restricted-session.js +60 -22
  36. package/dist/actors/codex/restricted-transport.d.ts +29 -0
  37. package/dist/actors/codex/restricted-transport.js +139 -27
  38. package/dist/actors/codex/restricted-turn.d.ts +19 -4
  39. package/dist/actors/codex/restricted-turn.js +43 -30
  40. package/dist/actors/computer-use/actor.d.ts +17 -17
  41. package/dist/actors/computer-use/actor.js +6 -6
  42. package/dist/actors/computer-use/admission-limit.d.ts +1 -1
  43. package/dist/actors/computer-use/admission-limit.js +1 -1
  44. package/dist/actors/computer-use/loop/actions.d.ts +3 -3
  45. package/dist/actors/computer-use/loop/actions.js +10 -10
  46. package/dist/actors/computer-use/loop/backstop.d.ts +3 -3
  47. package/dist/actors/computer-use/loop/backstop.js +15 -13
  48. package/dist/actors/computer-use/loop/ending.d.ts +7 -7
  49. package/dist/actors/computer-use/loop/ending.js +9 -9
  50. package/dist/actors/computer-use/loop/observation.d.ts +2 -2
  51. package/dist/actors/computer-use/loop/observation.js +9 -9
  52. package/dist/actors/computer-use/loop/provider-call.d.ts +1 -1
  53. package/dist/actors/computer-use/loop/provider-call.js +1 -1
  54. package/dist/actors/computer-use/loop/session.js +3 -3
  55. package/dist/actors/computer-use/loop/spend.d.ts +2 -2
  56. package/dist/actors/computer-use/loop/spend.js +4 -4
  57. package/dist/actors/computer-use/loop/trace.js +11 -10
  58. package/dist/actors/computer-use/loop/types.d.ts +37 -26
  59. package/dist/actors/computer-use/loop/usage.js +2 -2
  60. package/dist/actors/computer-use/loop.js +4 -4
  61. package/dist/actors/computer-use/openai-context.d.ts +37 -0
  62. package/dist/actors/computer-use/openai-context.js +214 -0
  63. package/dist/actors/computer-use/openai-provider.d.ts +12 -9
  64. package/dist/actors/computer-use/openai-provider.js +47 -36
  65. package/dist/actors/computer-use/openai-requests.d.ts +36 -10
  66. package/dist/actors/computer-use/openai-requests.js +82 -15
  67. package/dist/actors/computer-use/openai-wire.d.ts +19 -7
  68. package/dist/actors/computer-use/openai-wire.js +36 -34
  69. package/dist/actors/contract.d.ts +86 -49
  70. package/dist/actors/contract.js +29 -23
  71. package/dist/actors/local-agent/claude-session.d.ts +1 -1
  72. package/dist/actors/local-agent/claude-session.js +10 -9
  73. package/dist/actors/local-agent/cli.d.ts +8 -6
  74. package/dist/actors/local-agent/cli.js +24 -28
  75. package/dist/actors/local-agent/readiness.d.ts +2 -2
  76. package/dist/actors/local-agent/readiness.js +4 -4
  77. package/dist/actors/reasoning-effort.js +6 -6
  78. package/dist/actors/registry.d.ts +10 -10
  79. package/dist/actors/registry.js +18 -17
  80. package/dist/actors/scripted-browser/actor.d.ts +8 -8
  81. package/dist/actors/scripted-browser/actor.js +16 -16
  82. package/dist/actors/scripted-browser/browser-command.js +1 -1
  83. package/dist/actors/scripted-browser/journey.js +15 -4
  84. package/dist/actors/scripted-browser/steps.js +19 -1
  85. package/dist/actors/scripted-browser/types.d.ts +12 -2
  86. package/dist/actors/stop-conditions.d.ts +1 -1
  87. package/dist/actors/stop-conditions.js +1 -3
  88. package/dist/analysis/automatic-completion.d.ts +13 -8
  89. package/dist/analysis/automatic-completion.js +14 -8
  90. package/dist/analysis/automatic-config.d.ts +1 -1
  91. package/dist/analysis/automatic-config.js +2 -2
  92. package/dist/analysis/automatic.d.ts +10 -4
  93. package/dist/analysis/automatic.js +17 -13
  94. package/dist/analysis/codex-config.d.ts +6 -2
  95. package/dist/analysis/codex-config.js +22 -14
  96. package/dist/analysis/diagnostics.d.ts +23 -0
  97. package/dist/analysis/diagnostics.js +112 -0
  98. package/dist/analysis/evidence-sources.js +2 -2
  99. package/dist/analysis/evidence.js +6 -6
  100. package/dist/analysis/execute.d.ts +7 -1
  101. package/dist/analysis/execute.js +39 -12
  102. package/dist/analysis/job.d.ts +8 -5
  103. package/dist/analysis/job.js +15 -14
  104. package/dist/analysis/load.d.ts +1 -1
  105. package/dist/analysis/load.js +5 -5
  106. package/dist/analysis/provider.d.ts +9 -1
  107. package/dist/analysis/restricted-codex.d.ts +2 -0
  108. package/dist/analysis/restricted-codex.js +10 -1
  109. package/dist/analysis/service.d.ts +2 -0
  110. package/dist/analysis/service.js +35 -16
  111. package/dist/analysis/sharing.d.ts +2 -2
  112. package/dist/analysis/sharing.js +5 -5
  113. package/dist/analysis/store-executions.d.ts +5 -5
  114. package/dist/analysis/store-executions.js +12 -12
  115. package/dist/analysis/store.d.ts +4 -4
  116. package/dist/analysis/store.js +13 -13
  117. package/dist/cli/commands/analysis-signals.d.ts +2 -1
  118. package/dist/cli/commands/analysis-signals.js +4 -1
  119. package/dist/cli/commands/analyze.js +23 -18
  120. package/dist/cli/commands/codex.js +9 -9
  121. package/dist/cli/commands/comms.js +62 -34
  122. package/dist/cli/commands/feedback.js +20 -21
  123. package/dist/cli/commands/lab-format.d.ts +5 -4
  124. package/dist/cli/commands/lab-format.js +35 -23
  125. package/dist/cli/commands/lab-route-computer-use.d.ts +0 -2
  126. package/dist/cli/commands/lab-route-computer-use.js +32 -21
  127. package/dist/cli/commands/lab-route-preview.d.ts +1 -3
  128. package/dist/cli/commands/lab-route-preview.js +7 -7
  129. package/dist/cli/commands/lab-route-run.d.ts +6 -4
  130. package/dist/cli/commands/lab-route-run.js +6 -2
  131. package/dist/cli/commands/lab-route-scripted.d.ts +0 -2
  132. package/dist/cli/commands/lab-route-scripted.js +0 -1
  133. package/dist/cli/commands/lab-route-shared-world.d.ts +0 -2
  134. package/dist/cli/commands/lab-route-shared-world.js +3 -4
  135. package/dist/cli/commands/lab-route-terminal.d.ts +0 -2
  136. package/dist/cli/commands/lab-route-terminal.js +0 -1
  137. package/dist/cli/commands/lab-run.js +84 -54
  138. package/dist/cli/commands/lab-scorer.d.ts +2 -4
  139. package/dist/cli/commands/lab-scorer.js +1 -1
  140. package/dist/cli/commands/migrate.d.ts +4 -0
  141. package/dist/cli/commands/migrate.js +65 -0
  142. package/dist/cli/commands/observe.js +168 -107
  143. package/dist/cli/commands/run-command.d.ts +10 -0
  144. package/dist/cli/commands/run-command.js +85 -0
  145. package/dist/cli/commands/run-signals.d.ts +30 -0
  146. package/dist/cli/commands/run-signals.js +146 -0
  147. package/dist/cli/commands/runs.js +137 -136
  148. package/dist/cli/commands/setup.d.ts +2 -2
  149. package/dist/cli/commands/setup.js +26 -21
  150. package/dist/cli/commands/study.d.ts +3 -0
  151. package/dist/cli/commands/study.js +182 -0
  152. package/dist/cli/commands/tui.d.ts +4 -4
  153. package/dist/cli/commands/tui.js +21 -21
  154. package/dist/cli/commands/watch.js +64 -77
  155. package/dist/cli/deprecations.d.ts +18 -0
  156. package/dist/cli/deprecations.js +48 -0
  157. package/dist/cli/doctor.d.ts +18 -5
  158. package/dist/cli/doctor.js +111 -48
  159. package/dist/cli/first-run-path.d.ts +12 -10
  160. package/dist/cli/first-run-path.js +30 -28
  161. package/dist/cli/io.d.ts +46 -10
  162. package/dist/cli/io.js +109 -30
  163. package/dist/cli/observer-follow.d.ts +2 -2
  164. package/dist/cli/observer-follow.js +5 -6
  165. package/dist/cli/orientation.d.ts +6 -3
  166. package/dist/cli/orientation.js +43 -39
  167. package/dist/cli/product-sentence.d.ts +6 -0
  168. package/dist/cli/product-sentence.js +6 -0
  169. package/dist/cli/program.d.ts +6 -1
  170. package/dist/cli/program.js +112 -43
  171. package/dist/cli/telemetry.d.ts +17 -17
  172. package/dist/cli/telemetry.js +49 -41
  173. package/dist/cli.js +1 -1
  174. package/dist/comms/capture-surface.d.ts +14 -13
  175. package/dist/comms/capture-surface.js +23 -22
  176. package/dist/comms/catch-host.d.ts +8 -8
  177. package/dist/comms/catch-host.js +14 -14
  178. package/dist/comms/connections.js +4 -4
  179. package/dist/comms/email-catch.d.ts +3 -3
  180. package/dist/comms/email-catch.js +8 -8
  181. package/dist/comms/external-evidence.d.ts +27 -0
  182. package/dist/comms/external-evidence.js +93 -0
  183. package/dist/comms/extract.js +2 -2
  184. package/dist/comms/fake-inbox.d.ts +2 -2
  185. package/dist/comms/fake-inbox.js +2 -2
  186. package/dist/comms/lease-store.js +7 -7
  187. package/dist/comms/receiving-render.d.ts +1 -1
  188. package/dist/comms/receiving-render.js +2 -2
  189. package/dist/comms/sandbox-catch-script.d.ts +6 -6
  190. package/dist/comms/sandbox-catch-script.js +6 -6
  191. package/dist/comms/sandbox-catch.d.ts +36 -37
  192. package/dist/comms/sandbox-catch.js +34 -35
  193. package/dist/comms/setup.js +42 -8
  194. package/dist/comms/thread-evidence.d.ts +1 -1
  195. package/dist/comms/thread-evidence.js +6 -6
  196. package/dist/comms/types.d.ts +6 -6
  197. package/dist/comms/types.js +4 -4
  198. package/dist/evidence/browser-hygiene.d.ts +1 -1
  199. package/dist/evidence/browser-hygiene.js +1 -1
  200. package/dist/evidence/frame-signature.d.ts +1 -1
  201. package/dist/evidence/frame-signature.js +4 -4
  202. package/dist/evidence/redaction.d.ts +3 -3
  203. package/dist/evidence/redaction.js +5 -5
  204. package/dist/evidence/secret-scrub.d.ts +9 -0
  205. package/dist/evidence/secret-scrub.js +96 -0
  206. package/dist/feedback/draft.js +41 -85
  207. package/dist/feedback/export-bundle.js +5 -5
  208. package/dist/feedback/export.d.ts +5 -1
  209. package/dist/feedback/export.js +16 -17
  210. package/dist/feedback/feedback.d.ts +3 -3
  211. package/dist/feedback/feedback.js +21 -3
  212. package/dist/guest/chromium-text.js +2 -2
  213. package/dist/guest/desktop-native.d.ts +1 -1
  214. package/dist/guest/runtime.js +1 -1
  215. package/dist/index.d.ts +14 -15
  216. package/dist/index.js +10 -11
  217. package/dist/keys/key-resolution.d.ts +7 -5
  218. package/dist/keys/key-resolution.js +40 -38
  219. package/dist/lab/adapter-extension.d.ts +35 -13
  220. package/dist/lab/adapter-extension.js +50 -134
  221. package/dist/lab/adapter-scorer-loader.d.ts +32 -12
  222. package/dist/lab/adapter-scorer-loader.js +56 -32
  223. package/dist/lab/composition-rules.js +58 -58
  224. package/dist/lab/config.js +85 -43
  225. package/dist/lab/deprecated-fields.d.ts +10 -0
  226. package/dist/lab/deprecated-fields.js +24 -0
  227. package/dist/lab/device-presets.d.ts +10 -10
  228. package/dist/lab/device-presets.js +11 -11
  229. package/dist/lab/discover.d.ts +13 -9
  230. package/dist/lab/discover.js +118 -60
  231. package/dist/lab/doctor.d.ts +53 -11
  232. package/dist/lab/doctor.js +193 -47
  233. package/dist/lab/init-plan.js +1 -3
  234. package/dist/lab/init-templates.js +147 -147
  235. package/dist/lab/init.d.ts +1 -1
  236. package/dist/lab/init.js +41 -9
  237. package/dist/lab/keys.d.ts +2 -0
  238. package/dist/lab/keys.js +43 -9
  239. package/dist/lab/lab-deps.d.ts +80 -0
  240. package/dist/lab/lab-deps.js +4 -0
  241. package/dist/lab/parse/actors.js +17 -16
  242. package/dist/lab/parse/comms.js +8 -7
  243. package/dist/lab/parse/execution.js +15 -14
  244. package/dist/lab/parse/study-v3.d.ts +23 -0
  245. package/dist/lab/parse/study-v3.js +259 -0
  246. package/dist/lab/parse/subject-state.d.ts +7 -7
  247. package/dist/lab/parse/subject-state.js +18 -17
  248. package/dist/lab/parse/subject.js +30 -29
  249. package/dist/lab/parse/values.d.ts +1 -2
  250. package/dist/lab/parse/values.js +3 -6
  251. package/dist/lab/persona-resolve.d.ts +7 -7
  252. package/dist/lab/persona-resolve.js +8 -8
  253. package/dist/lab/persona.js +1 -3
  254. package/dist/lab/plan-base.d.ts +0 -2
  255. package/dist/lab/plan-base.js +6 -4
  256. package/dist/lab/plan-participants.d.ts +7 -7
  257. package/dist/lab/plan-participants.js +2 -2
  258. package/dist/lab/plan-types.d.ts +26 -8
  259. package/dist/lab/plan.d.ts +4 -18
  260. package/dist/lab/plan.js +24 -79
  261. package/dist/lab/preflight-probes.js +11 -11
  262. package/dist/lab/preflight-result.d.ts +2 -1
  263. package/dist/lab/preflight-result.js +5 -6
  264. package/dist/lab/preflight.d.ts +7 -8
  265. package/dist/lab/preflight.js +6 -6
  266. package/dist/lab/requirements.d.ts +20 -0
  267. package/dist/lab/requirements.js +54 -0
  268. package/dist/lab/route-inputs.d.ts +23 -12
  269. package/dist/lab/route-inputs.js +48 -51
  270. package/dist/lab/routing.d.ts +42 -41
  271. package/dist/lab/routing.js +66 -68
  272. package/dist/lab/run-lab-events.d.ts +3 -11
  273. package/dist/lab/run-lab-events.js +2 -16
  274. package/dist/lab/run-lab-homes.d.ts +76 -0
  275. package/dist/lab/run-lab-homes.js +7 -0
  276. package/dist/lab/run-lab-options.d.ts +12 -66
  277. package/dist/lab/run-lab-options.js +58 -285
  278. package/dist/lab/summary.d.ts +15 -9
  279. package/dist/lab/summary.js +24 -17
  280. package/dist/lab/tasks.d.ts +15 -15
  281. package/dist/lab/tasks.js +14 -14
  282. package/dist/lab/types.d.ts +201 -195
  283. package/dist/lab/types.js +2 -0
  284. package/dist/lab/validation.d.ts +21 -21
  285. package/dist/lab/validation.js +61 -59
  286. package/dist/lab/warnings.d.ts +10 -0
  287. package/dist/lab/warnings.js +70 -41
  288. package/dist/library-aliases.d.ts +56 -0
  289. package/dist/library-aliases.js +22 -0
  290. package/dist/observer/artifact.d.ts +2 -2
  291. package/dist/observer/artifact.js +9 -9
  292. package/dist/observer/data.d.ts +6 -6
  293. package/dist/observer/data.js +7 -6
  294. package/dist/observer/exposure.d.ts +1 -1
  295. package/dist/observer/exposure.js +11 -11
  296. package/dist/observer/http.js +1 -1
  297. package/dist/observer/library.d.ts +1 -1
  298. package/dist/observer/library.js +4 -2
  299. package/dist/observer/listen.js +5 -4
  300. package/dist/observer/render.d.ts +1 -1
  301. package/dist/observer/render.js +12 -7
  302. package/dist/observer/run-routes.d.ts +4 -4
  303. package/dist/observer/run-routes.js +6 -6
  304. package/dist/observer/serve.d.ts +17 -0
  305. package/dist/observer/serve.js +41 -6
  306. package/dist/observer/tunnel.js +1 -1
  307. package/dist/observer-app.html +4 -4
  308. package/dist/routes/computer-use/bundle-parts.d.ts +9 -9
  309. package/dist/routes/computer-use/bundle-parts.js +24 -17
  310. package/dist/routes/computer-use/bundle.d.ts +4 -11
  311. package/dist/routes/computer-use/bundle.js +12 -18
  312. package/dist/routes/computer-use/diagnostics.d.ts +1 -1
  313. package/dist/routes/computer-use/diagnostics.js +1 -1
  314. package/dist/routes/computer-use/e2b-desktop/comms.d.ts +11 -11
  315. package/dist/routes/computer-use/e2b-desktop/comms.js +24 -27
  316. package/dist/routes/computer-use/e2b-desktop/desktop.d.ts +2 -2
  317. package/dist/routes/computer-use/e2b-desktop/desktop.js +8 -11
  318. package/dist/routes/computer-use/e2b-desktop/fidelity.d.ts +16 -15
  319. package/dist/routes/computer-use/e2b-desktop/fidelity.js +20 -19
  320. package/dist/routes/computer-use/e2b-desktop/prepare.d.ts +3 -3
  321. package/dist/routes/computer-use/e2b-desktop/prepare.js +40 -43
  322. package/dist/routes/computer-use/e2b-desktop/start.d.ts +1 -1
  323. package/dist/routes/computer-use/e2b-desktop/start.js +32 -30
  324. package/dist/routes/computer-use/e2b-desktop/state.d.ts +5 -4
  325. package/dist/routes/computer-use/e2b-desktop/state.js +5 -3
  326. package/dist/routes/computer-use/e2b-desktop/teardown.d.ts +1 -1
  327. package/dist/routes/computer-use/e2b-desktop/teardown.js +33 -20
  328. package/dist/routes/computer-use/external-comms.d.ts +4 -4
  329. package/dist/routes/computer-use/external-comms.js +19 -43
  330. package/dist/routes/computer-use/fanout-bundle.d.ts +3 -3
  331. package/dist/routes/computer-use/fanout-bundle.js +38 -42
  332. package/dist/routes/computer-use/{fanout-lanes.js → fanout-records.js} +33 -28
  333. package/dist/routes/computer-use/in-process-desktop.d.ts +3 -10
  334. package/dist/routes/computer-use/in-process-desktop.js +3 -9
  335. package/dist/routes/computer-use/live-flush.d.ts +7 -5
  336. package/dist/routes/computer-use/live-flush.js +20 -28
  337. package/dist/routes/computer-use/{run-lanes.d.ts → live-phase.d.ts} +4 -2
  338. package/dist/routes/computer-use/{run-lanes.js → live-phase.js} +17 -16
  339. package/dist/routes/computer-use/local-tree-pack.d.ts +3 -3
  340. package/dist/routes/computer-use/local-tree-pack.js +4 -5
  341. package/dist/routes/computer-use/local-vm.d.ts +11 -8
  342. package/dist/routes/computer-use/local-vm.js +40 -67
  343. package/dist/routes/computer-use/participant-desktop.d.ts +8 -23
  344. package/dist/routes/computer-use/participant-desktop.js +5 -25
  345. package/dist/routes/computer-use/{lanes.d.ts → participant-execution.d.ts} +11 -11
  346. package/dist/routes/computer-use/{lanes.js → participant-execution.js} +39 -26
  347. package/dist/routes/computer-use/participant-facts.d.ts +7 -1
  348. package/dist/routes/computer-use/participant-facts.js +12 -1
  349. package/dist/routes/computer-use/participant-model.d.ts +25 -11
  350. package/dist/routes/computer-use/participant-model.js +76 -34
  351. package/dist/routes/computer-use/participant-prompt.d.ts +13 -13
  352. package/dist/routes/computer-use/participant-prompt.js +8 -8
  353. package/dist/routes/computer-use/participant-runs.d.ts +11 -11
  354. package/dist/routes/computer-use/participant-runs.js +18 -18
  355. package/dist/routes/computer-use/plan.d.ts +21 -29
  356. package/dist/routes/computer-use/plan.js +70 -78
  357. package/dist/routes/computer-use/preflight.d.ts +3 -5
  358. package/dist/routes/computer-use/preflight.js +40 -36
  359. package/dist/routes/computer-use/rerun-selection.js +11 -7
  360. package/dist/routes/computer-use/result.d.ts +1 -1
  361. package/dist/routes/computer-use/result.js +48 -38
  362. package/dist/routes/computer-use/route.d.ts +5 -5
  363. package/dist/routes/computer-use/route.js +36 -33
  364. package/dist/routes/computer-use/self-report.d.ts +8 -8
  365. package/dist/routes/computer-use/self-report.js +33 -33
  366. package/dist/routes/computer-use/setup.d.ts +9 -15
  367. package/dist/routes/computer-use/setup.js +106 -83
  368. package/dist/routes/computer-use/single-bundle.d.ts +13 -16
  369. package/dist/routes/computer-use/single-bundle.js +46 -53
  370. package/dist/routes/computer-use/subject-projection.d.ts +6 -8
  371. package/dist/routes/computer-use/subject-projection.js +24 -23
  372. package/dist/routes/computer-use/types.d.ts +195 -238
  373. package/dist/routes/computer-use/types.js +21 -13
  374. package/dist/routes/preview.d.ts +8 -2
  375. package/dist/routes/preview.js +8 -5
  376. package/dist/routes/scripted/bundle.d.ts +5 -8
  377. package/dist/routes/scripted/bundle.js +23 -27
  378. package/dist/routes/scripted/plan.d.ts +6 -8
  379. package/dist/routes/scripted/plan.js +15 -13
  380. package/dist/routes/scripted/result.d.ts +4 -1
  381. package/dist/routes/scripted/result.js +15 -11
  382. package/dist/routes/scripted/route.js +15 -17
  383. package/dist/routes/scripted/scenario.d.ts +1 -1
  384. package/dist/routes/scripted/scenario.js +3 -3
  385. package/dist/routes/scripted/setup.d.ts +3 -2
  386. package/dist/routes/scripted/setup.js +27 -26
  387. package/dist/routes/scripted/subject.d.ts +8 -3
  388. package/dist/routes/scripted/subject.js +10 -6
  389. package/dist/routes/scripted/surface-records.js +4 -4
  390. package/dist/routes/scripted/surface-sessions.d.ts +2 -2
  391. package/dist/routes/scripted/surface-sessions.js +4 -4
  392. package/dist/routes/scripted/types.d.ts +21 -40
  393. package/dist/routes/scripted/types.js +2 -3
  394. package/dist/routes/shared-world/{seat-records.js → bundle-records.js} +17 -16
  395. package/dist/routes/shared-world/bundle.d.ts +7 -7
  396. package/dist/routes/shared-world/bundle.js +60 -62
  397. package/dist/routes/shared-world/checkpoints.d.ts +5 -5
  398. package/dist/routes/shared-world/checkpoints.js +5 -5
  399. package/dist/routes/shared-world/comms.d.ts +8 -7
  400. package/dist/routes/shared-world/comms.js +38 -51
  401. package/dist/routes/shared-world/external-public.d.ts +6 -5
  402. package/dist/routes/shared-world/external-public.js +28 -25
  403. package/dist/routes/shared-world/handoff.d.ts +10 -9
  404. package/dist/routes/shared-world/handoff.js +89 -81
  405. package/dist/routes/shared-world/lobby-code.d.ts +7 -7
  406. package/dist/routes/shared-world/lobby-code.js +22 -22
  407. package/dist/routes/shared-world/{seats.d.ts → participant-specs.d.ts} +15 -12
  408. package/dist/routes/shared-world/{seats.js → participant-specs.js} +65 -63
  409. package/dist/routes/shared-world/plan.d.ts +2 -6
  410. package/dist/routes/shared-world/plan.js +8 -9
  411. package/dist/routes/shared-world/provenance.d.ts +4 -4
  412. package/dist/routes/shared-world/provenance.js +4 -4
  413. package/dist/routes/shared-world/provisioned.d.ts +3 -2
  414. package/dist/routes/shared-world/provisioned.js +55 -48
  415. package/dist/routes/shared-world/result.js +47 -36
  416. package/dist/routes/shared-world/route.js +54 -57
  417. package/dist/routes/shared-world/setup.d.ts +6 -5
  418. package/dist/routes/shared-world/setup.js +16 -18
  419. package/dist/routes/shared-world/types.d.ts +70 -141
  420. package/dist/routes/shared-world/types.js +5 -5
  421. package/dist/routes/terminal/adapter.d.ts +10 -10
  422. package/dist/routes/terminal/adapter.js +32 -129
  423. package/dist/routes/terminal/bundle.d.ts +10 -15
  424. package/dist/routes/terminal/bundle.js +43 -52
  425. package/dist/routes/terminal/credentials.d.ts +4 -4
  426. package/dist/routes/terminal/credentials.js +20 -20
  427. package/dist/routes/terminal/dry-run.d.ts +1 -1
  428. package/dist/routes/terminal/dry-run.js +13 -15
  429. package/dist/routes/terminal/encoding.d.ts +1 -1
  430. package/dist/routes/terminal/encoding.js +4 -4
  431. package/dist/routes/terminal/ledger.d.ts +8 -8
  432. package/dist/routes/terminal/ledger.js +22 -22
  433. package/dist/routes/terminal/live-finish.d.ts +1 -2
  434. package/dist/routes/terminal/live-finish.js +24 -23
  435. package/dist/routes/terminal/live-sandbox.d.ts +3 -2
  436. package/dist/routes/terminal/live-sandbox.js +31 -32
  437. package/dist/routes/terminal/participant-text.d.ts +23 -0
  438. package/dist/routes/terminal/participant-text.js +165 -0
  439. package/dist/routes/terminal/persona.js +1 -1
  440. package/dist/routes/terminal/plan.d.ts +2 -6
  441. package/dist/routes/terminal/plan.js +23 -19
  442. package/dist/routes/terminal/recorder.d.ts +6 -1
  443. package/dist/routes/terminal/recorder.js +12 -4
  444. package/dist/routes/terminal/result.js +12 -12
  445. package/dist/routes/terminal/route.js +30 -31
  446. package/dist/routes/terminal/runtime.d.ts +3 -2
  447. package/dist/routes/terminal/runtime.js +3 -3
  448. package/dist/routes/terminal/sandbox.d.ts +4 -4
  449. package/dist/routes/terminal/sandbox.js +10 -10
  450. package/dist/routes/terminal/session.js +22 -19
  451. package/dist/routes/terminal/token-usage.d.ts +1 -1
  452. package/dist/routes/terminal/token-usage.js +10 -11
  453. package/dist/routes/terminal/trace.d.ts +7 -10
  454. package/dist/routes/terminal/trace.js +11 -66
  455. package/dist/routes/terminal/types.d.ts +104 -114
  456. package/dist/routes/terminal/types.js +12 -7
  457. package/dist/run/active-runs.d.ts +13 -0
  458. package/dist/run/active-runs.js +12 -0
  459. package/dist/run/bundle-shape.d.ts +2 -1
  460. package/dist/run/bundle-shape.js +7 -7
  461. package/dist/run/bundle.d.ts +99 -92
  462. package/dist/run/bundle.js +10 -8
  463. package/dist/run/cost-summary.js +6 -5
  464. package/dist/run/costs.d.ts +6 -6
  465. package/dist/run/costs.js +17 -11
  466. package/dist/run/detail.d.ts +9 -9
  467. package/dist/run/detail.js +10 -10
  468. package/dist/run/dry-run-inputs.js +1 -1
  469. package/dist/run/dry-run.js +23 -30
  470. package/dist/run/{study-files.d.ts → evidence-files.d.ts} +4 -4
  471. package/dist/run/{study-files.js → evidence-files.js} +5 -5
  472. package/dist/run/feedback-shape.js +1 -1
  473. package/dist/run/judge.d.ts +34 -16
  474. package/dist/run/judge.js +41 -14
  475. package/dist/run/locate.d.ts +2 -0
  476. package/dist/run/locate.js +3 -1
  477. package/dist/run/outcomes.d.ts +3 -3
  478. package/dist/run/outcomes.js +3 -3
  479. package/dist/run/participant-records.d.ts +2 -2
  480. package/dist/run/participant-records.js +2 -2
  481. package/dist/run/paths.d.ts +1 -1
  482. package/dist/run/paths.js +2 -2
  483. package/dist/run/pricing.d.ts +30 -15
  484. package/dist/run/pricing.js +49 -30
  485. package/dist/run/projection.d.ts +27 -27
  486. package/dist/run/projection.js +23 -23
  487. package/dist/run/reclaim.d.ts +6 -0
  488. package/dist/run/reclaim.js +15 -5
  489. package/dist/run/results.d.ts +5 -5
  490. package/dist/run/run-index.d.ts +9 -7
  491. package/dist/run/run-index.js +24 -25
  492. package/dist/run/run-not-found.d.ts +5 -0
  493. package/dist/run/run-not-found.js +15 -0
  494. package/dist/run/run.d.ts +7 -4
  495. package/dist/run/run.js +10 -6
  496. package/dist/run/sandbox-receipts.d.ts +5 -5
  497. package/dist/run/sandbox-receipts.js +6 -6
  498. package/dist/run/shared-world-evidence.d.ts +67 -66
  499. package/dist/run/shared-world-evidence.js +4 -4
  500. package/dist/run/shared-world-shape.d.ts +3 -3
  501. package/dist/run/shared-world-shape.js +16 -16
  502. package/dist/run/stats.d.ts +17 -11
  503. package/dist/run/stats.js +124 -66
  504. package/dist/run/status.d.ts +21 -32
  505. package/dist/run/status.js +34 -33
  506. package/dist/run/stored-runs.js +2 -1
  507. package/dist/run/stream-shape.js +1 -1
  508. package/dist/run/streams.d.ts +29 -22
  509. package/dist/run/study-provenance.d.ts +47 -0
  510. package/dist/run/study-provenance.js +63 -0
  511. package/dist/run/study-result.d.ts +16 -0
  512. package/dist/run/study-result.js +9 -0
  513. package/dist/run/terminal-contract.d.ts +5 -5
  514. package/dist/run/terminal-contract.js +4 -4
  515. package/dist/run/text.d.ts +2 -0
  516. package/dist/run/text.js +4 -0
  517. package/dist/run/transient-comms-secrets.d.ts +5 -0
  518. package/dist/run/transient-comms-secrets.js +12 -0
  519. package/dist/run-lab.d.ts +40 -38
  520. package/dist/run-lab.js +42 -40
  521. package/dist/study/convert.d.ts +33 -0
  522. package/dist/study/convert.js +432 -0
  523. package/dist/study/field-paths.d.ts +12 -0
  524. package/dist/study/field-paths.js +126 -0
  525. package/dist/study/files.d.ts +26 -0
  526. package/dist/study/files.js +43 -0
  527. package/dist/study/migrate.d.ts +48 -0
  528. package/dist/study/migrate.js +408 -0
  529. package/dist/study/owned-files.d.ts +38 -0
  530. package/dist/study/owned-files.js +182 -0
  531. package/dist/subject/clone.d.ts +1 -1
  532. package/dist/subject/desktop-cli.d.ts +2 -2
  533. package/dist/subject/desktop-cli.js +3 -3
  534. package/dist/subject/local-tree-archive.d.ts +1 -1
  535. package/dist/subject/local-tree-archive.js +5 -5
  536. package/dist/subject/local-tree.d.ts +3 -3
  537. package/dist/subject/local-tree.js +1 -1
  538. package/dist/subject/node-bootstrap.d.ts +1 -1
  539. package/dist/subject/node-bootstrap.js +4 -4
  540. package/dist/subject/runtime.js +10 -11
  541. package/dist/subject/serve.d.ts +1 -1
  542. package/dist/subject/serve.js +4 -4
  543. package/dist/subject/state.d.ts +1 -1
  544. package/dist/subject/steps.d.ts +8 -8
  545. package/dist/subject/steps.js +7 -7
  546. package/dist/substrates/command-failure.d.ts +3 -3
  547. package/dist/substrates/command-failure.js +3 -3
  548. package/dist/substrates/detached.d.ts +6 -6
  549. package/dist/substrates/detached.js +7 -7
  550. package/dist/substrates/e2b/cdp-probe.d.ts +11 -11
  551. package/dist/substrates/e2b/cdp-probe.js +15 -18
  552. package/dist/substrates/e2b/desktop-browser.d.ts +5 -5
  553. package/dist/substrates/e2b/desktop-browser.js +12 -12
  554. package/dist/substrates/e2b/desktop-cdp.d.ts +15 -15
  555. package/dist/substrates/e2b/desktop-cdp.js +11 -11
  556. package/dist/substrates/e2b/desktop-executor.d.ts +1 -1
  557. package/dist/substrates/e2b/desktop-executor.js +1 -1
  558. package/dist/substrates/e2b/desktop-geometry.d.ts +6 -6
  559. package/dist/substrates/e2b/desktop-geometry.js +9 -9
  560. package/dist/substrates/e2b/desktop-media.d.ts +1 -1
  561. package/dist/substrates/e2b/desktop-media.js +3 -3
  562. package/dist/substrates/e2b/desktop-screenshot-cleanup.d.ts +3 -3
  563. package/dist/substrates/e2b/desktop-screenshot-cleanup.js +3 -3
  564. package/dist/substrates/e2b/sandbox.d.ts +1 -1
  565. package/dist/substrates/e2b/sandbox.js +2 -2
  566. package/dist/substrates/e2b/sdk.d.ts +11 -11
  567. package/dist/substrates/e2b/sdk.js +8 -8
  568. package/dist/substrates/local/runtime-host.d.ts +2 -0
  569. package/dist/substrates/local/runtime-host.js +6 -0
  570. package/dist/tui/actions.d.ts +8 -6
  571. package/dist/tui/actions.js +31 -22
  572. package/dist/tui/contract.d.ts +16 -16
  573. package/dist/tui/contract.js +10 -10
  574. package/dist/tui/launch.d.ts +4 -4
  575. package/dist/tui/launch.js +18 -19
  576. package/dist/tui/project.js +5 -5
  577. package/dist/tui-app.js +83 -83
  578. package/dist/verify/actor.d.ts +14 -14
  579. package/dist/verify/actor.js +45 -45
  580. package/dist/verify/artifacts.d.ts +3 -3
  581. package/dist/verify/artifacts.js +6 -6
  582. package/dist/verify/context-growth.d.ts +9 -0
  583. package/dist/verify/context-growth.js +62 -0
  584. package/dist/verify/costs.d.ts +7 -7
  585. package/dist/verify/costs.js +9 -9
  586. package/dist/verify/liveness.d.ts +1 -1
  587. package/dist/verify/liveness.js +4 -1
  588. package/dist/verify/rerun.d.ts +1 -1
  589. package/dist/verify/rerun.js +1 -1
  590. package/dist/verify/shared-world-concurrent.d.ts +2 -2
  591. package/dist/verify/shared-world-concurrent.js +79 -76
  592. package/dist/verify/shared-world.d.ts +2 -2
  593. package/dist/verify/shared-world.js +18 -18
  594. package/dist/verify/subject.d.ts +4 -4
  595. package/dist/verify/subject.js +18 -18
  596. package/dist/verify/verify.js +98 -49
  597. package/docs/README.md +26 -33
  598. package/docs/architecture/actor-contract.md +105 -98
  599. package/docs/architecture/desktop-sessions.md +14 -14
  600. package/docs/architecture/external-public-shared-world.md +66 -59
  601. package/docs/architecture/github-feedback-loop.md +1 -4
  602. package/docs/architecture/guest-desktop.md +2 -2
  603. package/docs/architecture/observer.md +37 -37
  604. package/docs/architecture/restricted-codex-analysis.md +70 -30
  605. package/docs/architecture/serve.md +34 -37
  606. package/docs/architecture/state-driven-executor.md +55 -59
  607. package/docs/architecture/task-protocol-support.md +7 -9
  608. package/docs/architecture/{terminal-product-lane.md → terminal-product-route.md} +83 -115
  609. package/docs/contracts/README.md +24 -0
  610. package/docs/contracts/adapter-admission.md +3 -3
  611. package/docs/contracts/adapter-fixtures.md +2 -5
  612. package/docs/contracts/core.md +1 -3
  613. package/docs/contracts/errors.md +62 -0
  614. package/docs/contracts/feedback.md +8 -9
  615. package/docs/contracts/policy.md +7 -12
  616. package/docs/contracts/run-bundle.md +54 -51
  617. package/docs/contracts/schemas.md +505 -390
  618. package/docs/contracts/study-analysis.md +26 -2
  619. package/docs/contracts/study-costs.md +11 -7
  620. package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +1 -1
  621. package/docs/decisions/0002-observer-is-one-self-contained-file.md +2 -2
  622. package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +3 -3
  623. package/docs/decisions/0004-participant-desktops-stay-in-the-route.md +2 -1
  624. package/docs/principles/actor-fidelity.md +4 -4
  625. package/docs/principles/engineering.md +3 -3
  626. package/docs/principles/invariants-and-defaults.md +38 -38
  627. package/docs/principles/self-driving-harness.md +1 -5
  628. package/docs/principles/three-roles.md +1 -1
  629. package/docs/product/automatic-analysis.md +8 -7
  630. package/examples/participant/README.md +5 -5
  631. package/examples/participant/run.mjs +11 -10
  632. package/examples/scorer/README.md +3 -3
  633. package/examples/scorer/run.mjs +11 -12
  634. package/package.json +16 -17
  635. package/skills/humanish/SKILL.md +63 -60
  636. package/skills/humanish/agents/openai.yaml +1 -1
  637. package/AGENTS.md +0 -99
  638. package/CONTRIBUTING.md +0 -148
  639. package/dist/actors/codex/qualified-versions.d.ts +0 -45
  640. package/dist/actors/codex/qualified-versions.js +0 -87
  641. package/dist/cli/commands/lab.d.ts +0 -3
  642. package/dist/cli/commands/lab.js +0 -193
  643. package/dist/deprecated.d.ts +0 -90
  644. package/dist/deprecated.js +0 -106
  645. package/dist/lab/bag-overrides.d.ts +0 -18
  646. package/dist/lab/bag-overrides.js +0 -121
  647. package/dist/observer/static.d.ts +0 -39
  648. package/dist/observer/static.js +0 -369
  649. package/dist/routes/computer-use/legacy-lane-spec.d.ts +0 -62
  650. package/dist/routes/computer-use/legacy-lane-spec.js +0 -39
  651. package/docs/architecture/comms-inbox.md +0 -122
  652. package/docs/architecture/desktop-recording.md +0 -80
  653. package/docs/architecture/local-browser-runtime.md +0 -178
  654. package/docs/architecture/observer-review.md +0 -144
  655. package/docs/architecture/participant-media.md +0 -90
  656. package/docs/architecture/project-layout.md +0 -62
  657. package/docs/architecture/real-email-receiving.md +0 -136
  658. package/docs/assets/humanish-drawdb-hero.png +0 -0
  659. package/docs/assets/humanish-observer-hero.png +0 -0
  660. package/docs/goals/current.md +0 -207
  661. package/docs/product/lobby-trivia-3player-external-public.md +0 -77
  662. package/docs/product/open-source-install-experience.md +0 -240
  663. package/docs/ramp/README.md +0 -294
  664. package/docs/release/open-source-readiness.md +0 -222
  665. package/docs/release/public-readiness-standard.md +0 -210
  666. package/docs/roadmap/world-class-open-source-v0.md +0 -311
  667. /package/dist/routes/computer-use/{fanout-lanes.d.ts → fanout-records.d.ts} +0 -0
  668. /package/dist/routes/shared-world/{seat-records.d.ts → bundle-records.d.ts} +0 -0
@@ -4,6 +4,7 @@ import { constants } from "node:fs";
4
4
  import { lstat, mkdir, open, readdir, realpath, rename, unlink } from "node:fs/promises";
5
5
  import { homedir, hostname } from "node:os";
6
6
  import path from "node:path";
7
+ import { isRecord } from "../run/type-guards.js";
7
8
  import { isReceivingProviderId } from "./providers.js";
8
9
  const SCHEMA = "humanish.comms-lease-journal.v1";
9
10
  const MAX_BYTES = 1_048_576;
@@ -30,7 +31,6 @@ function fail(code = "authority_unavailable") {
30
31
  }
31
32
  const digest = (value) => createHash("sha256").update(value).digest("hex");
32
33
  const clone = (value) => structuredClone(value);
33
- const plain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
34
34
  const safeString = (value, max = 512) => typeof value === "string" &&
35
35
  value.length > 0 &&
36
36
  value.length <= max &&
@@ -45,14 +45,14 @@ export function sameReceivingIdentity(a, b) {
45
45
  a.scopeId === b.scopeId);
46
46
  }
47
47
  export function validReceivingIdentity(value) {
48
- return (plain(value) &&
48
+ return (isRecord(value) &&
49
49
  isReceivingProviderId(value.provider) &&
50
50
  safeString(value.accountId) &&
51
51
  ["organization", "pod", "inbox"].includes(String(value.scopeType)) &&
52
52
  safeString(value.scopeId));
53
53
  }
54
54
  export function validReceivingLease(value, clientId) {
55
- return (plain(value) &&
55
+ return (isRecord(value) &&
56
56
  value.clientId === clientId &&
57
57
  safeString(value.resourceId) &&
58
58
  safeString(value.address, 320) &&
@@ -79,7 +79,7 @@ function ownerLiveness(owner) {
79
79
  }
80
80
  }
81
81
  function validateOwner(value) {
82
- return (plain(value) &&
82
+ return (isRecord(value) &&
83
83
  Number.isSafeInteger(value.pid) &&
84
84
  value.pid > 0 &&
85
85
  safeString(value.host) &&
@@ -95,9 +95,9 @@ function parseJournal(text) {
95
95
  catch {
96
96
  return fail();
97
97
  }
98
- if (!plain(value) ||
98
+ if (!isRecord(value) ||
99
99
  value.schema !== SCHEMA ||
100
- !plain(value.project) ||
100
+ !isRecord(value.project) ||
101
101
  !safeString(value.project.path, 4096) ||
102
102
  !path.isAbsolute(value.project.path) ||
103
103
  !Number.isSafeInteger(value.project.dev) ||
@@ -123,7 +123,7 @@ function parseJournal(text) {
123
123
  const leaseIds = new Set();
124
124
  const resources = new Set();
125
125
  for (const item of value.leases) {
126
- if (!plain(item) ||
126
+ if (!isRecord(item) ||
127
127
  typeof item.participantId !== "string" ||
128
128
  !LOCAL_ID.test(item.participantId) ||
129
129
  participants.has(item.participantId) ||
@@ -7,7 +7,7 @@ export declare const MAX_RECEIVING_MESSAGES = 100;
7
7
  export declare const MAX_SURFACE_FILES: number;
8
8
  /** A local message or lease id: safe as one path segment. */
9
9
  export declare const LOCAL_ID: RegExp;
10
- /** Totals describe the whole snapshot (not increments). Register secrets BEFORE publishing files. */
10
+ /** Totals describe the whole snapshot (not increments). Register secrets before publishing files. */
11
11
  export declare function renderReceivingInbox(options: {
12
12
  address: string;
13
13
  messages: ParticipantEmail[];
@@ -91,7 +91,7 @@ function safeStyle(style) {
91
91
  }
92
92
  return declarations.join(";");
93
93
  }
94
- /** Totals describe the whole snapshot (not increments). Register secrets BEFORE publishing files. */
94
+ /** Totals describe the whole snapshot (not increments). Register secrets before publishing files. */
95
95
  export function renderReceivingInbox(options) {
96
96
  if (!options.address ||
97
97
  options.address.length > 512 ||
@@ -358,7 +358,7 @@ function renderImage(m, attrs) {
358
358
  const alt = attrs.get("alt")?.slice(0, 1024) || "Email image";
359
359
  if (!data) {
360
360
  m.assets++;
361
- return `<span class="blocked-image" role="img" aria-label="${esc(alt)}">${esc(alt)} — Image unavailable. Remote images are blocked; embedded images must be supported and within the size limit.</span>`;
361
+ return `<span class="blocked-image" role="img" aria-label="${esc(alt)}">${esc(alt)}: image unavailable. Remote images are blocked, and an embedded image must use a supported format and fit the size limit.</span>`;
362
362
  }
363
363
  // srcset, background and all network-bearing image attributes are deliberately absent.
364
364
  const width = /^\d{1,3}$/.test(attrs.get("width") ?? "") ? ` width="${attrs.get("width")}"` : "";
@@ -1,15 +1,15 @@
1
1
  /**
2
- * The self-contained in-sandbox capture server — a plain **python3** script (stdlib only), because the
3
- * stock E2B desktop template ships python3 but NOT node, and the co-located catcher must run in a
2
+ * The self-contained in-sandbox capture server: a plain **python3** script (stdlib only), because the
3
+ * stock E2B desktop template ships python3 but not node, and the co-located catcher must run in a
4
4
  * runtime the sandbox guarantees (the precedented choice: LocalStack is a python catcher the app points
5
5
  * at; you pick the runtime the environment has). It runs on the sandbox's own python3, imports nothing
6
- * from humanish. DELIBERATELY dumb: it records each POST verbatim as an NDJSON line `{t, path, body}`
7
- * and returns a plausible provider success — all normalization/profile parsing happens host-side on the
6
+ * from humanish. Deliberately dumb: it records each POST verbatim as an NDJSON line `{t, path, body}`
7
+ * and returns a plausible provider success; all normalization/profile parsing happens host-side on the
8
8
  * drained lines, so the typed, tested profiles stay in one place. It also serves the host-rendered inbox
9
9
  * surface statically at /inbox + /api/inbox (with a script-forbidding CSP). argv: <port> <deliveriesFile>
10
- * <servedDir> [inboxPort]. The capture listener binds 127.0.0.1 (loopback) — the app under test reaches
10
+ * <servedDir> [inboxPort]. The capture listener binds 127.0.0.1 (loopback): the app under test reaches
11
11
  * it in-sandbox, nothing on the internet can inject a fake send. When an [inboxPort] is given (the
12
- * shared-world route, where the persona lives in a DIFFERENT sandbox), it ALSO starts a READ-ONLY inbox
12
+ * shared-world route, where the persona lives in a different sandbox), it also starts a read-only inbox
13
13
  * listener on 0.0.0.0:<inboxPort> so getHost can proxy the persona's inbox reads to it; that listener
14
14
  * serves GET only (POST → 405). The CUA same-sandbox route omits it and stays loopback-only.
15
15
  */
@@ -2,17 +2,17 @@
2
2
  // directory) and runs with python3. sandbox-catch.ts deploys and drains it; this module holds only
3
3
  // its source, so the Python can be read on its own.
4
4
  /**
5
- * The self-contained in-sandbox capture server — a plain **python3** script (stdlib only), because the
6
- * stock E2B desktop template ships python3 but NOT node, and the co-located catcher must run in a
5
+ * The self-contained in-sandbox capture server: a plain **python3** script (stdlib only), because the
6
+ * stock E2B desktop template ships python3 but not node, and the co-located catcher must run in a
7
7
  * runtime the sandbox guarantees (the precedented choice: LocalStack is a python catcher the app points
8
8
  * at; you pick the runtime the environment has). It runs on the sandbox's own python3, imports nothing
9
- * from humanish. DELIBERATELY dumb: it records each POST verbatim as an NDJSON line `{t, path, body}`
10
- * and returns a plausible provider success — all normalization/profile parsing happens host-side on the
9
+ * from humanish. Deliberately dumb: it records each POST verbatim as an NDJSON line `{t, path, body}`
10
+ * and returns a plausible provider success; all normalization/profile parsing happens host-side on the
11
11
  * drained lines, so the typed, tested profiles stay in one place. It also serves the host-rendered inbox
12
12
  * surface statically at /inbox + /api/inbox (with a script-forbidding CSP). argv: <port> <deliveriesFile>
13
- * <servedDir> [inboxPort]. The capture listener binds 127.0.0.1 (loopback) — the app under test reaches
13
+ * <servedDir> [inboxPort]. The capture listener binds 127.0.0.1 (loopback): the app under test reaches
14
14
  * it in-sandbox, nothing on the internet can inject a fake send. When an [inboxPort] is given (the
15
- * shared-world route, where the persona lives in a DIFFERENT sandbox), it ALSO starts a READ-ONLY inbox
15
+ * shared-world route, where the persona lives in a different sandbox), it also starts a read-only inbox
16
16
  * listener on 0.0.0.0:<inboxPort> so getHost can proxy the persona's inbox reads to it; that listener
17
17
  * serves GET only (POST → 405). The CUA same-sandbox route omits it and stays loopback-only.
18
18
  */
@@ -11,8 +11,8 @@ export declare const DEFAULT_SANDBOX_CATCH_PORT = 8025;
11
11
  export interface DeployCommsCatchOptions {
12
12
  /** Fixed loopback port the catch listens on (default 8025). Must be free inside the sandbox. */
13
13
  port?: number;
14
- /** Optional SECOND fixed port for a READ-ONLY inbox listener bound to 0.0.0.0, so a persona in a
15
- * DIFFERENT sandbox can reach the inbox surface via getHost (the shared-world route). Omit on the
14
+ /** Optional second fixed port for a read-only inbox listener bound to 0.0.0.0, so a persona in a
15
+ * different sandbox can reach the inbox surface via getHost (the shared-world route). Omit on the
16
16
  * CUA same-sandbox route (loopback is enough). Must differ from `port` and be free in the sandbox. */
17
17
  inboxPort?: number;
18
18
  /** In-sandbox working dir for the script + NDJSON (default /tmp/humanish-comms). */
@@ -30,20 +30,20 @@ export interface DeployCommsCatchOptions {
30
30
  }
31
31
  export interface DeployedCommsCatch {
32
32
  port: number;
33
- /** Inject THIS as the app's email-API base URL (e.g. RESEND_API_URL) — the sandbox's own loopback. */
33
+ /** Inject this as the app's email-API base URL (e.g. RESEND_API_URL): the sandbox's own loopback. */
34
34
  baseUrl: string;
35
35
  deliveriesPath: string;
36
- /** In-sandbox dir the HOST renders the persona-facing inbox-surface files into (via writeInboxSurface);
36
+ /** In-sandbox dir the host renders the persona-facing inbox-surface files into (via writeInboxSurface);
37
37
  * the catch serves them at /inbox and /api/inbox. */
38
38
  surfaceDir: string;
39
- /** The 0.0.0.0 read-only inbox port, when one was requested — getHost-expose THIS to give a
39
+ /** The 0.0.0.0 read-only inbox port, when one was requested; getHost-expose this to give a
40
40
  * different-sandbox persona a reachable inbox URL. Absent on the loopback-only (CUA) route. */
41
41
  inboxPort?: number;
42
42
  /** The loopback SMTP port, when one was requested. Point the app's SMTP host/port env at
43
- * 127.0.0.1 and THIS. */
43
+ * 127.0.0.1 and this port. */
44
44
  smtpPort?: number;
45
- /** Whether the catch's /health returned OUR service marker within the readiness budget. Callers MUST
46
- * treat `ready === false` as fatal (do not inject baseUrl into a dead catch — the app's sends would
45
+ /** Whether the catch's /health returned humanish's service marker within the readiness budget. Callers must
46
+ * treat `ready === false` as fatal (do not inject baseUrl into a dead catch: the app's sends would
47
47
  * silently fail with nothing captured). */
48
48
  ready: boolean;
49
49
  }
@@ -54,8 +54,8 @@ export interface RawCapturedSend {
54
54
  t: number;
55
55
  }
56
56
  /**
57
- * Write + launch the in-sandbox catch (detached), then probe it ready. Call AFTER the subject sandbox
58
- * is created and BEFORE the subject app's serve.start, so the base URL resolves at the app's boot.
57
+ * Write + launch the in-sandbox catch (detached), then probe it ready. Call after the subject sandbox
58
+ * is created and before the subject app's serve.start, so the base URL resolves at the app's boot.
59
59
  */
60
60
  export declare function deployCommsCatch(shell: Shell, options?: DeployCommsCatchOptions): Promise<DeployedCommsCatch>;
61
61
  /**
@@ -67,26 +67,25 @@ export declare function drainCommsCatch(shell: Shell, deployed: Pick<DeployedCom
67
67
  cursor: number;
68
68
  }>;
69
69
  /**
70
- * Parse an append-only deliveries NDJSON blob into raw sends. Split out of drainCommsCatch (#380) so
71
- * the SAME parsing serves a sandbox we own (read over the E2B command channel) and a catch running on
70
+ * Parse an append-only deliveries NDJSON blob into raw sends. Split out of drainCommsCatch so
71
+ * the same parsing serves a sandbox we own (read over the E2B command channel) and a catch running on
72
72
  * a plane we do not own (read from the local filesystem by `humanish comms catch`).
73
73
  *
74
- * A file that does not end in a newline may have a PARTIAL last line — a reader racing an append of a
74
+ * A file that does not end in a newline may have a partial last line when a reader races an append of a
75
75
  * large body. Dropping it is never lossy: the script only ever emits valid JSON lines, so an incomplete
76
76
  * line re-reads complete on the next pass.
77
77
  */
78
78
  export declare function parseDeliveriesNdjson(text: string): RawCapturedSend[];
79
79
  /**
80
- * The distinct `to` addresses the captured mail was actually sent to, parsed with the SAME profiles
80
+ * The distinct `to` addresses the captured mail was actually sent to, parsed with the same profiles
81
81
  * that route it. A lab run knows its recipients from the declared roster; a standalone catch does not,
82
- * so it discovers them from the mail itself — otherwise an operator who forgot to name an address gets
83
- * a technically-healthy catch rendering an empty inbox forever, which is the false-green class #380 is
84
- * about.
82
+ * so it discovers them from the mail itself. Otherwise an operator who forgot to name an address gets
83
+ * a technically-healthy catch rendering an empty inbox forever: a false green.
85
84
  */
86
85
  export declare function capturedRecipientAddresses(sends: readonly RawCapturedSend[], profiles?: EmailSendProfile[]): string[];
87
86
  /**
88
- * Route raw sends into a FRESH FakeInbox and return the deduped, delivery-ordered messages. Split out
89
- * of refreshInboxSurface (#380) so the rendering pipeline is shared by every transport; the freshness
87
+ * Route raw sends into a fresh FakeInbox and return the deduped, delivery-ordered messages. Split out
88
+ * of refreshInboxSurface so the rendering pipeline is shared by every transport; the freshness
90
89
  * is what makes a full rebuild idempotent (a send is never routed twice, so no duplicate emails).
91
90
  */
92
91
  export declare function inboxMessagesFrom(sends: readonly RawCapturedSend[], recipients: readonly InboxSurfaceRecipient[]): Promise<CommsMessage[]>;
@@ -98,7 +97,7 @@ export declare function inboxMessagesFrom(sends: readonly RawCapturedSend[], rec
98
97
  export declare function routeCapturedSends(sends: RawCapturedSend[], channel: CommsChannel, profiles?: EmailSendProfile[]): Promise<number>;
99
98
  /** Outcome of a whole-run comms collect. `artifact` is present only when captured mail matched a
100
99
  * provisioned inbox; `captured > 0 && artifact === undefined` means the app sent mail to an address
101
- * no declared recipient covers (captured but unevidenced) — the caller should surface that, not drop
100
+ * no declared recipient covers (captured but unevidenced); the caller should surface that, not drop
102
101
  * it silently. */
103
102
  export interface CommsThreadCollection {
104
103
  artifact?: CommsThreadArtifact;
@@ -110,8 +109,8 @@ export interface CommsThreadCollection {
110
109
  /**
111
110
  * End of a run's comms funnel: drain everything the in-sandbox catch captured, route it into the
112
111
  * host `channel`, poll the provisioned `inboxes`, and build the digest-only thread artifact. The
113
- * `artifact` is omitted when nothing was captured OR nothing matched a provisioned inbox (an empty
114
- * file would be a false claim of a delivered thread) — but `captured`/`matched` are always reported so
112
+ * `artifact` is omitted when nothing was captured or nothing matched a provisioned inbox (an empty
113
+ * file would be a false claim of a delivered thread), but `captured`/`matched` are always reported so
115
114
  * the caller can warn on captured-but-unevidenced mail rather than lose it silently. Composes the
116
115
  * tested drain/route/build pieces so the CUA and shared-world routes collect evidence identically. The
117
116
  * full NDJSON is drained from cursor 0 (a whole-run collect), so it is idempotent to call once at
@@ -126,9 +125,9 @@ export declare function collectCommsThread(args: {
126
125
  profiles?: EmailSendProfile[];
127
126
  requestTimeoutMs?: number;
128
127
  }): Promise<CommsThreadCollection>;
129
- /** An adopter-hosted catch: humanish never provisioned it, so it is addressed over HTTP (#328). */
128
+ /** An adopter-hosted catch: humanish never provisioned it, so it is addressed over HTTP. */
130
129
  export interface ExternalCommsCatch {
131
- /** Base URL of the catch the ADOPTER runs (its POST capture endpoint and GET /deliveries). */
130
+ /** Base URL of the catch the adopter runs (its POST capture endpoint and GET /deliveries). */
132
131
  catchBaseUrl: string;
133
132
  /** Base URL the persona opens to read mail. Defaults to catchBaseUrl (same server serves /inbox). */
134
133
  inboxBaseUrl?: string;
@@ -138,8 +137,8 @@ export interface ExternalCommsCatch {
138
137
  /** The URL a persona is told to open to read its mail on an adopter-hosted plane. */
139
138
  export declare function externalInboxUrl(external: ExternalCommsCatch): string;
140
139
  /**
141
- * Probe an adopter-hosted catch the way the in-sandbox one is probed: assert OUR service marker in
142
- * /health, not merely any 2xx — an adopter's reverse proxy or a captive portal will happily return
140
+ * Probe an adopter-hosted catch the way the in-sandbox one is probed: assert humanish's service marker in
141
+ * /health, not merely any 2xx: an adopter's reverse proxy or a captive portal will happily return
143
142
  * 200 for anything, and a comms lab whose catch is not actually there collects nothing while
144
143
  * looking fine. Fail-closed callers treat `false` as a hard stop before spending on a run.
145
144
  */
@@ -161,8 +160,8 @@ export declare function drainExternalCommsCatch(external: ExternalCommsCatch, cu
161
160
  }>;
162
161
  /**
163
162
  * The adopter-hosted analogue of collectCommsThread: drain over HTTP, route into the host inbox bus,
164
- * and build the SAME digest-only humanish.comms-thread.v1 artifact. Evidence shape does not depend
165
- * on who hosted the catch — only the transport does.
163
+ * and build the same digest-only humanish.comms-thread.v1 artifact. Evidence shape does not depend
164
+ * on who hosted the catch; only the transport does.
166
165
  */
167
166
  export declare function collectExternalCommsThread(args: {
168
167
  external: ExternalCommsCatch;
@@ -173,10 +172,10 @@ export declare function collectExternalCommsThread(args: {
173
172
  fetchFn?: typeof fetch;
174
173
  }): Promise<CommsThreadCollection>;
175
174
  /**
176
- * Render the persona-facing inbox surface (host-side, typed — see capture-surface.ts) and write the files
177
- * into the sandbox's served dir, so the catch serves a LIVE inbox the persona opens and clicks. Creates
175
+ * Render the persona-facing inbox surface (host-side, typed; see capture-surface.ts) and write the files
176
+ * into the sandbox's served dir, so the catch serves a live inbox the persona opens and clicks. Creates
178
177
  * the nested route dirs first; overwrites idempotently, so call it whenever the message set changes
179
- * (e.g. after a mid-run drain). Returns the number of files written. Raw content is written INTO the
178
+ * (e.g. after a mid-run drain). Returns the number of files written. Raw content is written into the
180
179
  * sandbox only (runtime-only, served to the in-sandbox browser); nothing here persists to the bundle.
181
180
  */
182
181
  export declare function writeInboxSurface(shell: Shell, surfaceDir: string, messages: CommsMessage[], options?: InboxRenderOptions & {
@@ -189,17 +188,17 @@ export interface InboxSurfaceRecipient {
189
188
  address: string;
190
189
  }
191
190
  /**
192
- * One mid-run inbox-surface refresh cycle: FULL rebuild from the append-only NDJSON (drain from cursor 0)
193
- * into a FRESH FakeInbox each call, provisioning the declared `recipients`, then (re)render the surface
191
+ * One mid-run inbox-surface refresh cycle: full rebuild from the append-only NDJSON (drain from cursor 0)
192
+ * into a fresh FakeInbox each call, provisioning the declared `recipients`, then (re)render the surface
194
193
  * so the persona sees new mail while the session is live. Returns the total captured-send `count` + whether
195
194
  * it rendered.
196
195
  *
197
- * The full rebuild is deliberate — it is IDEMPOTENT and RETRY-SAFE: a transient writeInboxSurface failure
198
- * PROPAGATES (the caller retries next tick without advancing its `sinceCount`), and because each rebuild
196
+ * The full rebuild is deliberate because it is idempotent and retry-safe: a transient writeInboxSurface failure
197
+ * propagates (the caller retries next tick without advancing its `sinceCount`), and because each rebuild
199
198
  * starts from a clean channel, a send is never routed twice, so the persona never sees duplicate emails.
200
- * Pass `sinceCount` (the last SUCCESSFULLY-rendered send count) to skip the (N-file) render when nothing
199
+ * Pass `sinceCount` (the last successfully-rendered send count) to skip the (N-file) render when nothing
201
200
  * new has arrived. This is independent of the teardown collectCommsThread drain (its own fresh channel,
202
- * also cursor 0) — no evidence is lost or altered. The NDJSON is small for a run, so re-reading it is cheap.
201
+ * also cursor 0), so no evidence is lost or altered. The NDJSON is small for a run, so re-reading it is cheap.
203
202
  */
204
203
  export declare function refreshInboxSurface(args: {
205
204
  shell: Shell;
@@ -1,10 +1,10 @@
1
- // Deploy the vendor-neutral email catch INSIDE the subject E2B sandbox and bridge captured sends back
2
- // to the host bus (#297 config-block core). The host `startEmailCatchServer` binds the HOST's loopback,
3
- // which a sandboxed app cannot reach — `127.0.0.1:PORT` from the app is the SANDBOX's loopback. So the
1
+ // Deploy the vendor-neutral email catch inside the subject E2B sandbox and bridge captured sends back
2
+ // to the host bus. The host `startEmailCatchServer` binds the host's loopback,
3
+ // which a sandboxed app cannot reach: `127.0.0.1:PORT` from the app is the sandbox's loopback. So the
4
4
  // listener must live in the sandbox: we write a tiny self-contained capture server (no deps, no host
5
5
  // import) into the sandbox, launch it detached (the same substrate that serves the subject app), and
6
6
  // each poll `cat` its append-only NDJSON of captured sends back to the host, where the real profiles
7
- // parse them and route into the CommsChannel. A FIXED loopback port is chosen up front so the app's
7
+ // parse them and route into the CommsChannel. A fixed loopback port is chosen up front so the app's
8
8
  // injected base-URL env (`http://127.0.0.1:<port>`) is known before the sandbox is created.
9
9
  import { FakeInbox } from "./fake-inbox.js";
10
10
  import { buildCommsThreadArtifact } from "./thread-evidence.js";
@@ -18,7 +18,7 @@ import { runOrThrow, shellQuote } from "../substrates/shell.js";
18
18
  * unlikely to collide with a subject app; override via config if it does. */
19
19
  export const DEFAULT_SANDBOX_CATCH_PORT = 8025;
20
20
  const DEFAULT_CATCH_DIR = "/tmp/humanish-comms";
21
- /** Readiness probe that asserts OUR service marker in the /health body (not merely any 2xx) — so a
21
+ /** Readiness probe that asserts humanish's service marker in the /health body (a bare 2xx is not enough), so a
22
22
  * process squatting on the fixed port cannot produce a false "ready" while the app's sends bypass us. */
23
23
  async function catchHealthy(shell, port, options) {
24
24
  const now = options.now ?? Date.now;
@@ -38,8 +38,8 @@ async function catchHealthy(shell, port, options) {
38
38
  }
39
39
  }
40
40
  /**
41
- * Write + launch the in-sandbox catch (detached), then probe it ready. Call AFTER the subject sandbox
42
- * is created and BEFORE the subject app's serve.start, so the base URL resolves at the app's boot.
41
+ * Write + launch the in-sandbox catch (detached), then probe it ready. Call after the subject sandbox
42
+ * is created and before the subject app's serve.start, so the base URL resolves at the app's boot.
43
43
  */
44
44
  export async function deployCommsCatch(shell, options = {}) {
45
45
  // Validate the port to an integer before it reaches the shell command (defense-in-depth: a future
@@ -93,8 +93,8 @@ export async function deployCommsCatch(shell, options = {}) {
93
93
  ...options.timers,
94
94
  };
95
95
  const ready = await catchHealthy(shell, port, probe);
96
- // Confirm the read-only inbox listener bound too (loopback-reachable at its own port), when requested —
97
- // else a getHost-exposed inbox would 502. Fail closed by folding it into `ready`.
96
+ // Confirm the read-only inbox listener bound too (loopback-reachable at its own port), when requested.
97
+ // Without it a getHost-exposed inbox would 502. Fail closed by folding it into `ready`.
98
98
  const inboxReady = inboxPort === undefined ? true : await catchHealthy(shell, inboxPort, probe);
99
99
  return {
100
100
  port,
@@ -114,8 +114,8 @@ export async function drainCommsCatch(shell, deployed, cursor = 0, requestTimeou
114
114
  const result = await runOrThrow(shell, `cat ${shellQuote(deployed.deliveriesPath)} 2>/dev/null || true`, { requestTimeoutMs });
115
115
  const stdout = result.stdout;
116
116
  let lines = stdout.split("\n").filter((line) => line.trim().length > 0);
117
- // If the file doesn't end in a newline, the last line may be a PARTIAL append (the host `cat` raced
118
- // an in-sandbox append of a large body). Drop it and don't advance the cursor past it — it re-reads
117
+ // If the file doesn't end in a newline, the last line may be a partial append (the host `cat` raced
118
+ // an in-sandbox append of a large body). Drop it and don't advance the cursor past it; it re-reads
119
119
  // complete on the next poll, so a captured send is never lost to the race (the script only ever emits
120
120
  // valid JSON, so an incomplete line is the only cause of a parse miss).
121
121
  if (!stdout.endsWith("\n") && lines.length > 0)
@@ -139,11 +139,11 @@ export async function drainCommsCatch(shell, deployed, cursor = 0, requestTimeou
139
139
  return { sends, cursor: lines.length };
140
140
  }
141
141
  /**
142
- * Parse an append-only deliveries NDJSON blob into raw sends. Split out of drainCommsCatch (#380) so
143
- * the SAME parsing serves a sandbox we own (read over the E2B command channel) and a catch running on
142
+ * Parse an append-only deliveries NDJSON blob into raw sends. Split out of drainCommsCatch so
143
+ * the same parsing serves a sandbox we own (read over the E2B command channel) and a catch running on
144
144
  * a plane we do not own (read from the local filesystem by `humanish comms catch`).
145
145
  *
146
- * A file that does not end in a newline may have a PARTIAL last line — a reader racing an append of a
146
+ * A file that does not end in a newline may have a partial last line when a reader races an append of a
147
147
  * large body. Dropping it is never lossy: the script only ever emits valid JSON lines, so an incomplete
148
148
  * line re-reads complete on the next pass.
149
149
  */
@@ -170,11 +170,10 @@ export function parseDeliveriesNdjson(text) {
170
170
  return sends;
171
171
  }
172
172
  /**
173
- * The distinct `to` addresses the captured mail was actually sent to, parsed with the SAME profiles
173
+ * The distinct `to` addresses the captured mail was actually sent to, parsed with the same profiles
174
174
  * that route it. A lab run knows its recipients from the declared roster; a standalone catch does not,
175
- * so it discovers them from the mail itself — otherwise an operator who forgot to name an address gets
176
- * a technically-healthy catch rendering an empty inbox forever, which is the false-green class #380 is
177
- * about.
175
+ * so it discovers them from the mail itself. Otherwise an operator who forgot to name an address gets
176
+ * a technically-healthy catch rendering an empty inbox forever: a false green.
178
177
  */
179
178
  export function capturedRecipientAddresses(sends, profiles = DEFAULT_EMAIL_PROFILES) {
180
179
  const addresses = new Set();
@@ -199,8 +198,8 @@ export function capturedRecipientAddresses(sends, profiles = DEFAULT_EMAIL_PROFI
199
198
  return [...addresses];
200
199
  }
201
200
  /**
202
- * Route raw sends into a FRESH FakeInbox and return the deduped, delivery-ordered messages. Split out
203
- * of refreshInboxSurface (#380) so the rendering pipeline is shared by every transport; the freshness
201
+ * Route raw sends into a fresh FakeInbox and return the deduped, delivery-ordered messages. Split out
202
+ * of refreshInboxSurface so the rendering pipeline is shared by every transport; the freshness
204
203
  * is what makes a full rebuild idempotent (a send is never routed twice, so no duplicate emails).
205
204
  */
206
205
  export async function inboxMessagesFrom(sends, recipients) {
@@ -256,8 +255,8 @@ export async function routeCapturedSends(sends, channel, profiles = DEFAULT_EMAI
256
255
  /**
257
256
  * End of a run's comms funnel: drain everything the in-sandbox catch captured, route it into the
258
257
  * host `channel`, poll the provisioned `inboxes`, and build the digest-only thread artifact. The
259
- * `artifact` is omitted when nothing was captured OR nothing matched a provisioned inbox (an empty
260
- * file would be a false claim of a delivered thread) — but `captured`/`matched` are always reported so
258
+ * `artifact` is omitted when nothing was captured or nothing matched a provisioned inbox (an empty
259
+ * file would be a false claim of a delivered thread), but `captured`/`matched` are always reported so
261
260
  * the caller can warn on captured-but-unevidenced mail rather than lose it silently. Composes the
262
261
  * tested drain/route/build pieces so the CUA and shared-world routes collect evidence identically. The
263
262
  * full NDJSON is drained from cursor 0 (a whole-run collect), so it is idempotent to call once at
@@ -297,8 +296,8 @@ export function externalInboxUrl(external) {
297
296
  return `${baseOf(external.inboxBaseUrl ?? external.catchBaseUrl)}/inbox`;
298
297
  }
299
298
  /**
300
- * Probe an adopter-hosted catch the way the in-sandbox one is probed: assert OUR service marker in
301
- * /health, not merely any 2xx — an adopter's reverse proxy or a captive portal will happily return
299
+ * Probe an adopter-hosted catch the way the in-sandbox one is probed: assert humanish's service marker in
300
+ * /health, not merely any 2xx: an adopter's reverse proxy or a captive portal will happily return
302
301
  * 200 for anything, and a comms lab whose catch is not actually there collects nothing while
303
302
  * looking fine. Fail-closed callers treat `false` as a hard stop before spending on a run.
304
303
  */
@@ -365,8 +364,8 @@ export async function drainExternalCommsCatch(external, cursor = 0, options = {}
365
364
  }
366
365
  /**
367
366
  * The adopter-hosted analogue of collectCommsThread: drain over HTTP, route into the host inbox bus,
368
- * and build the SAME digest-only humanish.comms-thread.v1 artifact. Evidence shape does not depend
369
- * on who hosted the catch — only the transport does.
367
+ * and build the same digest-only humanish.comms-thread.v1 artifact. Evidence shape does not depend
368
+ * on who hosted the catch; only the transport does.
370
369
  */
371
370
  export async function collectExternalCommsThread(args) {
372
371
  const drainOptions = {
@@ -397,10 +396,10 @@ export async function collectExternalCommsThread(args) {
397
396
  };
398
397
  }
399
398
  /**
400
- * Render the persona-facing inbox surface (host-side, typed — see capture-surface.ts) and write the files
401
- * into the sandbox's served dir, so the catch serves a LIVE inbox the persona opens and clicks. Creates
399
+ * Render the persona-facing inbox surface (host-side, typed; see capture-surface.ts) and write the files
400
+ * into the sandbox's served dir, so the catch serves a live inbox the persona opens and clicks. Creates
402
401
  * the nested route dirs first; overwrites idempotently, so call it whenever the message set changes
403
- * (e.g. after a mid-run drain). Returns the number of files written. Raw content is written INTO the
402
+ * (e.g. after a mid-run drain). Returns the number of files written. Raw content is written into the
404
403
  * sandbox only (runtime-only, served to the in-sandbox browser); nothing here persists to the bundle.
405
404
  */
406
405
  export async function writeInboxSurface(shell, surfaceDir, messages, options = {}) {
@@ -421,17 +420,17 @@ export async function writeInboxSurface(shell, surfaceDir, messages, options = {
421
420
  return files.length;
422
421
  }
423
422
  /**
424
- * One mid-run inbox-surface refresh cycle: FULL rebuild from the append-only NDJSON (drain from cursor 0)
425
- * into a FRESH FakeInbox each call, provisioning the declared `recipients`, then (re)render the surface
423
+ * One mid-run inbox-surface refresh cycle: full rebuild from the append-only NDJSON (drain from cursor 0)
424
+ * into a fresh FakeInbox each call, provisioning the declared `recipients`, then (re)render the surface
426
425
  * so the persona sees new mail while the session is live. Returns the total captured-send `count` + whether
427
426
  * it rendered.
428
427
  *
429
- * The full rebuild is deliberate — it is IDEMPOTENT and RETRY-SAFE: a transient writeInboxSurface failure
430
- * PROPAGATES (the caller retries next tick without advancing its `sinceCount`), and because each rebuild
428
+ * The full rebuild is deliberate because it is idempotent and retry-safe: a transient writeInboxSurface failure
429
+ * propagates (the caller retries next tick without advancing its `sinceCount`), and because each rebuild
431
430
  * starts from a clean channel, a send is never routed twice, so the persona never sees duplicate emails.
432
- * Pass `sinceCount` (the last SUCCESSFULLY-rendered send count) to skip the (N-file) render when nothing
431
+ * Pass `sinceCount` (the last successfully-rendered send count) to skip the (N-file) render when nothing
433
432
  * new has arrived. This is independent of the teardown collectCommsThread drain (its own fresh channel,
434
- * also cursor 0) — no evidence is lost or altered. The NDJSON is small for a run, so re-reading it is cheap.
433
+ * also cursor 0), so no evidence is lost or altered. The NDJSON is small for a run, so re-reading it is cheap.
435
434
  */
436
435
  export async function refreshInboxSurface(args) {
437
436
  const { sends } = await drainCommsCatch(args.shell, args.deployed, 0, args.requestTimeoutMs);
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { constants } from "node:fs";
3
3
  import { lstat, open, unlink } from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { parse, stringify } from "yaml";
5
+ import { parse, parseDocument } from "yaml";
6
6
  import { COMMS_CONFIG_PATH, readCommsConnections } from "./connections.js";
7
7
  import { discoverProviderKeys, listUserKeys, probeKeySources, } from "../keys/key-resolution.js";
8
8
  import { COMMS_PROVIDERS, commsProviderLabel, DEFAULT_COMMS_PROVIDER, } from "./providers.js";
@@ -11,6 +11,9 @@ import { RECEIVING_SCOPE_UNSUPPORTED } from "./receiving-types.js";
11
11
  import { parseLabConfig } from "../lab/config.js";
12
12
  import { resolveLabManifest } from "../lab/discover.js";
13
13
  import { assertPreparedSelectedOutputDirectory, prepareManagedHumanishOutputDirectory, prepareSelectedOutputDirectory, readContainedRegularFile, writeContainedOutputFile, } from "../run/contained-output.js";
14
+ import { otherStudyFiles } from "../study/files.js";
15
+ import { convertStudyText, isStudyV3 } from "../study/convert.js";
16
+ import { isNodeError } from "../run/type-guards.js";
14
17
  export async function checkCommsConnection(args) {
15
18
  const base = {
16
19
  schema: "humanish.comms-check.v1",
@@ -122,7 +125,8 @@ export async function configureCommsLab(args) {
122
125
  const bytes = await readContainedRegularFile(root, rel);
123
126
  if (!bytes || bytes.length > 1024 * 1024)
124
127
  return { ...base, message: "The lab is missing or too large." };
125
- const raw = parse(bytes.toString("utf8"));
128
+ const text = bytes.toString("utf8");
129
+ const raw = parse(text);
126
130
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
127
131
  return { ...base, message: "The lab must be a mapping." };
128
132
  const lab = raw;
@@ -132,8 +136,20 @@ export async function configureCommsLab(args) {
132
136
  ...base,
133
137
  message: "This lab uses local capture. Preserve it or make a separate lab before selecting real email.",
134
138
  };
135
- lab.comms = { ...existing, email: { ...existing?.email, connection: args.connection } };
136
- const validated = parseLabConfig(lab);
139
+ // The copy is a v3 study. A v2 source is converted first, so the copy keeps its comments.
140
+ let studyText = text;
141
+ if (!isStudyV3(raw)) {
142
+ const converted = convertStudyText(text, args.cwd);
143
+ if (!converted.ok)
144
+ return {
145
+ ...base,
146
+ message: `This lab could not be converted to a v3 study: ${converted.reason}`,
147
+ };
148
+ studyText = converted.conversion.text;
149
+ }
150
+ const study = parseDocument(studyText);
151
+ study.setIn(["comms", "email", "connection"], args.connection);
152
+ const validated = parseLabConfig(study.toJS());
137
153
  if (!validated.ok)
138
154
  return {
139
155
  ...base,
@@ -145,12 +161,30 @@ export async function configureCommsLab(args) {
145
161
  .replace(/-receiving$/, "") + "-receiving.yaml";
146
162
  if (!/^[A-Za-z0-9_][A-Za-z0-9._-]{0,110}\.yaml$/.test(filename))
147
163
  return { ...base, message: "Use a simple lab filename before configuring email." };
148
- const destination = `.humanish/local/labs/${filename}`;
149
- if (rel === destination)
164
+ const destination = `.humanish/local/studies/${filename}`;
165
+ if (path.basename(rel) === filename)
150
166
  return {
151
167
  ...base,
152
168
  message: "This is already the local receiving copy. Select its original lab to configure a separate copy, or edit this manifest directly.",
153
169
  };
170
+ // A second file with this name in another study directory would make discovery refuse both.
171
+ const stem = filename.replace(/\.yaml$/, "");
172
+ const others = await otherStudyFiles(stem,
173
+ // Any directory entry counts, a dangling symbolic link included: discovery would see it too.
174
+ async (candidate) => {
175
+ try {
176
+ await lstat(path.join(root.requestedPath, candidate));
177
+ return true;
178
+ }
179
+ catch (error) {
180
+ return !(isNodeError(error) && error.code === "ENOENT");
181
+ }
182
+ }, destination);
183
+ if (others.length > 0)
184
+ return {
185
+ ...base,
186
+ message: `${others.join(", ")} already uses the name ${stem}. Rename or remove it before configuring email, so \`run ${stem}\` reads one file.`,
187
+ };
154
188
  let prior;
155
189
  try {
156
190
  prior = await readContainedRegularFile(root, destination);
@@ -158,7 +192,7 @@ export async function configureCommsLab(args) {
158
192
  catch {
159
193
  return { ...base, message: "The local destination could not be read safely." };
160
194
  }
161
- const content = stringify(lab);
195
+ const content = String(study);
162
196
  const token = createHash("sha256")
163
197
  .update(bytes)
164
198
  .update("\0")
@@ -183,7 +217,7 @@ export async function configureCommsLab(args) {
183
217
  ...base,
184
218
  message: "The lab or destination changed since preview. Preview again before saving.",
185
219
  };
186
- const directory = await prepareManagedHumanishOutputDirectory(args.cwd, "local", "labs");
220
+ const directory = await prepareManagedHumanishOutputDirectory(args.cwd, "local", "studies");
187
221
  const lockPath = path.join(directory.physicalPath, ".comms-configure.lock");
188
222
  const lock = await open(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
189
223
  const identity = await lock.stat();
@@ -11,7 +11,7 @@ interface CommsThreadEntry {
11
11
  subjectDigest?: string;
12
12
  /** sha256-16 of each actionable link (high-entropy → non-reversible). */
13
13
  linkDigests: string[];
14
- /** COUNT ONLY — a short OTP's digest is reversible, so the code itself never lands here. */
14
+ /** Count only: a short OTP's digest is reversible, so the code itself never lands here. */
15
15
  codeCount: number;
16
16
  sentAt: number;
17
17
  deliveredAt: number;
@@ -1,14 +1,14 @@
1
- // Digest-only evidence for a captured comms thread (#297). Proves "the verification mail arrived and
2
- // the persona could act on it" WITHOUT persisting raw PHI. Written as an adapter-artifact
1
+ // Digest-only evidence for a captured comms thread. Proves "the verification mail arrived and
2
+ // the persona could act on it" without persisting raw PHI. Written as an adapter-artifact
3
3
  // (humanish.comms-thread.v1), so it inherits the bundle's existence-verify + public-safety scan.
4
4
  //
5
5
  // Digest discipline (deliberate, see below): addresses + links are digested (high entropy → the digest
6
- // is not reversible). The subject is DIGESTED too, not stored as text — redactText only scrubs
7
- // secret-SHAPED tokens/paths, not free-form PII (the #108 gap), so a subject like "results for <name>"
6
+ // is not reversible). The subject is digested too instead of stored as text, because redactText only scrubs
7
+ // secret-shaped tokens/paths, not free-form PII, so a subject like "results for <name>"
8
8
  // would pass through verbatim; a sha256-16 keeps a PII subject non-reversible while still letting you
9
- // correlate identical subjects. OTP CODES are a COUNT ONLY, never digested — a sha256 of a 6-digit code
9
+ // correlate identical subjects. OTP codes are a count only, never digested: a sha256 of a 6-digit code
10
10
  // has ~10^6 preimages and is trivially brute-forced back to the code, so a "code digest" would leak it.
11
- // Net: NO raw address/subject/link/OTP text ever lands in the artifact. Same caution as the lobby code.
11
+ // Net: no raw address/subject/link/OTP text ever lands in the artifact. Same caution as the lobby code.
12
12
  import { digestText } from "../evidence/redaction.js";
13
13
  const COMMS_THREAD_SCHEMA = "humanish.comms-thread.v1";
14
14
  /** Project polled inbox messages into the digest-only thread artifact. Pure; the caller writes it into