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
@@ -1,7 +1,7 @@
1
1
  import type { ReceivingSurfaceFile } from "./receiving-types.js";
2
2
  import type { CommsMessage } from "./types.js";
3
3
  /** [fromOrigin, toOrigin] pairs: rewrite a link/href whose origin is the app's in-sandbox origin into a
4
- * lane-reachable origin, so the persona's browser can actually follow the verify link when it clicks. */
4
+ * participant-reachable origin, so the persona's browser can actually follow the verify link when it clicks. */
5
5
  export type OriginMap = Array<[string, string]>;
6
6
  export interface InboxRenderOptions {
7
7
  originMap?: OriginMap;
@@ -13,17 +13,18 @@ export interface InboxRenderOptions {
13
13
  export declare function inboxRecipientScope(address: string): string;
14
14
  export declare function recipientInboxUrl(inboxUrl: string, address: string): string;
15
15
  /** Rewrite a URL's origin per the map (origins are exact strings the harness minted). Leaves
16
- * non-matching URLs — including sibling origins sharing a prefix — untouched. */
16
+ * non-matching URLs (including sibling origins sharing a prefix) untouched. */
17
17
  export declare function rewriteOrigin(url: string, originMap?: OriginMap): string;
18
18
  /**
19
- * Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes ITS OWN
19
+ * Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes its own
20
20
  * origin into the verify links it emails (usually its loopback serve origin); the persona reaches the
21
- * app at a possibly-different origin — the same loopback on the CUA route (identity, a no-op), the
22
- * harness-minted getHost URL on the shared-world route (where the rewrite is REQUIRED). Emits the serve
23
- * origin plus its loopback aliases at the serve port (127.0.0.1 / localhost / 0.0.0.0) so an app that
24
- * stamps `localhost` still rewrites, and — because the harness cannot infer an absolute PUBLIC base URL
25
- * an app was configured with — an operator-declared `linkOrigin` escape hatch, matched first. Origins
26
- * only (no trailing slash, no path), so replaceOriginBoundary matches on a URL boundary.
21
+ * app at a possibly-different origin: the same loopback on the computer-use route (identity, a
22
+ * no-op), the harness-minted getHost URL on the shared-world route (where the rewrite is required).
23
+ * Emits the serve origin plus its loopback aliases at the serve port (127.0.0.1 / localhost /
24
+ * 0.0.0.0) so an app that stamps `localhost` still rewrites, and (because the harness cannot infer
25
+ * an absolute public base URL an app was configured with) an operator-declared `linkOrigin` escape
26
+ * hatch, matched first. Origins only (no trailing slash, no path), so replaceOriginBoundary matches
27
+ * on a URL boundary.
27
28
  */
28
29
  export declare function buildOriginMap(args: {
29
30
  internalServeUrl?: string | undefined;
@@ -32,11 +33,11 @@ export declare function buildOriginMap(args: {
32
33
  }): OriginMap;
33
34
  /** Best-guess primary call-to-action: the first verify/confirm-looking link, else the first link. */
34
35
  export declare function pickVerifyUrl(links: string[]): string | undefined;
35
- /** The inbox LIST page (semantic table; each row links to its message). */
36
+ /** The inbox list page (semantic table; each row links to its message). */
36
37
  export declare function renderInboxList(messages: CommsMessage[], options?: InboxRenderOptions): string;
37
- /** One message, DEFAULT view: the app's real captured email in a minimal high-contrast shell. */
38
+ /** One message, default view: the app's real captured email in a minimal high-contrast shell. */
38
39
  export declare function renderInboxMessage(message: CommsMessage, options?: InboxRenderOptions): string;
39
- /** One message, SYNTHESIZED view: a guaranteed-legible reading pane (big verify button + big OTP). The
40
+ /** One message, synthesized view: a guaranteed-legible reading pane (big verify button + big OTP). The
40
41
  * reliability fallback when the app's real email is a vision minefield; opt-in, never the default. */
41
42
  export declare function renderInboxMessageSynth(message: CommsMessage, options?: InboxRenderOptions): string;
42
43
  /** JSON projection of one message (the programmatic/CLI actor surface + the reliability backstop). Raw
@@ -46,7 +47,7 @@ export declare function inboxMessageJson(message: CommsMessage, options?: InboxR
46
47
  * Build every served file for the inbox surface from the normalized messages. The host writes each to
47
48
  * `<servedDir>/<path>`; the in-sandbox catch serves `<servedDir>/<pathname>`, falling back to
48
49
  * `<pathname>/index` (so a URL like `/inbox` maps to the `inbox/index` file while `/inbox/{id}` stays a
49
- * directory) — standard web-server index semantics that avoid a file-vs-directory path collision. The
50
+ * directory), standard web-server index semantics that avoid a file-vs-directory path collision. The
50
51
  * URLs the persona actually sees stay clean (`/inbox`, `/inbox/{id}`, `/inbox/{id}/synth`). Content type
51
52
  * is inferred from the `/api/` prefix. Message ids are `comms-NNNN` (never `index`/`latest`), so leaf
52
53
  * filenames never collide with an id.
@@ -1,4 +1,4 @@
1
- // The inbox surface for captured mail (#297): the pages a computer-use participant opens to read a
1
+ // The inbox surface for captured mail: the pages a computer-use participant opens to read a
2
2
  // captured verification email and follow its link. The pages are minimal semantic HTML (native
3
3
  // links and tables, black on white, large targets), because vision participants read a simple
4
4
  // inbox more reliably than a webmail client. The default view is the app's real captured email,
@@ -27,7 +27,7 @@ function inboxPath(options) {
27
27
  : `/inbox/for/${inboxRecipientScope(options.recipient)}`;
28
28
  }
29
29
  const VERIFY_HINT = /verify|confirm|activate|validate|magic|token|account|sign[\s-]?up/i;
30
- /** Replace `from` with `to` everywhere it ends on an ORIGIN BOUNDARY — end-of-string or one of the URL
30
+ /** Replace `from` with `to` everywhere it ends on an origin boundary: end-of-string or one of the URL
31
31
  * delimiters `/ ? # " ' <space> > \`. A bare prefix replace would mangle a sibling origin that shares
32
32
  * `from` as a numeric prefix (`:30000` vs `:3000`) or a suffix-domain (`:3000.evil`); the boundary
33
33
  * check prevents that. Works both for a single URL and inside a blob of email HTML. */
@@ -47,7 +47,7 @@ function replaceOriginBoundary(text, from, to) {
47
47
  }
48
48
  }
49
49
  /** Rewrite a URL's origin per the map (origins are exact strings the harness minted). Leaves
50
- * non-matching URLs — including sibling origins sharing a prefix — untouched. */
50
+ * non-matching URLs (including sibling origins sharing a prefix) untouched. */
51
51
  export function rewriteOrigin(url, originMap = []) {
52
52
  let out = url;
53
53
  for (const [from, to] of originMap)
@@ -75,14 +75,15 @@ function safePort(url) {
75
75
  }
76
76
  }
77
77
  /**
78
- * Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes ITS OWN
78
+ * Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes its own
79
79
  * origin into the verify links it emails (usually its loopback serve origin); the persona reaches the
80
- * app at a possibly-different origin — the same loopback on the CUA route (identity, a no-op), the
81
- * harness-minted getHost URL on the shared-world route (where the rewrite is REQUIRED). Emits the serve
82
- * origin plus its loopback aliases at the serve port (127.0.0.1 / localhost / 0.0.0.0) so an app that
83
- * stamps `localhost` still rewrites, and — because the harness cannot infer an absolute PUBLIC base URL
84
- * an app was configured with — an operator-declared `linkOrigin` escape hatch, matched first. Origins
85
- * only (no trailing slash, no path), so replaceOriginBoundary matches on a URL boundary.
80
+ * app at a possibly-different origin: the same loopback on the computer-use route (identity, a
81
+ * no-op), the harness-minted getHost URL on the shared-world route (where the rewrite is required).
82
+ * Emits the serve origin plus its loopback aliases at the serve port (127.0.0.1 / localhost /
83
+ * 0.0.0.0) so an app that stamps `localhost` still rewrites, and (because the harness cannot infer
84
+ * an absolute public base URL an app was configured with) an operator-declared `linkOrigin` escape
85
+ * hatch, matched first. Origins only (no trailing slash, no path), so replaceOriginBoundary matches
86
+ * on a URL boundary.
86
87
  */
87
88
  export function buildOriginMap(args) {
88
89
  const to = safeOrigin(args.reachableBaseUrl);
@@ -121,7 +122,7 @@ function esc(value) {
121
122
  .replace(/>/g, "&gt;")
122
123
  .replace(/"/g, "&quot;");
123
124
  }
124
- /** Defense-in-depth stripping before rendering the app's real email HTML. The LOAD-BEARING protection is
125
+ /** Defense-in-depth stripping before rendering the app's real email HTML. The main protection is
125
126
  * the page CSP (`script-src 'none'`, set both as a page() meta and as a catch response header), which is
126
127
  * browser-enforced and neuters inline handlers, `javascript:` URLs, and injected <script> regardless of
127
128
  * what this misses. This strip additionally removes elements CSP does not cover (a redirecting
@@ -133,9 +134,9 @@ function neutralizeEmailHtml(html, originMap) {
133
134
  .replace(/<(script|style|iframe|object|embed|svg|math|template)\b[\s\S]*?<\/\1\s*>/gi, "")
134
135
  // stray or void dangerous tags, incl. unclosed openers and redirect/reroot tags
135
136
  .replace(/<\/?(script|style|iframe|object|embed|svg|math|base|meta|link|form)\b[^>]*>/gi, "")
136
- // inline event handlers, whether preceded by whitespace OR a "/" attribute separator
137
+ // inline event handlers, whether preceded by whitespace or a "/" attribute separator
137
138
  .replace(/[\s/]on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, " ")
138
- // javascript:/vbscript: in nav/resource attrs — quoted OR unquoted (data: is left for inline images)
139
+ // javascript:/vbscript: in nav/resource attrs, quoted or unquoted (data: is left for inline images)
139
140
  .replace(/(href|src|xlink:href|formaction|action)\s*=\s*(?:"(?:javascript|vbscript):[^"]*"|'(?:javascript|vbscript):[^']*'|(?:javascript|vbscript):[^\s>]*)/gi, '$1="#"');
140
141
  // Rewrite the app's origin inside the (now-neutralized) HTML so its own links resolve to a reachable host.
141
142
  for (const [from, to] of originMap)
@@ -213,7 +214,7 @@ function renderEmailImages(html, message, originMap) {
213
214
  }
214
215
  }
215
216
  if (!src)
216
- return `<span class="email-image-unavailable" role="img" aria-label="${esc(alt || "Email image")}">${esc(alt ? `${alt} — ${unavailable}` : unavailable)}</span>`;
217
+ return `<span class="email-image-unavailable" role="img" aria-label="${esc(alt || "Email image")}">${esc(alt ? `${alt}: ${unavailable}` : unavailable)}</span>`;
217
218
  const sizing = ["width", "height"]
218
219
  .map((name) => {
219
220
  const value = attributes.get(name);
@@ -241,11 +242,11 @@ const PAGE_CSS = "body{font:16px/1.55 system-ui,-apple-system,Segoe UI,Roboto,sa
241
242
  "@media(max-width:560px){.inbox-list thead{display:none}.inbox-list,.inbox-list tbody,.inbox-list tr,.inbox-list td{display:block}" +
242
243
  ".inbox-list tr{padding:12px 0;border-bottom:1px solid #ddd}.inbox-list td{padding:3px 0;border:0}" +
243
244
  ".inbox-list td:first-child a{display:block;min-height:32px;font-weight:600}.inbox-list td[data-label]::before{content:attr(data-label) ': ';font-size:13px;color:#666}}";
244
- /** The surface CSP — the browser-enforced, load-bearing protection against the app-authored email running
245
+ /** The surface CSP: the browser-enforced main protection against the app-authored email running
245
246
  * script or hijacking navigation on the surface page. `script-src 'none'` blocks inline handlers,
246
247
  * `javascript:` URLs, and any injected <script>; `object-src`/`frame-src 'none'` block plugins/frames;
247
248
  * `base-uri 'none'` blocks <base> reroot. Images/styles stay permissive so the real email still renders.
248
- * Set BOTH as a page() meta (covers direct render use) and as a catch response header (covers serving). */
249
+ * Set both as a page() meta (covers direct render use) and as a catch response header (covers serving). */
249
250
  const INBOX_SURFACE_CSP = "default-src 'self'; script-src 'none'; object-src 'none'; base-uri 'none'; frame-src 'none'; img-src * data:; style-src 'unsafe-inline'; font-src * data:";
250
251
  function page(title, inner) {
251
252
  return ('<!doctype html><html lang="en"><head><meta charset="utf-8">' +
@@ -273,7 +274,7 @@ function fmtTime(ms) {
273
274
  function toValues(message) {
274
275
  return message.to.map((address) => address.value);
275
276
  }
276
- /** The inbox LIST page (semantic table; each row links to its message). */
277
+ /** The inbox list page (semantic table; each row links to its message). */
277
278
  export function renderInboxList(messages, options = {}) {
278
279
  const base = inboxPath(options);
279
280
  const rows = messages
@@ -295,8 +296,8 @@ export function renderInboxList(messages, options = {}) {
295
296
  "</td></tr>")
296
297
  .join("");
297
298
  const body = (options.recipient
298
- ? '<div class="bar">Inbox for ' + esc(options.recipient) + " — "
299
- : '<div class="bar">Shared operator inbox — all captured recipients — ') +
299
+ ? '<div class="bar">Inbox for ' + esc(options.recipient) + ": "
300
+ : '<div class="bar">Shared operator inbox (all captured recipients): ') +
300
301
  messages.length +
301
302
  " message" +
302
303
  (messages.length === 1 ? "" : "s") +
@@ -307,7 +308,7 @@ export function renderInboxList(messages, options = {}) {
307
308
  "</tbody></table></main>";
308
309
  return page("Inbox", body);
309
310
  }
310
- /** One message, DEFAULT view: the app's real captured email in a minimal high-contrast shell. */
311
+ /** One message, default view: the app's real captured email in a minimal high-contrast shell. */
311
312
  export function renderInboxMessage(message, options = {}) {
312
313
  const originMap = options.originMap ?? [];
313
314
  const base = inboxPath(options);
@@ -336,7 +337,7 @@ export function renderInboxMessage(message, options = {}) {
336
337
  "</main>";
337
338
  return page(message.subject ?? "Message", body);
338
339
  }
339
- /** One message, SYNTHESIZED view: a guaranteed-legible reading pane (big verify button + big OTP). The
340
+ /** One message, synthesized view: a guaranteed-legible reading pane (big verify button + big OTP). The
340
341
  * reliability fallback when the app's real email is a vision minefield; opt-in, never the default. */
341
342
  export function renderInboxMessageSynth(message, options = {}) {
342
343
  const originMap = options.originMap ?? [];
@@ -418,7 +419,7 @@ function inboxListJson(messages, options = {}) {
418
419
  * Build every served file for the inbox surface from the normalized messages. The host writes each to
419
420
  * `<servedDir>/<path>`; the in-sandbox catch serves `<servedDir>/<pathname>`, falling back to
420
421
  * `<pathname>/index` (so a URL like `/inbox` maps to the `inbox/index` file while `/inbox/{id}` stays a
421
- * directory) — standard web-server index semantics that avoid a file-vs-directory path collision. The
422
+ * directory), standard web-server index semantics that avoid a file-vs-directory path collision. The
422
423
  * URLs the persona actually sees stay clean (`/inbox`, `/inbox/{id}`, `/inbox/{id}/synth`). Content type
423
424
  * is inferred from the `/api/` prefix. Message ids are `comms-NNNN` (never `index`/`latest`), so leaf
424
425
  * filenames never collide with an id.
@@ -9,14 +9,14 @@ export interface CommsCatchHostOptions {
9
9
  dir: string;
10
10
  /** Bearer token required on GET /deliveries. Strongly recommended when the host is reachable. */
11
11
  token?: string;
12
- /** SECOND port for the READ-ONLY inbox listener bound to 0.0.0.0, so a persona on another machine
13
- * (or in a per-lane desktop) can reach the inbox. Omit to stay loopback-only. */
12
+ /** Second port for the read-only inbox listener bound to 0.0.0.0, so a persona on another machine
13
+ * (or on a participant's desktop) can reach the inbox. Omit to stay loopback-only. */
14
14
  inboxPort?: number;
15
15
  /** Optional loopback SMTP listener sharing the HTTP capture and inbox pipeline. */
16
16
  smtpPort?: number;
17
- /** Restrict the rendered inbox to these addresses. Omit to render whatever the app actually mailed
18
- * — a standalone catch has no lab roster to read recipients from, and an operator who forgets to
19
- * name one should not get a healthy catch that renders an empty inbox forever (#380). */
17
+ /** Restrict the rendered inbox to these addresses. Omit to render whatever the app actually mailed;
18
+ * a standalone catch has no lab roster to read recipients from, and an operator who forgets to
19
+ * name one should not get a healthy catch that renders an empty inbox forever. */
20
20
  recipients?: string[];
21
21
  /** Inbox re-render cadence in ms (test seam). */
22
22
  renderIntervalMs?: number;
@@ -24,10 +24,10 @@ export interface CommsCatchHostOptions {
24
24
  /**
25
25
  * Render the persona-facing inbox surface from the catch's own deliveries file into `surfaceDir`.
26
26
  *
27
- * This is the half that was missing on an adopter-hosted plane (#380). The catch serves /inbox and
28
- * /api/inbox as STATIC FILES; in-sandbox, humanish-as-host renders those files on a cadence. On a
27
+ * This is the half that was missing on an adopter-hosted plane. The catch serves /inbox and
28
+ * /api/inbox as static files; in-sandbox, humanish-as-host renders those files on a cadence. On a
29
29
  * plane humanish does not provision there is no such host, so nothing wrote them and every persona
30
- * that reached /inbox got `message not found` against a catch whose /health was green — the funnel
30
+ * that reached /inbox got `message not found` against a catch whose /health was green, so the funnel
31
31
  * dead-ended at a technically-healthy service.
32
32
  *
33
33
  * Rebuilding from scratch each pass is deliberate and matches refreshInboxSurface: a fresh channel per
@@ -1,19 +1,19 @@
1
- // `humanish comms catch` (#328): run the email catch on the OPERATOR's own host.
1
+ // `humanish comms catch`: run the email catch on the operator's own host.
2
2
  //
3
- // Why this exists. The in-sandbox catch only works when humanish provisions the subject itself —
3
+ // Why this exists. The in-sandbox catch only works when humanish provisions the subject itself:
4
4
  // it clones the app into a sandbox, injects the email-API base URL at boot, and hosts the catch
5
- // alongside it. An adopter whose study runs against their OWN deployed environment (an app-url or
5
+ // alongside it. An adopter whose study runs against their own deployed environment (an app-url or
6
6
  // operator-provisioned plane) hands humanish a URL instead, so humanish never boots the app and
7
7
  // has nowhere to put a catch. Before this, a `comms:` block on that route was warned inert and the
8
8
  // personas simply stalled at the verification screen.
9
9
  //
10
10
  // The fix is to let the adopter host the same catch and declare it (`comms.email.external`).
11
- // Shipping it as a COMMAND rather than a spec matters: the adopter runs the identical
11
+ // Shipping it as a command rather than a spec matters: the adopter runs the identical
12
12
  // implementation humanish deploys in-sandbox, so the capture shape, the inbox surface, and the
13
13
  // drain contract cannot drift between the two planes. Point the app's email-API base URL at this
14
14
  // server and point the lab's `catchBaseUrl` at it too.
15
15
  //
16
- // Runtime: the catch is a python3 stdlib server with no dependencies (that was the 0.29.0 lesson —
16
+ // Runtime: the catch is a python3 stdlib server with no dependencies (that was the 0.29.0 lesson:
17
17
  // a co-located catcher must use a runtime the environment guarantees). This command writes the
18
18
  // same script and runs it in the foreground until interrupted.
19
19
  import { spawn } from "node:child_process";
@@ -27,10 +27,10 @@ const DEFAULT_RENDER_INTERVAL_MS = 3_000;
27
27
  /**
28
28
  * Render the persona-facing inbox surface from the catch's own deliveries file into `surfaceDir`.
29
29
  *
30
- * This is the half that was missing on an adopter-hosted plane (#380). The catch serves /inbox and
31
- * /api/inbox as STATIC FILES; in-sandbox, humanish-as-host renders those files on a cadence. On a
30
+ * This is the half that was missing on an adopter-hosted plane. The catch serves /inbox and
31
+ * /api/inbox as static files; in-sandbox, humanish-as-host renders those files on a cadence. On a
32
32
  * plane humanish does not provision there is no such host, so nothing wrote them and every persona
33
- * that reached /inbox got `message not found` against a catch whose /health was green — the funnel
33
+ * that reached /inbox got `message not found` against a catch whose /health was green, so the funnel
34
34
  * dead-ended at a technically-healthy service.
35
35
  *
36
36
  * Rebuilding from scratch each pass is deliberate and matches refreshInboxSurface: a fresh channel per
@@ -43,7 +43,7 @@ export async function renderInboxSurfaceLocally(args) {
43
43
  text = await readFile(args.deliveriesPath, "utf8");
44
44
  }
45
45
  catch {
46
- text = ""; // no mail captured yet — still render, so /inbox answers "No messages yet."
46
+ text = ""; // no mail captured yet; still render, so /inbox answers "No messages yet."
47
47
  }
48
48
  const sends = parseDeliveriesNdjson(text);
49
49
  const addresses = args.recipients && args.recipients.length > 0
@@ -55,7 +55,7 @@ export async function renderInboxSurfaceLocally(args) {
55
55
  }));
56
56
  const messages = await inboxMessagesFrom(sends, recipients);
57
57
  const files = buildInboxSurface(messages, { recipients: addresses });
58
- // A reused catch directory can change recipients or truncate its delivery log. Remove ONLY files
58
+ // A reused catch directory can change recipients or truncate its delivery log. Remove only files
59
59
  // recorded as generated routes, so old recipient/message URLs cannot survive that change.
60
60
  const manifestPath = path.join(args.surfaceDir, ".inbox-files.json");
61
61
  let previous = [];
@@ -105,8 +105,8 @@ export async function runCommsCatchHost(options, io) {
105
105
  const surfaceDir = path.join(dir, "surface");
106
106
  await mkdir(surfaceDir, { recursive: true });
107
107
  await writeFile(scriptPath, SANDBOX_CATCH_SCRIPT, "utf8");
108
- // Render the EMPTY inbox before the server is announced, so /inbox resolves to the "No messages
109
- // yet." page from the first request instead of a bare `message not found` (#380) — a persona reads
108
+ // Render the empty inbox before the server is announced, so /inbox resolves to the "No messages
109
+ // yet." page from the first request instead of a bare `message not found`, because a persona reads
110
110
  // that 404 as a broken product and reports a blocker that is really just an empty mailbox.
111
111
  await renderInboxSurfaceLocally({
112
112
  deliveriesPath,
@@ -131,7 +131,7 @@ export async function runCommsCatchHost(options, io) {
131
131
  ...(inboxPort === undefined
132
132
  ? []
133
133
  : [
134
- ` read-only inbox listener on http://0.0.0.0:${inboxPort} (GET only; expose THIS to personas)`,
134
+ ` read-only inbox listener on http://0.0.0.0:${inboxPort} (GET only; this is the address to give personas)`,
135
135
  ]),
136
136
  ` POST /emails <- point your app's email-API base URL here`,
137
137
  ` GET /inbox <- shared operator inbox${inboxPort === undefined ? " (loopback only without --inbox-port)" : ""}`,
@@ -149,7 +149,7 @@ export async function runCommsCatchHost(options, io) {
149
149
  ? [` authTokenEnv: HUMANISH_COMMS_TOKEN # value read at runtime, never persisted`]
150
150
  : []),
151
151
  ``,
152
- `Captured mail is written to ${deliveriesPath}. Raw bodies stay on THIS host: the run bundle`,
152
+ `Captured mail is written to ${deliveriesPath}. Raw bodies stay on this host: the run bundle`,
153
153
  `only ever receives digests (from/to/subject/link) and an OTP count.`,
154
154
  ``,
155
155
  ].join("\n") + "\n");
@@ -4,10 +4,10 @@ import path from "node:path";
4
4
  import { parse, stringify } from "yaml";
5
5
  import { COMMS_PROVIDERS, DEFAULT_COMMS_PROVIDER, isReceivingProviderId, } from "./providers.js";
6
6
  import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory, prepareManagedHumanishOutputDirectory, readContainedRegularFile, writeContainedOutputFile, } from "../run/contained-output.js";
7
+ import { isRecord } from "../run/type-guards.js";
7
8
  const COMMS_CONNECTIONS_SCHEMA = "humanish.comms-connections.v1";
8
9
  export const COMMS_CONFIG_PATH = ".humanish/local/comms.yaml";
9
10
  export { COMMS_PROVIDERS };
10
- const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
11
11
  const validName = (name) => /^[a-z][a-z0-9-]{0,47}$/.test(name) && name !== "constructor" && name !== "prototype";
12
12
  const empty = () => ({ schema: COMMS_CONNECTIONS_SCHEMA, connections: {} });
13
13
  async function exists(file) {
@@ -22,16 +22,16 @@ async function exists(file) {
22
22
  }
23
23
  }
24
24
  function parseConnections(raw) {
25
- if (!record(raw) ||
25
+ if (!isRecord(raw) ||
26
26
  raw.schema !== COMMS_CONNECTIONS_SCHEMA ||
27
- !record(raw.connections) ||
27
+ !isRecord(raw.connections) ||
28
28
  Object.keys(raw).some((key) => key !== "schema" && key !== "connections") ||
29
29
  Object.keys(raw.connections).length > 32)
30
30
  throw new Error("Invalid connection configuration.");
31
31
  const connections = {};
32
32
  for (const [name, value] of Object.entries(raw.connections)) {
33
33
  if (!validName(name) ||
34
- !record(value) ||
34
+ !isRecord(value) ||
35
35
  !isReceivingProviderId(value.provider) ||
36
36
  typeof value.apiKeyEnv !== "string" ||
37
37
  !/^[A-Z][A-Z0-9_]{0,99}$/.test(value.apiKeyEnv) ||
@@ -10,10 +10,10 @@ interface NormalizedSend {
10
10
  }
11
11
  /**
12
12
  * A provider wire-shape adapter. Vendor-neutral seam: the server owns the HTTP + routing + bus
13
- * delivery; a profile only knows how ONE provider (or a custom app) shapes its send request.
13
+ * delivery; a profile only knows how one provider (or a custom app) shapes its send request.
14
14
  */
15
15
  export interface EmailSendProfile {
16
- /** Descriptive name (e.g. "generic", "sendgrid"). Not a dependency — just a label. */
16
+ /** Descriptive name (e.g. "generic", "sendgrid"). Not a dependency, just a label. */
17
17
  name: string;
18
18
  /** Request paths (method POST) this profile accepts a send on. */
19
19
  sendPaths: string[];
@@ -31,7 +31,7 @@ export interface EmailCatchServer {
31
31
  close(): Promise<void>;
32
32
  }
33
33
  export interface EmailCatchOptions {
34
- /** Bind host. Default 127.0.0.1 (loopback ONLY). */
34
+ /** Bind host. Default 127.0.0.1 (loopback only). */
35
35
  host?: string;
36
36
  /** Port. Default 0 (ephemeral). */
37
37
  port?: number;
@@ -1,9 +1,9 @@
1
- // A VENDOR-NEUTRAL loopback catch server for email-send APIs (#297). An app hardwired to a hosted
2
- // email provider is redirected into the fake bus with ONE env var and no code change: point the app's
3
- // API base URL at this server. The catch does not depend on, or name itself after, any one vendor —
1
+ // A vendor-neutral loopback catch server for email-send APIs. An app hardwired to a hosted
2
+ // email provider is redirected into the fake bus with one env var and no code change: point the app's
3
+ // API base URL at this server. The catch does not depend on, or name itself after, any one vendor:
4
4
  // it normalizes each provider's distinct wire shape (Resend's flat body, SendGrid's nested
5
5
  // personalizations, Postmark's TitleCase, a custom app's own JSON) to one shape via pluggable
6
- // PROFILES, and routes the result into a CommsChannel. Resend-compatible and SendGrid-compatible out
6
+ // profiles, and routes the result into a CommsChannel. Resend-compatible and SendGrid-compatible out
7
7
  // of the box; extend with a custom profile for anything else. (There is no standardized email-send
8
8
  // request shape across vendors, so normalization is by design, not a shortcut.)
9
9
  //
@@ -34,8 +34,8 @@ function toAddresses(value) {
34
34
  return [];
35
35
  }
36
36
  // ---------------------------------------------------------------- built-in profiles
37
- /** The flat-JSON shape: `{ from, to, subject, html, text }`. Matches Resend (POST /emails) AND a
38
- * custom app that sends the common shape AND Postmark's TitleCase keys (From/To/HtmlBody/…). */
37
+ /** The flat-JSON shape: `{ from, to, subject, html, text }`. Matches Resend (POST /emails) and a
38
+ * custom app that sends the common shape and Postmark's TitleCase keys (From/To/HtmlBody/…). */
39
39
  const genericEmailProfile = {
40
40
  name: "generic",
41
41
  sendPaths: ["/emails", "/emails/batch", "/email", "/email/batch", "/send"],
@@ -125,7 +125,7 @@ function readBody(req, limit) {
125
125
  req.on("data", (chunk) => {
126
126
  size += chunk.length;
127
127
  if (size > limit) {
128
- // Reject WITHOUT destroying the socket here: the handler responds 413 cleanly first (a
128
+ // Reject without destroying the socket here: the handler responds 413 cleanly first (a
129
129
  // write-after-destroy would otherwise reset the connection), then tears the request down.
130
130
  reject(new BodyTooLargeError("request body too large"));
131
131
  return;
@@ -193,7 +193,7 @@ export async function startEmailCatchServer(channel, options = {}) {
193
193
  const sends = profile.parse(path, parsed);
194
194
  if (sends.length === 0) {
195
195
  // A well-formed request that names no deliverable recipient (empty body, or a shape the profile
196
- // could not resolve to a send) — return a bad-request rather than a fabricated success id.
196
+ // could not resolve to a send): return a bad-request rather than a fabricated success id.
197
197
  respondJson(res, 422, { error: "no deliverable message in request" });
198
198
  return;
199
199
  }
@@ -0,0 +1,27 @@
1
+ import type { LabCommsEmail, LabCommsExternal } from "../lab/types.js";
2
+ import type { PreparedRunArtifactPaths } from "../run/paths.js";
3
+ /**
4
+ * Why a catch token cannot be used, or undefined when it can. An unset or empty token is none. A
5
+ * token that is not well-formed Unicode cannot be sent as a header or encoded as a URL, so it is
6
+ * refused with the short ones.
7
+ */
8
+ export declare function catchTokenRefusal(token: string | undefined): string | undefined;
9
+ /** The catch token the run sends: the value of `authTokenEnv`, when the lab names one. */
10
+ export declare function catchTokenOf(external: LabCommsExternal, env: Record<string, string | undefined>): string | undefined;
11
+ /**
12
+ * Drains the catch, routes its sends to the declared recipients' inboxes and writes the thread to
13
+ * `comms/thread.json`. Returns that path when a message matched, and the warnings to add to the
14
+ * run, each scrubbed of the run's known secret values and the catch token, then redacted. It never
15
+ * throws: the run continues without comms evidence.
16
+ */
17
+ export declare function collectExternalCommsEvidence(args: {
18
+ external: LabCommsExternal;
19
+ email: LabCommsEmail;
20
+ env: Record<string, string | undefined>;
21
+ runPaths: PreparedRunArtifactPaths;
22
+ /** The run's known secret values. The route may add to it until the drain runs. */
23
+ knownSecretValues: readonly string[];
24
+ }): Promise<{
25
+ path?: string;
26
+ warnings: string[];
27
+ }>;
@@ -0,0 +1,93 @@
1
+ // An adopter-hosted catch's thread, collected into a run. The computer-use and shared-world routes
2
+ // both call this, so every warning it returns is scrubbed the same way on both: an error or the
3
+ // catch URL can carry the catch's bearer token or a provisioned value, and neither has a secret's
4
+ // shape for pattern redaction to find.
5
+ import { redactText, toErrorMessage } from "../evidence/redaction.js";
6
+ import { scrubSecretValues } from "../evidence/secret-scrub.js";
7
+ import { addressedRecipients } from "../lab/parse/comms.js";
8
+ import { writeContainedOutputFile } from "../run/contained-output.js";
9
+ import { FakeInbox } from "./fake-inbox.js";
10
+ import { collectExternalCommsThread } from "./sandbox-catch.js";
11
+ /**
12
+ * The shortest catch bearer token humanish uses. The token guards GET /deliveries on a host the run
13
+ * reaches over the network, so it must not be guessable: 16 random base64 characters are 96 bits.
14
+ * The run also scrubs the token from every warning as a literal, and a short token would scrub
15
+ * ordinary words.
16
+ */
17
+ const MIN_CATCH_TOKEN_LENGTH = 16;
18
+ /** String.prototype.isWellFormed (ES2024, Node 20+); the ES2023 lib types do not declare it. */
19
+ function isWellFormed(value) {
20
+ return value.isWellFormed();
21
+ }
22
+ /**
23
+ * Why a catch token cannot be used, or undefined when it can. An unset or empty token is none. A
24
+ * token that is not well-formed Unicode cannot be sent as a header or encoded as a URL, so it is
25
+ * refused with the short ones.
26
+ */
27
+ export function catchTokenRefusal(token) {
28
+ if (token === undefined || token.length === 0)
29
+ return undefined;
30
+ if (!isWellFormed(token))
31
+ return "The comms catch token is not well-formed Unicode; use printable ASCII, such as the output of `openssl rand -hex 16`, on the catch and in the run.";
32
+ if (token.length >= MIN_CATCH_TOKEN_LENGTH)
33
+ return undefined;
34
+ return `The comms catch token is ${token.length} characters; it must be at least ${MIN_CATCH_TOKEN_LENGTH}, such as the output of \`openssl rand -hex 16\`, on the catch and in the run.`;
35
+ }
36
+ /** The catch token the run sends: the value of `authTokenEnv`, when the lab names one. */
37
+ export function catchTokenOf(external, env) {
38
+ return external.authTokenEnv === undefined ? undefined : env[external.authTokenEnv];
39
+ }
40
+ /**
41
+ * Drains the catch, routes its sends to the declared recipients' inboxes and writes the thread to
42
+ * `comms/thread.json`. Returns that path when a message matched, and the warnings to add to the
43
+ * run, each scrubbed of the run's known secret values and the catch token, then redacted. It never
44
+ * throws: the run continues without comms evidence.
45
+ */
46
+ export async function collectExternalCommsEvidence(args) {
47
+ const { external, email, env, runPaths, knownSecretValues } = args;
48
+ const authToken = catchTokenOf(external, env);
49
+ // Pattern redaction alone until the literal scrub exists, so a warning built in the catch below
50
+ // is never unredacted. The scrub is built inside the try, where a failure becomes that warning.
51
+ let scrubbed = (warnings) => warnings.map(redactText);
52
+ try {
53
+ const scrub = scrubSecretValues([
54
+ ...knownSecretValues,
55
+ ...(authToken === undefined ? [] : [authToken]),
56
+ ]);
57
+ scrubbed = (warnings) => warnings.map((warning) => redactText(scrub(warning)));
58
+ const channel = new FakeInbox();
59
+ const inboxes = [];
60
+ for (const recipient of addressedRecipients(email)) {
61
+ inboxes.push(await channel.provisionAddress(recipient.participantId, recipient.address));
62
+ }
63
+ const collected = await collectExternalCommsThread({
64
+ external: { ...external, ...(authToken === undefined ? {} : { authToken }) },
65
+ channel,
66
+ inboxes,
67
+ });
68
+ if (collected.artifact) {
69
+ const path = "comms/thread.json";
70
+ await writeContainedOutputFile(runPaths, path, `${JSON.stringify(collected.artifact, null, 2)}\n`, "utf8");
71
+ return { path, warnings: [] };
72
+ }
73
+ if (collected.captured > 0) {
74
+ return {
75
+ warnings: scrubbed([
76
+ `The email catch captured ${collected.captured} email send(s), but none matched a declared recipient inbox, so no email evidence was written. Set comms.email.recipients[].address to the address the app sends to.`,
77
+ ]),
78
+ };
79
+ }
80
+ return {
81
+ warnings: scrubbed([
82
+ `The email catch captured no email sends: the app never delivered mail through the catch at ${external.catchBaseUrl}. Verify the app's email API base URL points at it and that the flow reached an email step.`,
83
+ ]),
84
+ };
85
+ }
86
+ catch (error) {
87
+ return {
88
+ warnings: scrubbed([
89
+ `Comms evidence collection failed against the adopter-hosted catch (run continues): ${String(toErrorMessage(error))}`,
90
+ ]),
91
+ };
92
+ }
93
+ }
@@ -48,8 +48,8 @@ export function extractOtpCodes(body) {
48
48
  list.push(c);
49
49
  }
50
50
  };
51
- // The alphanumeric alternative requires at least one DIGIT (lookahead) so a labeled prose word like
52
- // "your code is INVALID" isn't captured as a code; pure-digit codes (4–8) match directly.
51
+ // The alphanumeric alternative requires at least one digit (lookahead) so a labeled prose word like
52
+ // `your code is INVALID` isn't captured as a code; pure-digit codes (4–8) match directly.
53
53
  const labeledRe = /(?:one[-\s]?time\s+(?:pass)?code|verification\s+code|security\s+code|access\s+code|login\s+code|confirmation\s+code|passcode|\bOTP\b|\bPIN\b|\bcode\b)\D{0,15}\b([0-9]{4,8}|(?=[A-Za-z0-9]*[0-9])[A-Z0-9]{6,8})\b/gi;
54
54
  let m;
55
55
  while ((m = labeledRe.exec(text)) !== null)
@@ -1,6 +1,6 @@
1
1
  import type { CommsAddress, CommsChannel, CommsChannelKind, CommsMessage, InboundRaw, OutboundMessage } from "./types.js";
2
2
  export interface FakeInboxOptions {
3
- /** "email" (default) or "sms" — the address shape + surface differ; machinery is identical. */
3
+ /** "email" (default) or "sms": the address shape + surface differ; machinery is identical. */
4
4
  channel?: CommsChannelKind;
5
5
  /** Email domain for minted addresses. Default example.test, an RFC 6761 reserved, unroutable
6
6
  * test domain. */
@@ -21,7 +21,7 @@ export declare class FakeInbox implements CommsChannel {
21
21
  constructor(options?: FakeInboxOptions);
22
22
  provision(participantId: string): Promise<CommsAddress>;
23
23
  /**
24
- * Provision an inbox for `participantId` at an EXPLICIT address (a lab-declared recipient), so
24
+ * Provision an inbox for `participantId` at an explicit address (a lab-declared recipient), so
25
25
  * the app-under-test's send to that literal address resolves in `deliverRaw` (which drops
26
26
  * recipients with no provisioned inbox). Declaring the same address intentionally shares its
27
27
  * inbox; if `participantId` already held a different auto-generated address, the declared
@@ -1,4 +1,4 @@
1
- // The in-process email/SMS bus (#297): a working, in-memory implementation of the CommsChannel
1
+ // The in-process email/SMS bus: a working, in-memory implementation of the CommsChannel
2
2
  // port. Deterministic, offline and free: a message the app under test sends (through an ingress
3
3
  // such as the email catch) is routed to the addressed inbox and read back through the same port.
4
4
  // Nothing leaves the process. See types.ts for the port and its public-safety notes.
@@ -60,7 +60,7 @@ export class FakeInbox {
60
60
  return address;
61
61
  }
62
62
  /**
63
- * Provision an inbox for `participantId` at an EXPLICIT address (a lab-declared recipient), so
63
+ * Provision an inbox for `participantId` at an explicit address (a lab-declared recipient), so
64
64
  * the app-under-test's send to that literal address resolves in `deliverRaw` (which drops
65
65
  * recipients with no provisioned inbox). Declaring the same address intentionally shares its
66
66
  * inbox; if `participantId` already held a different auto-generated address, the declared