humanish 0.107.0 → 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 (521) hide show
  1. package/ARCHITECTURE.md +22 -17
  2. package/CHANGELOG.md +14 -0
  3. package/CONTEXT.md +51 -44
  4. package/README.md +145 -172
  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-ui.js +1 -1
  14. package/dist/actors/codex/codex-admission.d.ts +91 -0
  15. package/dist/actors/codex/codex-admission.js +150 -0
  16. package/dist/actors/codex/restricted-executable.d.ts +4 -4
  17. package/dist/actors/codex/restricted-executable.js +8 -8
  18. package/dist/actors/codex/restricted-launch.d.ts +2 -1
  19. package/dist/actors/codex/restricted-launch.js +1 -1
  20. package/dist/actors/codex/restricted-participant.js +8 -6
  21. package/dist/actors/codex/restricted-policy.d.ts +2 -2
  22. package/dist/actors/codex/restricted-policy.js +2 -2
  23. package/dist/actors/codex/restricted-session.d.ts +2 -2
  24. package/dist/actors/codex/restricted-session.js +10 -5
  25. package/dist/actors/computer-use/actor.d.ts +16 -16
  26. package/dist/actors/computer-use/actor.js +2 -2
  27. package/dist/actors/computer-use/admission-limit.d.ts +1 -1
  28. package/dist/actors/computer-use/admission-limit.js +1 -1
  29. package/dist/actors/computer-use/loop/actions.d.ts +3 -3
  30. package/dist/actors/computer-use/loop/actions.js +10 -10
  31. package/dist/actors/computer-use/loop/backstop.d.ts +3 -3
  32. package/dist/actors/computer-use/loop/backstop.js +14 -12
  33. package/dist/actors/computer-use/loop/ending.d.ts +5 -5
  34. package/dist/actors/computer-use/loop/ending.js +7 -7
  35. package/dist/actors/computer-use/loop/observation.d.ts +2 -2
  36. package/dist/actors/computer-use/loop/observation.js +9 -9
  37. package/dist/actors/computer-use/loop/provider-call.d.ts +1 -1
  38. package/dist/actors/computer-use/loop/provider-call.js +1 -1
  39. package/dist/actors/computer-use/loop/session.js +3 -3
  40. package/dist/actors/computer-use/loop/spend.d.ts +1 -1
  41. package/dist/actors/computer-use/loop/spend.js +1 -1
  42. package/dist/actors/computer-use/loop/trace.js +9 -8
  43. package/dist/actors/computer-use/loop/types.d.ts +25 -23
  44. package/dist/actors/computer-use/loop/usage.js +2 -2
  45. package/dist/actors/computer-use/openai-context.d.ts +37 -0
  46. package/dist/actors/computer-use/openai-context.js +214 -0
  47. package/dist/actors/computer-use/openai-provider.d.ts +11 -8
  48. package/dist/actors/computer-use/openai-provider.js +45 -34
  49. package/dist/actors/computer-use/openai-requests.d.ts +36 -10
  50. package/dist/actors/computer-use/openai-requests.js +82 -15
  51. package/dist/actors/computer-use/openai-wire.d.ts +19 -7
  52. package/dist/actors/computer-use/openai-wire.js +36 -34
  53. package/dist/actors/contract.d.ts +78 -43
  54. package/dist/actors/contract.js +29 -23
  55. package/dist/actors/local-agent/claude-session.d.ts +1 -1
  56. package/dist/actors/local-agent/claude-session.js +10 -9
  57. package/dist/actors/local-agent/cli.d.ts +8 -6
  58. package/dist/actors/local-agent/cli.js +24 -28
  59. package/dist/actors/local-agent/readiness.d.ts +2 -2
  60. package/dist/actors/local-agent/readiness.js +4 -4
  61. package/dist/actors/reasoning-effort.js +6 -6
  62. package/dist/actors/registry.d.ts +10 -10
  63. package/dist/actors/registry.js +18 -17
  64. package/dist/actors/scripted-browser/actor.d.ts +8 -8
  65. package/dist/actors/scripted-browser/actor.js +16 -16
  66. package/dist/actors/scripted-browser/browser-command.js +1 -1
  67. package/dist/actors/scripted-browser/steps.js +1 -1
  68. package/dist/actors/scripted-browser/types.d.ts +1 -1
  69. package/dist/actors/stop-conditions.d.ts +1 -1
  70. package/dist/analysis/codex-config.d.ts +6 -2
  71. package/dist/analysis/codex-config.js +22 -14
  72. package/dist/analysis/service.js +5 -5
  73. package/dist/cli/commands/analyze.js +19 -16
  74. package/dist/cli/commands/codex.js +9 -9
  75. package/dist/cli/commands/comms.js +54 -33
  76. package/dist/cli/commands/feedback.js +20 -21
  77. package/dist/cli/commands/lab-format.d.ts +5 -4
  78. package/dist/cli/commands/lab-format.js +35 -23
  79. package/dist/cli/commands/lab-route-computer-use.js +6 -7
  80. package/dist/cli/commands/lab-route-preview.js +4 -3
  81. package/dist/cli/commands/lab-route-shared-world.js +3 -3
  82. package/dist/cli/commands/lab-run.js +10 -11
  83. package/dist/cli/commands/lab-scorer.d.ts +2 -2
  84. package/dist/cli/commands/lab-scorer.js +1 -1
  85. package/dist/cli/commands/migrate.d.ts +4 -0
  86. package/dist/cli/commands/migrate.js +65 -0
  87. package/dist/cli/commands/observe.js +147 -108
  88. package/dist/cli/commands/run-command.d.ts +10 -0
  89. package/dist/cli/commands/run-command.js +85 -0
  90. package/dist/cli/commands/runs.js +137 -138
  91. package/dist/cli/commands/setup.d.ts +2 -2
  92. package/dist/cli/commands/setup.js +26 -21
  93. package/dist/cli/commands/study.d.ts +3 -0
  94. package/dist/cli/commands/study.js +182 -0
  95. package/dist/cli/commands/tui.d.ts +4 -4
  96. package/dist/cli/commands/tui.js +21 -21
  97. package/dist/cli/commands/watch.js +60 -75
  98. package/dist/cli/deprecations.d.ts +18 -0
  99. package/dist/cli/deprecations.js +48 -0
  100. package/dist/cli/doctor.d.ts +15 -5
  101. package/dist/cli/doctor.js +103 -48
  102. package/dist/cli/first-run-path.d.ts +12 -10
  103. package/dist/cli/first-run-path.js +30 -28
  104. package/dist/cli/io.d.ts +41 -6
  105. package/dist/cli/io.js +97 -22
  106. package/dist/cli/observer-follow.d.ts +2 -2
  107. package/dist/cli/observer-follow.js +5 -6
  108. package/dist/cli/orientation.d.ts +6 -3
  109. package/dist/cli/orientation.js +43 -39
  110. package/dist/cli/product-sentence.d.ts +6 -0
  111. package/dist/cli/product-sentence.js +6 -0
  112. package/dist/cli/program.d.ts +6 -1
  113. package/dist/cli/program.js +109 -40
  114. package/dist/cli/telemetry.d.ts +15 -15
  115. package/dist/cli/telemetry.js +48 -40
  116. package/dist/cli.js +1 -1
  117. package/dist/comms/capture-surface.d.ts +13 -12
  118. package/dist/comms/capture-surface.js +23 -22
  119. package/dist/comms/catch-host.d.ts +7 -7
  120. package/dist/comms/catch-host.js +14 -14
  121. package/dist/comms/email-catch.d.ts +3 -3
  122. package/dist/comms/email-catch.js +8 -8
  123. package/dist/comms/external-evidence.js +2 -2
  124. package/dist/comms/extract.js +2 -2
  125. package/dist/comms/fake-inbox.d.ts +2 -2
  126. package/dist/comms/fake-inbox.js +2 -2
  127. package/dist/comms/receiving-render.d.ts +1 -1
  128. package/dist/comms/receiving-render.js +2 -2
  129. package/dist/comms/sandbox-catch-script.d.ts +6 -6
  130. package/dist/comms/sandbox-catch-script.js +6 -6
  131. package/dist/comms/sandbox-catch.d.ts +36 -37
  132. package/dist/comms/sandbox-catch.js +34 -35
  133. package/dist/comms/setup.js +42 -8
  134. package/dist/comms/thread-evidence.d.ts +1 -1
  135. package/dist/comms/thread-evidence.js +6 -6
  136. package/dist/comms/types.d.ts +6 -6
  137. package/dist/comms/types.js +3 -3
  138. package/dist/evidence/frame-signature.d.ts +1 -1
  139. package/dist/evidence/frame-signature.js +4 -4
  140. package/dist/evidence/redaction.d.ts +3 -3
  141. package/dist/evidence/redaction.js +4 -4
  142. package/dist/feedback/draft.js +36 -78
  143. package/dist/feedback/export-bundle.js +1 -1
  144. package/dist/feedback/export.d.ts +5 -1
  145. package/dist/feedback/export.js +7 -8
  146. package/dist/feedback/feedback.d.ts +2 -2
  147. package/dist/feedback/feedback.js +21 -3
  148. package/dist/guest/chromium-text.js +2 -2
  149. package/dist/guest/desktop-native.d.ts +1 -1
  150. package/dist/guest/runtime.js +1 -1
  151. package/dist/index.d.ts +12 -10
  152. package/dist/index.js +8 -6
  153. package/dist/keys/key-resolution.d.ts +5 -5
  154. package/dist/keys/key-resolution.js +23 -23
  155. package/dist/lab/adapter-extension.d.ts +7 -7
  156. package/dist/lab/adapter-extension.js +13 -36
  157. package/dist/lab/adapter-scorer-loader.d.ts +7 -7
  158. package/dist/lab/adapter-scorer-loader.js +32 -32
  159. package/dist/lab/composition-rules.js +42 -42
  160. package/dist/lab/config.js +65 -25
  161. package/dist/lab/deprecated-fields.d.ts +10 -0
  162. package/dist/lab/deprecated-fields.js +24 -0
  163. package/dist/lab/device-presets.d.ts +8 -8
  164. package/dist/lab/device-presets.js +11 -11
  165. package/dist/lab/discover.d.ts +13 -9
  166. package/dist/lab/discover.js +117 -57
  167. package/dist/lab/doctor.d.ts +10 -3
  168. package/dist/lab/doctor.js +47 -9
  169. package/dist/lab/init-templates.js +147 -147
  170. package/dist/lab/init.d.ts +1 -1
  171. package/dist/lab/init.js +41 -9
  172. package/dist/lab/keys.d.ts +2 -0
  173. package/dist/lab/keys.js +42 -8
  174. package/dist/lab/lab-deps.d.ts +7 -12
  175. package/dist/lab/parse/actors.js +9 -9
  176. package/dist/lab/parse/comms.js +5 -5
  177. package/dist/lab/parse/execution.js +13 -13
  178. package/dist/lab/parse/study-v3.d.ts +23 -0
  179. package/dist/lab/parse/study-v3.js +259 -0
  180. package/dist/lab/parse/subject-state.d.ts +7 -7
  181. package/dist/lab/parse/subject-state.js +16 -16
  182. package/dist/lab/parse/subject.js +28 -28
  183. package/dist/lab/parse/values.d.ts +1 -1
  184. package/dist/lab/parse/values.js +3 -3
  185. package/dist/lab/persona-resolve.d.ts +4 -4
  186. package/dist/lab/persona-resolve.js +4 -4
  187. package/dist/lab/plan-participants.d.ts +2 -2
  188. package/dist/lab/plan-types.d.ts +4 -4
  189. package/dist/lab/plan.d.ts +1 -7
  190. package/dist/lab/plan.js +10 -41
  191. package/dist/lab/preflight-probes.js +11 -11
  192. package/dist/lab/preflight-result.d.ts +2 -1
  193. package/dist/lab/preflight-result.js +5 -4
  194. package/dist/lab/preflight.d.ts +7 -5
  195. package/dist/lab/preflight.js +5 -5
  196. package/dist/lab/routing.d.ts +31 -25
  197. package/dist/lab/routing.js +60 -29
  198. package/dist/lab/run-lab-events.d.ts +0 -3
  199. package/dist/lab/run-lab-events.js +0 -6
  200. package/dist/lab/run-lab-homes.d.ts +22 -10
  201. package/dist/lab/run-lab-homes.js +5 -1
  202. package/dist/lab/run-lab-options.d.ts +1 -1
  203. package/dist/lab/run-lab-options.js +16 -13
  204. package/dist/lab/summary.d.ts +7 -6
  205. package/dist/lab/summary.js +10 -10
  206. package/dist/lab/tasks.d.ts +15 -15
  207. package/dist/lab/tasks.js +14 -14
  208. package/dist/lab/types.d.ts +101 -95
  209. package/dist/lab/types.js +2 -0
  210. package/dist/lab/validation.d.ts +13 -13
  211. package/dist/lab/validation.js +44 -42
  212. package/dist/lab/warnings.d.ts +10 -0
  213. package/dist/lab/warnings.js +61 -33
  214. package/dist/library-aliases.d.ts +56 -0
  215. package/dist/library-aliases.js +22 -0
  216. package/dist/observer/artifact.d.ts +1 -1
  217. package/dist/observer/artifact.js +6 -6
  218. package/dist/observer/data.d.ts +6 -6
  219. package/dist/observer/data.js +5 -4
  220. package/dist/observer/exposure.d.ts +1 -1
  221. package/dist/observer/exposure.js +11 -11
  222. package/dist/observer/http.js +1 -1
  223. package/dist/observer/library.d.ts +1 -1
  224. package/dist/observer/library.js +4 -2
  225. package/dist/observer/listen.js +5 -4
  226. package/dist/observer/render.d.ts +1 -1
  227. package/dist/observer/render.js +10 -5
  228. package/dist/observer/run-routes.d.ts +3 -3
  229. package/dist/observer/run-routes.js +1 -1
  230. package/dist/observer/serve.js +7 -3
  231. package/dist/observer/tunnel.js +1 -1
  232. package/dist/observer-app.html +4 -4
  233. package/dist/routes/computer-use/bundle-parts.d.ts +9 -9
  234. package/dist/routes/computer-use/bundle-parts.js +15 -15
  235. package/dist/routes/computer-use/bundle.js +3 -3
  236. package/dist/routes/computer-use/e2b-desktop/comms.d.ts +1 -1
  237. package/dist/routes/computer-use/e2b-desktop/comms.js +14 -14
  238. package/dist/routes/computer-use/e2b-desktop/desktop.js +4 -4
  239. package/dist/routes/computer-use/e2b-desktop/fidelity.d.ts +4 -3
  240. package/dist/routes/computer-use/e2b-desktop/fidelity.js +11 -10
  241. package/dist/routes/computer-use/e2b-desktop/prepare.js +7 -7
  242. package/dist/routes/computer-use/e2b-desktop/start.js +9 -8
  243. package/dist/routes/computer-use/e2b-desktop/teardown.js +5 -4
  244. package/dist/routes/computer-use/external-comms.d.ts +1 -1
  245. package/dist/routes/computer-use/external-comms.js +1 -1
  246. package/dist/routes/computer-use/fanout-bundle.js +18 -18
  247. package/dist/routes/computer-use/fanout-records.js +10 -11
  248. package/dist/routes/computer-use/live-flush.d.ts +1 -1
  249. package/dist/routes/computer-use/live-flush.js +6 -6
  250. package/dist/routes/computer-use/live-phase.d.ts +4 -2
  251. package/dist/routes/computer-use/live-phase.js +6 -6
  252. package/dist/routes/computer-use/local-vm.js +1 -1
  253. package/dist/routes/computer-use/participant-desktop.d.ts +4 -4
  254. package/dist/routes/computer-use/participant-desktop.js +4 -4
  255. package/dist/routes/computer-use/participant-execution.d.ts +4 -4
  256. package/dist/routes/computer-use/participant-execution.js +9 -9
  257. package/dist/routes/computer-use/participant-model.d.ts +6 -0
  258. package/dist/routes/computer-use/participant-model.js +31 -13
  259. package/dist/routes/computer-use/participant-prompt.d.ts +11 -11
  260. package/dist/routes/computer-use/participant-prompt.js +6 -6
  261. package/dist/routes/computer-use/participant-runs.d.ts +7 -7
  262. package/dist/routes/computer-use/participant-runs.js +13 -13
  263. package/dist/routes/computer-use/plan.js +22 -22
  264. package/dist/routes/computer-use/preflight.js +21 -21
  265. package/dist/routes/computer-use/rerun-selection.js +1 -1
  266. package/dist/routes/computer-use/result.js +11 -11
  267. package/dist/routes/computer-use/route.d.ts +2 -1
  268. package/dist/routes/computer-use/route.js +15 -14
  269. package/dist/routes/computer-use/self-report.d.ts +7 -7
  270. package/dist/routes/computer-use/self-report.js +31 -31
  271. package/dist/routes/computer-use/setup.d.ts +1 -1
  272. package/dist/routes/computer-use/setup.js +17 -18
  273. package/dist/routes/computer-use/single-bundle.d.ts +7 -7
  274. package/dist/routes/computer-use/single-bundle.js +28 -30
  275. package/dist/routes/computer-use/subject-projection.d.ts +1 -1
  276. package/dist/routes/computer-use/subject-projection.js +6 -6
  277. package/dist/routes/computer-use/types.d.ts +40 -42
  278. package/dist/routes/computer-use/types.js +10 -10
  279. package/dist/routes/preview.d.ts +8 -2
  280. package/dist/routes/preview.js +8 -4
  281. package/dist/routes/scripted/bundle.d.ts +2 -2
  282. package/dist/routes/scripted/bundle.js +13 -12
  283. package/dist/routes/scripted/plan.d.ts +1 -1
  284. package/dist/routes/scripted/plan.js +9 -9
  285. package/dist/routes/scripted/result.js +5 -5
  286. package/dist/routes/scripted/route.js +5 -6
  287. package/dist/routes/scripted/scenario.js +2 -2
  288. package/dist/routes/scripted/setup.js +7 -7
  289. package/dist/routes/scripted/subject.js +1 -1
  290. package/dist/routes/scripted/surface-records.js +3 -3
  291. package/dist/routes/scripted/types.d.ts +3 -5
  292. package/dist/routes/scripted/types.js +1 -1
  293. package/dist/routes/shared-world/bundle-records.js +9 -8
  294. package/dist/routes/shared-world/bundle.d.ts +5 -5
  295. package/dist/routes/shared-world/bundle.js +40 -38
  296. package/dist/routes/shared-world/checkpoints.d.ts +1 -1
  297. package/dist/routes/shared-world/checkpoints.js +1 -1
  298. package/dist/routes/shared-world/comms.d.ts +5 -5
  299. package/dist/routes/shared-world/comms.js +14 -14
  300. package/dist/routes/shared-world/external-public.d.ts +4 -3
  301. package/dist/routes/shared-world/external-public.js +16 -12
  302. package/dist/routes/shared-world/handoff.d.ts +7 -6
  303. package/dist/routes/shared-world/handoff.js +65 -57
  304. package/dist/routes/shared-world/lobby-code.d.ts +2 -2
  305. package/dist/routes/shared-world/lobby-code.js +9 -9
  306. package/dist/routes/shared-world/participant-specs.d.ts +7 -6
  307. package/dist/routes/shared-world/participant-specs.js +31 -24
  308. package/dist/routes/shared-world/plan.js +7 -7
  309. package/dist/routes/shared-world/provenance.d.ts +2 -2
  310. package/dist/routes/shared-world/provenance.js +2 -2
  311. package/dist/routes/shared-world/provisioned.js +14 -13
  312. package/dist/routes/shared-world/result.js +20 -21
  313. package/dist/routes/shared-world/route.js +23 -22
  314. package/dist/routes/shared-world/setup.d.ts +3 -3
  315. package/dist/routes/shared-world/setup.js +4 -4
  316. package/dist/routes/shared-world/types.d.ts +30 -28
  317. package/dist/routes/shared-world/types.js +4 -4
  318. package/dist/routes/terminal/adapter.d.ts +3 -3
  319. package/dist/routes/terminal/adapter.js +5 -5
  320. package/dist/routes/terminal/bundle.d.ts +3 -3
  321. package/dist/routes/terminal/bundle.js +22 -21
  322. package/dist/routes/terminal/credentials.d.ts +4 -4
  323. package/dist/routes/terminal/credentials.js +7 -7
  324. package/dist/routes/terminal/dry-run.js +4 -4
  325. package/dist/routes/terminal/encoding.js +2 -2
  326. package/dist/routes/terminal/ledger.d.ts +3 -3
  327. package/dist/routes/terminal/ledger.js +14 -14
  328. package/dist/routes/terminal/live-finish.js +3 -3
  329. package/dist/routes/terminal/live-sandbox.js +12 -13
  330. package/dist/routes/terminal/persona.js +1 -1
  331. package/dist/routes/terminal/plan.js +11 -11
  332. package/dist/routes/terminal/recorder.js +2 -2
  333. package/dist/routes/terminal/result.js +8 -8
  334. package/dist/routes/terminal/route.js +6 -7
  335. package/dist/routes/terminal/sandbox.d.ts +1 -1
  336. package/dist/routes/terminal/sandbox.js +1 -1
  337. package/dist/routes/terminal/session.js +8 -7
  338. package/dist/routes/terminal/token-usage.d.ts +1 -1
  339. package/dist/routes/terminal/token-usage.js +7 -8
  340. package/dist/routes/terminal/trace.d.ts +2 -2
  341. package/dist/routes/terminal/trace.js +2 -2
  342. package/dist/routes/terminal/types.d.ts +26 -28
  343. package/dist/routes/terminal/types.js +3 -3
  344. package/dist/run/bundle-shape.js +3 -3
  345. package/dist/run/bundle.d.ts +46 -46
  346. package/dist/run/bundle.js +2 -1
  347. package/dist/run/cost-summary.js +3 -3
  348. package/dist/run/costs.js +7 -1
  349. package/dist/run/detail.d.ts +6 -6
  350. package/dist/run/detail.js +4 -4
  351. package/dist/run/dry-run-inputs.js +1 -1
  352. package/dist/run/dry-run.js +18 -18
  353. package/dist/run/judge.d.ts +19 -13
  354. package/dist/run/judge.js +18 -8
  355. package/dist/run/paths.d.ts +1 -1
  356. package/dist/run/paths.js +1 -1
  357. package/dist/run/pricing.d.ts +11 -6
  358. package/dist/run/pricing.js +20 -13
  359. package/dist/run/projection.d.ts +14 -14
  360. package/dist/run/projection.js +15 -15
  361. package/dist/run/reclaim.js +2 -2
  362. package/dist/run/results.d.ts +5 -5
  363. package/dist/run/run-index.d.ts +8 -6
  364. package/dist/run/run-index.js +15 -16
  365. package/dist/run/run-not-found.d.ts +5 -0
  366. package/dist/run/run-not-found.js +15 -0
  367. package/dist/run/run.d.ts +5 -4
  368. package/dist/run/run.js +1 -1
  369. package/dist/run/sandbox-receipts.d.ts +2 -2
  370. package/dist/run/sandbox-receipts.js +3 -3
  371. package/dist/run/shared-world-evidence.d.ts +36 -35
  372. package/dist/run/shared-world-evidence.js +4 -4
  373. package/dist/run/shared-world-shape.d.ts +1 -1
  374. package/dist/run/shared-world-shape.js +6 -6
  375. package/dist/run/stats.d.ts +12 -6
  376. package/dist/run/stats.js +116 -58
  377. package/dist/run/status.d.ts +8 -29
  378. package/dist/run/status.js +5 -27
  379. package/dist/run/stored-runs.js +2 -1
  380. package/dist/run/stream-shape.js +1 -1
  381. package/dist/run/streams.d.ts +6 -6
  382. package/dist/run/study-provenance.d.ts +47 -0
  383. package/dist/run/study-provenance.js +63 -0
  384. package/dist/run/study-result.d.ts +16 -0
  385. package/dist/run/study-result.js +9 -0
  386. package/dist/run/text.d.ts +2 -0
  387. package/dist/run/text.js +4 -0
  388. package/dist/run-lab.d.ts +7 -7
  389. package/dist/run-lab.js +1 -3
  390. package/dist/study/convert.d.ts +33 -0
  391. package/dist/study/convert.js +432 -0
  392. package/dist/study/field-paths.d.ts +12 -0
  393. package/dist/study/field-paths.js +126 -0
  394. package/dist/study/files.d.ts +26 -0
  395. package/dist/study/files.js +43 -0
  396. package/dist/study/migrate.d.ts +48 -0
  397. package/dist/study/migrate.js +408 -0
  398. package/dist/study/owned-files.d.ts +38 -0
  399. package/dist/study/owned-files.js +182 -0
  400. package/dist/subject/clone.d.ts +1 -1
  401. package/dist/subject/desktop-cli.d.ts +1 -1
  402. package/dist/subject/desktop-cli.js +2 -2
  403. package/dist/subject/local-tree-archive.js +1 -1
  404. package/dist/subject/local-tree.d.ts +1 -1
  405. package/dist/subject/node-bootstrap.js +1 -1
  406. package/dist/subject/runtime.js +8 -9
  407. package/dist/subject/serve.d.ts +1 -1
  408. package/dist/subject/serve.js +3 -3
  409. package/dist/subject/state.d.ts +1 -1
  410. package/dist/subject/steps.d.ts +5 -5
  411. package/dist/subject/steps.js +4 -4
  412. package/dist/substrates/command-failure.d.ts +3 -3
  413. package/dist/substrates/command-failure.js +3 -3
  414. package/dist/substrates/detached.d.ts +6 -6
  415. package/dist/substrates/detached.js +7 -7
  416. package/dist/substrates/e2b/cdp-probe.d.ts +9 -9
  417. package/dist/substrates/e2b/cdp-probe.js +14 -17
  418. package/dist/substrates/e2b/desktop-browser.d.ts +2 -2
  419. package/dist/substrates/e2b/desktop-browser.js +8 -8
  420. package/dist/substrates/e2b/desktop-cdp.d.ts +9 -9
  421. package/dist/substrates/e2b/desktop-cdp.js +8 -8
  422. package/dist/substrates/e2b/desktop-executor.d.ts +1 -1
  423. package/dist/substrates/e2b/desktop-executor.js +1 -1
  424. package/dist/substrates/e2b/desktop-geometry.d.ts +3 -3
  425. package/dist/substrates/e2b/desktop-geometry.js +7 -7
  426. package/dist/substrates/e2b/desktop-media.d.ts +1 -1
  427. package/dist/substrates/e2b/desktop-media.js +1 -1
  428. package/dist/substrates/e2b/desktop-screenshot-cleanup.d.ts +3 -3
  429. package/dist/substrates/e2b/desktop-screenshot-cleanup.js +3 -3
  430. package/dist/substrates/e2b/sandbox.d.ts +1 -1
  431. package/dist/substrates/e2b/sandbox.js +1 -1
  432. package/dist/substrates/e2b/sdk.d.ts +11 -11
  433. package/dist/substrates/e2b/sdk.js +6 -6
  434. package/dist/tui/actions.d.ts +1 -1
  435. package/dist/tui/actions.js +13 -13
  436. package/dist/tui/contract.d.ts +16 -16
  437. package/dist/tui/contract.js +10 -10
  438. package/dist/tui/launch.d.ts +4 -4
  439. package/dist/tui/launch.js +18 -19
  440. package/dist/tui/project.js +5 -5
  441. package/dist/tui-app.js +82 -82
  442. package/dist/verify/actor.d.ts +8 -8
  443. package/dist/verify/actor.js +36 -36
  444. package/dist/verify/artifacts.d.ts +2 -2
  445. package/dist/verify/artifacts.js +3 -3
  446. package/dist/verify/context-growth.d.ts +9 -0
  447. package/dist/verify/context-growth.js +62 -0
  448. package/dist/verify/costs.d.ts +6 -6
  449. package/dist/verify/costs.js +8 -8
  450. package/dist/verify/shared-world-concurrent.d.ts +2 -2
  451. package/dist/verify/shared-world-concurrent.js +79 -76
  452. package/dist/verify/shared-world.d.ts +2 -2
  453. package/dist/verify/shared-world.js +16 -16
  454. package/dist/verify/subject.d.ts +4 -4
  455. package/dist/verify/subject.js +18 -18
  456. package/dist/verify/verify.js +92 -43
  457. package/docs/README.md +25 -32
  458. package/docs/architecture/actor-contract.md +56 -87
  459. package/docs/architecture/desktop-sessions.md +7 -7
  460. package/docs/architecture/external-public-shared-world.md +61 -59
  461. package/docs/architecture/github-feedback-loop.md +1 -4
  462. package/docs/architecture/guest-desktop.md +2 -2
  463. package/docs/architecture/observer.md +34 -34
  464. package/docs/architecture/restricted-codex-analysis.md +34 -28
  465. package/docs/architecture/serve.md +34 -37
  466. package/docs/architecture/state-driven-executor.md +55 -58
  467. package/docs/architecture/task-protocol-support.md +5 -5
  468. package/docs/architecture/terminal-product-route.md +67 -107
  469. package/docs/contracts/README.md +24 -0
  470. package/docs/contracts/adapter-admission.md +3 -3
  471. package/docs/contracts/adapter-fixtures.md +2 -5
  472. package/docs/contracts/core.md +1 -3
  473. package/docs/contracts/errors.md +62 -0
  474. package/docs/contracts/feedback.md +8 -9
  475. package/docs/contracts/policy.md +7 -12
  476. package/docs/contracts/run-bundle.md +45 -44
  477. package/docs/contracts/schemas.md +421 -362
  478. package/docs/contracts/study-analysis.md +3 -3
  479. package/docs/contracts/study-costs.md +11 -7
  480. package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +1 -1
  481. package/docs/decisions/0002-observer-is-one-self-contained-file.md +2 -2
  482. package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +3 -3
  483. package/docs/principles/actor-fidelity.md +4 -4
  484. package/docs/principles/engineering.md +3 -3
  485. package/docs/principles/invariants-and-defaults.md +38 -38
  486. package/docs/principles/self-driving-harness.md +1 -5
  487. package/docs/principles/three-roles.md +1 -1
  488. package/docs/product/automatic-analysis.md +7 -7
  489. package/examples/participant/README.md +5 -5
  490. package/examples/participant/run.mjs +11 -10
  491. package/examples/scorer/README.md +3 -3
  492. package/examples/scorer/run.mjs +11 -12
  493. package/package.json +16 -17
  494. package/skills/humanish/SKILL.md +60 -58
  495. package/skills/humanish/agents/openai.yaml +1 -1
  496. package/AGENTS.md +0 -99
  497. package/CONTRIBUTING.md +0 -148
  498. package/dist/actors/codex/qualified-versions.d.ts +0 -72
  499. package/dist/actors/codex/qualified-versions.js +0 -132
  500. package/dist/cli/commands/lab.d.ts +0 -3
  501. package/dist/cli/commands/lab.js +0 -199
  502. package/dist/cli/renamed-options.d.ts +0 -11
  503. package/dist/cli/renamed-options.js +0 -18
  504. package/dist/observer/static.d.ts +0 -39
  505. package/dist/observer/static.js +0 -369
  506. package/docs/architecture/comms-inbox.md +0 -122
  507. package/docs/architecture/desktop-recording.md +0 -80
  508. package/docs/architecture/local-browser-runtime.md +0 -178
  509. package/docs/architecture/observer-review.md +0 -144
  510. package/docs/architecture/participant-media.md +0 -90
  511. package/docs/architecture/project-layout.md +0 -62
  512. package/docs/architecture/real-email-receiving.md +0 -136
  513. package/docs/assets/humanish-drawdb-hero.png +0 -0
  514. package/docs/assets/humanish-observer-hero.png +0 -0
  515. package/docs/goals/current.md +0 -207
  516. package/docs/product/lobby-trivia-3player-external-public.md +0 -77
  517. package/docs/product/open-source-install-experience.md +0 -240
  518. package/docs/ramp/README.md +0 -295
  519. package/docs/release/open-source-readiness.md +0 -222
  520. package/docs/release/public-readiness-standard.md +0 -210
  521. package/docs/roadmap/world-class-open-source-v0.md +0 -311
package/dist/lab/keys.js CHANGED
@@ -93,7 +93,10 @@ const ACTOR = {
93
93
  count: true,
94
94
  lanes: PARTICIPANT_ENTRY,
95
95
  // Roster groups are participants with a count; the parser expands them into `lanes[]`.
96
- roster: { ...PARTICIPANT_ENTRY, count: true },
96
+ roster: {
97
+ ...PARTICIPANT_ENTRY,
98
+ count: true,
99
+ },
97
100
  persona: true,
98
101
  mission: true,
99
102
  model: true,
@@ -200,14 +203,45 @@ const LAB_KEYS = {
200
203
  defaults: { open: true },
201
204
  comms: { email: EMAIL },
202
205
  };
206
+ function without(shape, ...keys) {
207
+ return Object.fromEntries(Object.entries(shape).filter(([key]) => !keys.includes(key)));
208
+ }
209
+ // humanish.study.v3. The route is declared, so `subject.topology` goes. The participant keys leave
210
+ // the actor for `participants`, and `caps` is one top-level block. Top-level `personas` and
211
+ // `scenario.inline` are read by no route, and `scenario` is a string.
212
+ const STUDY_KEYS = {
213
+ schema: true,
214
+ id: true,
215
+ title: true,
216
+ description: true,
217
+ route: true,
218
+ mode: true,
219
+ subject: without(SUBJECT, "topology"),
220
+ actor: without(ACTOR, "count", "lanes", "roster", "laneFocus"),
221
+ // A count, `{ count, instruction }` or a list of entries. studyToV2 checks which form the route
222
+ // takes; an entry with a count is a group.
223
+ participants: { ...PARTICIPANT_ENTRY, count: true },
224
+ surfaces: true,
225
+ caps: CAPS,
226
+ execution: without(EXECUTION, "caps"),
227
+ scenario: true,
228
+ policies: LAB_KEYS.policies,
229
+ review: LAB_KEYS.review,
230
+ defaults: LAB_KEYS.defaults,
231
+ comms: LAB_KEYS.comms,
232
+ };
203
233
  /** The first key in `raw` that LAB_KEYS does not list, as an error message; undefined if none. */
204
234
  export function findUnknownLabKey(raw) {
205
- return walk(raw, LAB_KEYS, "");
235
+ return walk(raw, LAB_KEYS, "", "lab");
236
+ }
237
+ /** The first key in a humanish.study.v3 document that the format does not have; undefined if none. */
238
+ export function findUnknownStudyKey(raw) {
239
+ return walk(raw, STUDY_KEYS, "", "study");
206
240
  }
207
- function walk(value, shape, path) {
241
+ function walk(value, shape, path, noun) {
208
242
  if (Array.isArray(value)) {
209
243
  for (const [index, item] of value.entries()) {
210
- const found = walk(item, shape, `${path}[${index}]`);
244
+ const found = walk(item, shape, `${path}[${index}]`, noun);
211
245
  if (found)
212
246
  return found;
213
247
  }
@@ -220,23 +254,23 @@ function walk(value, shape, path) {
220
254
  // Object.hasOwn: a key like `constructor` must not match Object.prototype.
221
255
  const unknown = Object.keys(record).filter((key) => !Object.hasOwn(shape, key));
222
256
  if (unknown.length > 0)
223
- return unknownKeysMessage(path, unknown, known);
257
+ return unknownKeysMessage(path, unknown, known, noun);
224
258
  for (const [key, child] of Object.entries(record)) {
225
259
  const expected = shape[key];
226
260
  if (expected !== true && expected !== undefined) {
227
- const found = walk(child, expected, path ? `${path}.${key}` : key);
261
+ const found = walk(child, expected, path ? `${path}.${key}` : key, noun);
228
262
  if (found)
229
263
  return found;
230
264
  }
231
265
  }
232
266
  return undefined;
233
267
  }
234
- function unknownKeysMessage(path, unknown, known) {
268
+ function unknownKeysMessage(path, unknown, known, noun) {
235
269
  const named = unknown.map((key) => {
236
270
  const suggestion = closest(key, known);
237
271
  return suggestion ? `${key} (did you mean \`${suggestion}\`?)` : key;
238
272
  });
239
- return `Unknown lab field(s)${path ? ` in \`${path}\`` : ""}: ${named.join(", ")}. Known fields: ${known.join(", ")}.`;
273
+ return `Unknown ${noun} field(s)${path ? ` in \`${path}\`` : ""}: ${named.join(", ")}. Known fields: ${known.join(", ")}.`;
240
274
  }
241
275
  // The known key within edit distance 2, or one that differs only in case.
242
276
  function closest(key, known) {
@@ -9,12 +9,7 @@ import type { DetachedTimers } from "../substrates/detached.js";
9
9
  import type { E2BDesktopModule } from "../substrates/e2b/sdk.js";
10
10
  import type { LocalTreeArchive } from "../subject/local-tree-archive.js";
11
11
  import type { SubjectPhaseEvent } from "../subject/steps.js";
12
- /** The participant a computer-use subject phase belongs to, with the route's field names. */
13
- export interface PhaseParticipant {
14
- readonly laneId: string;
15
- readonly laneIndex: number;
16
- readonly laneCount: number;
17
- }
12
+ import type { ParticipantRef } from "./run-lab-events.js";
18
13
  export interface LabDeps {
19
14
  /** Loads the E2B SDK. Defaults to loadE2BDesktopModule. */
20
15
  readonly desktopModule?: () => Promise<E2BDesktopModule>;
@@ -29,10 +24,10 @@ export interface LabDeps {
29
24
  * computer-use phase names its participant; a shared-world plane's names none. onEvent still
30
25
  * receives every phase.
31
26
  */
32
- readonly subjectPhaseSink?: (event: SubjectPhaseEvent, participant?: PhaseParticipant) => void;
27
+ readonly subjectPhaseSink?: (event: SubjectPhaseEvent, participant?: ParticipantRef) => void;
33
28
  /**
34
- * Shared world: runs each seat's computer-use session in place of the actor's own. The planner
35
- * reads whether it is set: a custom runner cannot enforce actors[0].maxOutputTokens.
29
+ * Shared world: runs each participant's computer-use session in place of the actor's own. The
30
+ * planner reads whether it is set: a custom runner cannot enforce actors[0].maxOutputTokens.
36
31
  */
37
32
  readonly runSession?: (options: CuaActorSessionOptions) => Promise<CuaLoopResult>;
38
33
  /**
@@ -50,13 +45,13 @@ export interface LabDeps {
50
45
  /** Shared world: the background stateSeries prober's cadence in milliseconds. Defaults to 1000. */
51
46
  readonly proberCadenceMs?: number;
52
47
  /**
53
- * Shared world, external-public plane: how long the host seat has to surface a /lobby/CODE URL
48
+ * Shared world, external-public plane: how long the host has to surface a /lobby/CODE URL
54
49
  * before the run fails closed. Defaults to 120000, capped by execution.timeoutMs.
55
50
  */
56
51
  readonly handoffDeadlineMs?: number;
57
52
  /**
58
- * Shared world, external-public plane: reads a /lobby/CODE off a seat's screenshot. Defaults to
59
- * the single-frame OpenAI read.
53
+ * Shared world, external-public plane: reads a /lobby/CODE off a participant's screenshot.
54
+ * Defaults to the single-frame OpenAI read.
60
55
  */
61
56
  readonly readLobbyCodeFromFrame?: (frame: Buffer, apiKey: string) => Promise<string | undefined>;
62
57
  /**
@@ -22,8 +22,8 @@ export function focusOf(actor) {
22
22
  // Actor ids humanish no longer registers. Rejecting them at parse keeps a lab that names one
23
23
  // from running on a route that ignores actors[0].type, such as a this-repo dry run.
24
24
  const REMOVED_ACTOR_TYPES = new Set(["pi-agent-core", "claude-agent-sdk"]);
25
- // Only actors on a lane a route dispatches. codex-app-server is registered but declares only the
26
- // "code" lane, which no lab route runs, so naming it would lead to the same dead end.
25
+ // Only actors of a run kind a route dispatches. codex-app-server is registered but declares only
26
+ // the "code" run kind, which no study route runs, so naming it would lead to the same dead end.
27
27
  function routableActorTypes() {
28
28
  return [
29
29
  ...registeredComputerUseActors(),
@@ -57,10 +57,10 @@ export function parseActors(raw) {
57
57
  if (count !== undefined)
58
58
  actor.count = count;
59
59
  if (entry.lanes !== undefined && entry.roster !== undefined) {
60
- return invalid(`actors[${index}].lanes and actors[${index}].roster are mutually exclusive: declare participants in \`lanes\` OR in compact \`roster\` groups, not both.`);
60
+ return invalid(`Set either actors[${index}].lanes or actors[${index}].roster, not both: list participants one by one in \`lanes\`, or in groups in \`roster\`.`);
61
61
  }
62
62
  if (entry.roster !== undefined && count !== undefined) {
63
- return invalid(`actors[${index}].roster and actors[${index}].count are mutually exclusive — use compact differentiated groups OR a homogeneous count, not both.`);
63
+ return invalid(`Set either actors[${index}].roster or actors[${index}].count, not both: \`roster\` declares groups of distinct participants, and \`count\` declares identical ones.`);
64
64
  }
65
65
  if (entry.roster !== undefined && entry.laneFocus !== undefined) {
66
66
  return invalid(`actors[${index}].roster and actors[${index}].laneFocus are mutually exclusive: a roster group's instruction is each participant's steer.`);
@@ -160,7 +160,7 @@ function parseRosterGroups(raw, actorIndex) {
160
160
  return invalid(`actors[${actorIndex}].roster[${groupIndex}].id is required and must be a public-safe token matching ${PARTICIPANT_ID_PATTERN}.`);
161
161
  }
162
162
  if (!PARTICIPANT_ID_PATTERN.test(groupId) || groupId.length > PARTICIPANT_ID_MAX_CHARS - 3) {
163
- return invalid(`actors[${actorIndex}].roster[${groupIndex}].id must be a public-safe token matching ${PARTICIPANT_ID_PATTERN} and at most ${PARTICIPANT_ID_MAX_CHARS - 3} chars (generated participant ids use <id>-NN); got "${groupId}".`);
163
+ return invalid(`actors[${actorIndex}].roster[${groupIndex}].id must match ${PARTICIPANT_ID_PATTERN} and be at most ${PARTICIPANT_ID_MAX_CHARS - 3} characters, because each generated participant id adds a suffix like "-01"; "${groupId}" is not.`);
164
164
  }
165
165
  if (seenGroupIds.has(groupId)) {
166
166
  return invalid(`actors[${actorIndex}].roster group ids must be unique (duplicate "${groupId}").`);
@@ -272,7 +272,7 @@ function parseParticipantEntries(raw, actorIndex) {
272
272
  const entryPath = str(entry.entry);
273
273
  if (entryPath !== undefined)
274
274
  parsedEntry.entry = entryPath;
275
- // `host` marks the designated host seat on the external-public shared-world route; the
275
+ // `host` marks the designated host on the external-public shared-world route; the
276
276
  // exactly-one-host check runs in externalPublicSharedWorldValidationReason (route context).
277
277
  if (entry.host !== undefined) {
278
278
  if (typeof entry.host !== "boolean") {
@@ -287,7 +287,7 @@ function parseParticipantEntries(raw, actorIndex) {
287
287
  }
288
288
  /**
289
289
  * The researcher's protocol. Each task carries what the participant is asked to do and, optionally,
290
- * the observation that proves it happened — reusing `stopWhen`, so a criterion is exactly as
290
+ * the observation that proves it happened, reusing `stopWhen`, so a criterion is exactly as
291
291
  * expressive as a stop condition and an author who knows one knows the other.
292
292
  *
293
293
  * A task with no `success` is allowed on purpose: some things you ask a participant to do (think
@@ -314,7 +314,7 @@ function parseTasks(raw, field) {
314
314
  seen.add(id);
315
315
  const goal = str(entry.goal);
316
316
  if (goal === undefined) {
317
- return invalid(`\`${field}[${index}].goal\` is required — what the PARTICIPANT is asked to do, in their language.`);
317
+ return invalid(`\`${field}[${index}].goal\` is required: what the participant is asked to do, in their own words.`);
318
318
  }
319
319
  const successResult = parseStopWhen(entry.success, `${field}[${index}].success`);
320
320
  if (!successResult.ok)
@@ -327,7 +327,7 @@ function parseTasks(raw, field) {
327
327
  }
328
328
  return { ok: true, value: tasks };
329
329
  }
330
- /** The bounds a dwell window (#510) must sit inside: at least one frame, at most an hour. */
330
+ /** The bounds a dwell window must sit inside: at least one frame, at most an hour. */
331
331
  const DWELL_MIN_MS = 1_000;
332
332
  const DWELL_MAX_MS = 3_600_000;
333
333
  const DWELL_DEFAULT_EVERY_MS = 10_000;
@@ -30,8 +30,8 @@ function parseCommsEmail(raw) {
30
30
  if (raw.kind !== undefined && raw.kind !== "fake") {
31
31
  return invalid("`comms.email.kind` must be `fake`.");
32
32
  }
33
- // external ingress (#328): the ADOPTER runs the catch, so there is no subject env for humanish to
34
- // inject and `injectEnv` becomes meaningless rather than merely unused — the operator points their
33
+ // external ingress: the adopter runs the catch, so there is no subject env for humanish to
34
+ // inject and `injectEnv` has nothing to name: the operator points their
35
35
  // own app at their own catch. Parse it first so the injectEnv requirement can key off it.
36
36
  const external = parseExternalCatch(raw.external);
37
37
  if (!external.ok)
@@ -127,7 +127,7 @@ function parseExternalCatch(raw) {
127
127
  return invalid("`comms.email.external` must be a mapping.");
128
128
  const catchBaseUrl = str(raw.catchBaseUrl);
129
129
  if (catchBaseUrl === undefined) {
130
- return invalid("`comms.email.external.catchBaseUrl` is required — the base URL of the catch YOU run (humanish reads its GET /deliveries and your app POSTs its sends to it).");
130
+ return invalid("`comms.email.external.catchBaseUrl` is required: the base URL of the catch you run. Your app sends its mail to it, and humanish reads GET /deliveries from it.");
131
131
  }
132
132
  for (const [field, value] of [
133
133
  ["catchBaseUrl", catchBaseUrl],
@@ -148,7 +148,7 @@ function parseExternalCatch(raw) {
148
148
  }
149
149
  const authTokenEnv = str(raw.authTokenEnv);
150
150
  if (authTokenEnv !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(authTokenEnv)) {
151
- return invalid(`\`comms.email.external.authTokenEnv\` must be a valid env var NAME (got "${authTokenEnv}"); the value is read at runtime and never persisted.`);
151
+ return invalid(`\`comms.email.external.authTokenEnv\` must be an environment variable name, and "${authTokenEnv}" is not one. Its value is read when the run starts and never stored.`);
152
152
  }
153
153
  const inboxBaseUrl = str(raw.inboxBaseUrl);
154
154
  return {
@@ -175,7 +175,7 @@ function parseSmtp(raw) {
175
175
  const hostEnv = envName(raw.hostEnv, "hostEnv");
176
176
  const portEnv = envName(raw.portEnv, "portEnv");
177
177
  if (hostEnv === undefined || portEnv === undefined) {
178
- return invalid("`comms.email.smtp` needs both `hostEnv` and `portEnv` — the subject-env vars your app reads for its SMTP host and port. The harness sets them to its own loopback listener.");
178
+ return invalid("`comms.email.smtp` needs both `hostEnv` and `portEnv`: the environment variables your app reads for its SMTP host and port. humanish sets them to its own loopback listener.");
179
179
  }
180
180
  if (hostEnv.startsWith("__invalid__") || portEnv.startsWith("__invalid__")) {
181
181
  return invalid("`comms.email.smtp.hostEnv` and `portEnv` must be valid env var names.");
@@ -33,7 +33,7 @@ export function parseExecution(raw) {
33
33
  }
34
34
  if (desktopResult.value)
35
35
  execution.desktop = desktopResult.value;
36
- // Reuse the terminal route's caps parser (same shape, not a fork) — a malformed budget is a hard
36
+ // Reuse the terminal route's caps parser (same shape, not a fork); a malformed budget is a hard
37
37
  // error, never silently dropped (a cap that silently does nothing would be a safety lie).
38
38
  const capsResult = parseCaps(raw.caps);
39
39
  if (!capsResult.ok) {
@@ -90,9 +90,9 @@ function parseTerminal(raw) {
90
90
  const transport = str(raw.transport);
91
91
  if (transport !== "exec-stream") {
92
92
  // "pty" is deliberately rejected: stdin is disabled, so the capture is a non-interactive
93
- // exec stream — an interactive-PTY label would overstate the mechanism (invariant 6 + the
94
- // goal packet's PTY ruling). True duplex PTY does not ship.
95
- return invalid("`execution.terminal.transport` must be exec-stream — captured non-interactive exec output (stdin disabled) is not an interactive PTY; true duplex PTY transport is not supported.");
93
+ // exec stream, and an interactive-PTY label would overstate the mechanism. True duplex PTY
94
+ // does not ship.
95
+ return invalid("`execution.terminal.transport` must be exec-stream: the route captures the agent's output with stdin closed, and an interactive terminal (pty) is not supported.");
96
96
  }
97
97
  terminal.transport = transport;
98
98
  }
@@ -103,9 +103,9 @@ function parseTerminal(raw) {
103
103
  }
104
104
  if (stdin === "sent") {
105
105
  // Assisted input is forbidden until the interventions ledger + comparability flag + verify
106
- // check exist (safety contract item 7) — shipping it now would let an assisted run pose as
107
- // autonomous green proof.
108
- return invalid("`execution.terminal.stdin: sent` (assisted input) is not supported — the current route cannot capture assisted input with a non-comparable marker. stdin is disabled by default.");
106
+ // check exist (the terminal route's stdin rule); shipping it now would let an assisted run
107
+ // pose as autonomous green proof.
108
+ return invalid("`execution.terminal.stdin: sent` (assisted input) is not supported: the route cannot yet mark a run that received typed help as different from an unassisted one. Remove the setting; stdin stays closed.");
109
109
  }
110
110
  terminal.stdin = stdin;
111
111
  }
@@ -153,13 +153,13 @@ function parseDesktop(raw) {
153
153
  }
154
154
  desktop.browser = browser;
155
155
  }
156
- // A custom E2B desktop template NAME or ID. Trimmed non-empty when present; deliberately NOT
157
- // allowlisted (any string is a valid template name/id — over-restricting would reject real
156
+ // A custom E2B desktop template name or ID. Trimmed non-empty when present; deliberately not
157
+ // allowlisted (any string is a valid template name/id; over-restricting would reject real
158
158
  // adopter images). An explicitly-set but blank/whitespace value is a mistake, not a template.
159
159
  if (raw.template !== undefined) {
160
160
  const template = str(raw.template);
161
161
  if (template === undefined) {
162
- return invalid("`execution.desktop.template` must be a non-empty E2B desktop template NAME or ID when set (any string is accepted; there is no allowlist).");
162
+ return invalid("`execution.desktop.template` must be a non-empty E2B desktop template name or ID when it is set. Any name is accepted.");
163
163
  }
164
164
  desktop.template = template;
165
165
  }
@@ -247,8 +247,8 @@ export function parseScenario(raw) {
247
247
  }
248
248
  /**
249
249
  * Parse `scenario.caps`. Returns a parse failure on a malformed value rather than silently
250
- * dropping a budget declaration (a cap that silently does nothing would be a safety lie —
251
- * invariant 6). Each cap must be a non-negative finite number.
250
+ * dropping a budget declaration (a cap that silently does nothing would claim protection it
251
+ * does not give). Each cap must be a non-negative finite number.
252
252
  */
253
253
  function parseCaps(raw) {
254
254
  if (raw === undefined) {
@@ -339,7 +339,7 @@ function parseReviewScorer(raw) {
339
339
  return invalid("`review.scorer` must be a mapping with a `ref` path (e.g. { ref: scorers/product.mjs }).");
340
340
  const unknownKeys = Object.keys(raw).filter((key) => key !== "ref");
341
341
  if (unknownKeys.length > 0) {
342
- return invalid(`Unknown \`review.scorer\` field(s): ${unknownKeys.join(", ")}. review.scorer accepts only \`ref\` (a repo-relative scorer-module path).`);
342
+ return invalid(`\`review.scorer\` accepts only \`ref\` (a scorer module path relative to the project), and these fields are unknown: ${unknownKeys.join(", ")}.`);
343
343
  }
344
344
  const ref = str(raw.ref);
345
345
  if (!ref)
@@ -0,0 +1,23 @@
1
+ import { type LabConfigParseFailure } from "../types.js";
2
+ declare const ROUTES: readonly ["preview", "computer-use", "shared-world", "terminal", "scripted"];
3
+ /** A route a study declares. */
4
+ export type StudyRoute = (typeof ROUTES)[number];
5
+ export interface StudyDocument {
6
+ readonly route: StudyRoute;
7
+ /** The same document in the humanish.lab.v2 spelling. */
8
+ readonly v2: Record<string, unknown>;
9
+ /** For each expanded participant, the index of the `participants` entry it came from. */
10
+ readonly participantSource: readonly number[];
11
+ }
12
+ type Parsed<T> = {
13
+ ok: true;
14
+ value: T;
15
+ } | LabConfigParseFailure;
16
+ /** Rewrite a humanish.study.v3 document into humanish.lab.v2, or refuse it. */
17
+ export declare function studyToV2(raw: Record<string, unknown>): Parsed<StudyDocument>;
18
+ /**
19
+ * Spell a v2 parser message the way the study file is written: `actors[0].lanes[3]` becomes the
20
+ * `participants` entry it came from, `execution.caps` and `scenario.caps` become `caps`, and so on.
21
+ */
22
+ export declare function studySpelling(message: string, route: StudyRoute, participantSource: readonly number[]): string;
23
+ export {};
@@ -0,0 +1,259 @@
1
+ // humanish.study.v3, the study format. A study declares its route, one `actor`, its `participants`
2
+ // and one `caps` block. studyToV2 rewrites a v3 document into the v2 spelling, so both formats run
3
+ // through one parser and parse into the same LabConfig. parseLabConfig then checks the declared
4
+ // route against the one the config takes, and refuses any field that route does not read.
5
+ import { isRecord } from "../../run/type-guards.js";
6
+ import { findUnknownStudyKey } from "../keys.js";
7
+ import { LAB_CONFIG_SCHEMA } from "../types.js";
8
+ import { PARTICIPANT_ID_MAX_CHARS, PARTICIPANT_ID_PATTERN } from "./actors.js";
9
+ import { invalid, posInt } from "./values.js";
10
+ // The values `route:` takes: every route routeOf can return.
11
+ const ROUTES = [
12
+ "preview",
13
+ "computer-use",
14
+ "shared-world",
15
+ "terminal",
16
+ "scripted",
17
+ ];
18
+ // The caps keys each route reads. Computer use and shared world stop on estimated model spend;
19
+ // the terminal route checks its spend ledger, product jobs and the command deadline.
20
+ const CAPS_KEYS = {
21
+ preview: [],
22
+ "computer-use": ["maxUsd", "maxTotalUsd"],
23
+ "shared-world": ["maxUsd", "maxTotalUsd"],
24
+ terminal: ["maxUsd", "maxJobs", "maxMinutes"],
25
+ scripted: [],
26
+ };
27
+ /** Rewrite a humanish.study.v3 document into humanish.lab.v2, or refuse it. */
28
+ export function studyToV2(raw) {
29
+ const moved = movedKeyReason(raw);
30
+ if (moved)
31
+ return invalid(moved);
32
+ const unknownKey = findUnknownStudyKey(raw);
33
+ if (unknownKey)
34
+ return invalid(unknownKey);
35
+ const route = ROUTES.find((name) => name === raw.route);
36
+ if (route === undefined) {
37
+ return invalid(`A study needs \`route:\`, one of ${ROUTES.join(", ")}.`);
38
+ }
39
+ if (raw.mode !== undefined && raw.mode !== "dry-run" && raw.mode !== "live") {
40
+ return invalid("`mode` must be `dry-run` or `live`. Leave it out for a dry run.");
41
+ }
42
+ if (!isRecord(raw.actor)) {
43
+ return invalid("A study needs `actor:`, an object with at least `type`.");
44
+ }
45
+ if (raw.scenario !== undefined && typeof raw.scenario !== "string") {
46
+ return invalid("`scenario` is a scenario id or a path to one, as a string.");
47
+ }
48
+ const participants = participantsOf(route, raw.participants);
49
+ if (!participants.ok)
50
+ return participants;
51
+ const surfaces = surfaceCount(route, raw.surfaces);
52
+ if (!surfaces.ok)
53
+ return surfaces;
54
+ const capsReason = capsKeysReason(route, raw.caps);
55
+ if (capsReason)
56
+ return invalid(capsReason);
57
+ // The v2 inert-field table does not list this one, but the terminal planner never reads it.
58
+ if (route === "terminal" && isRecord(raw.execution) && raw.execution.timeoutMs !== undefined) {
59
+ return invalid("route: terminal does not read `execution.timeoutMs`: `caps.maxMinutes` is the command deadline. Remove it.");
60
+ }
61
+ const { count, instruction, entries, source } = participants.value;
62
+ // The v2 actor keys that hold participants.
63
+ const v2Participants = [
64
+ ["count", count ?? surfaces.value],
65
+ ["laneFocus", instruction === undefined ? undefined : { instruction }],
66
+ ["lanes", entries],
67
+ ];
68
+ const actor = { ...raw.actor };
69
+ for (const [key, value] of v2Participants)
70
+ if (value !== undefined)
71
+ actor[key] = value;
72
+ const caps = raw.caps;
73
+ const scenario = {};
74
+ if (raw.scenario !== undefined)
75
+ scenario.ref = raw.scenario;
76
+ if (raw.mode !== undefined)
77
+ scenario.mode = raw.mode;
78
+ if (caps !== undefined && route === "terminal")
79
+ scenario.caps = caps;
80
+ const execution = caps !== undefined && route !== "terminal"
81
+ ? withKey(raw.execution, "caps", caps)
82
+ : raw.execution;
83
+ const subject = route === "shared-world" ? withKey(raw.subject, "topology", "shared-world") : raw.subject;
84
+ const v2 = { schema: LAB_CONFIG_SCHEMA };
85
+ const fields = [
86
+ ["id", raw.id],
87
+ ["title", raw.title],
88
+ ["description", raw.description],
89
+ ["subject", subject],
90
+ ["actors", [actor]],
91
+ ["execution", execution],
92
+ ["scenario", Object.keys(scenario).length > 0 ? scenario : undefined],
93
+ ["policies", raw.policies],
94
+ ["review", raw.review],
95
+ ["defaults", raw.defaults],
96
+ ["comms", raw.comms],
97
+ ];
98
+ for (const [key, value] of fields)
99
+ if (value !== undefined)
100
+ v2[key] = value;
101
+ return { ok: true, value: { route, v2, participantSource: source } };
102
+ }
103
+ /**
104
+ * Spell a v2 parser message the way the study file is written: `actors[0].lanes[3]` becomes the
105
+ * `participants` entry it came from, `execution.caps` and `scenario.caps` become `caps`, and so on.
106
+ */
107
+ export function studySpelling(message, route, participantSource) {
108
+ return (message
109
+ .replaceAll(/actors\[0\]\.lanes\[(\d+)\]/g, (_match, index) => `participants[${participantSource[Number(index)] ?? index}]`)
110
+ // "an `actors[0].lanes` roster" reads "a `participants` list" in a v3 file.
111
+ .replaceAll(/\ban `actors\[0\]\.(lanes|roster)` roster\b/g, "a `participants` list")
112
+ .replaceAll(/actors\[0\]\.(lanes|roster)\b/g, "participants")
113
+ .replaceAll(/actors\[0\]\.laneFocus(\.instruction)?/g, "participants.instruction")
114
+ .replaceAll(/actors\[0\]\.count\b/g, route === "scripted" ? "surfaces" : "participants")
115
+ .replaceAll("actors[0]", "actor")
116
+ .replaceAll(/\b(execution|scenario)\.caps\b/g, "caps")
117
+ .replaceAll(/\bscenario\.mode\b/g, "mode")
118
+ .replaceAll(/\bscenario\.ref\b/g, "scenario")
119
+ .replaceAll("subject.topology: shared-world", "route: shared-world"));
120
+ }
121
+ // The v2 keys a hand-converted file is most likely to keep, each with where it went.
122
+ function movedKeyReason(raw) {
123
+ if (raw.actors !== undefined) {
124
+ return "A study has one `actor:` object, and its participants go in `participants:`. `actors:` is the humanish.lab.v2 spelling.";
125
+ }
126
+ if (isRecord(raw.actor)) {
127
+ for (const key of ["count", "lanes", "roster", "laneFocus"]) {
128
+ if (raw.actor[key] !== undefined) {
129
+ return `\`actor.${key}\` moved to \`participants:\`: a count, \`{ count, instruction }\`, or a list of participants.`;
130
+ }
131
+ }
132
+ }
133
+ if (isRecord(raw.subject) && raw.subject.topology !== undefined) {
134
+ return "`subject.topology` moved to `route:`. A shared world is `route: shared-world`.";
135
+ }
136
+ if (isRecord(raw.execution) && raw.execution.caps !== undefined) {
137
+ return "`execution.caps` moved to a top-level `caps:` block.";
138
+ }
139
+ return undefined;
140
+ }
141
+ function participantsOf(route, raw) {
142
+ if (raw === undefined) {
143
+ if (route === "shared-world")
144
+ return invalid(listOnly(route));
145
+ return { ok: true, value: { source: [] } };
146
+ }
147
+ switch (route) {
148
+ case "scripted":
149
+ return invalid("route: scripted takes no `participants`. It replays committed steps on `surfaces`: [desktop] or [desktop, mobile].");
150
+ case "terminal":
151
+ return invalid("route: terminal takes no `participants`. It runs one agent.");
152
+ case "preview":
153
+ return typeof raw === "number"
154
+ ? countOf(raw)
155
+ : invalid("route: preview takes `participants` as a count, such as `participants: 4`.");
156
+ case "shared-world":
157
+ return Array.isArray(raw) ? entriesOf(raw) : invalid(listOnly(route));
158
+ case "computer-use":
159
+ if (typeof raw === "number")
160
+ return countOf(raw);
161
+ if (Array.isArray(raw))
162
+ return entriesOf(raw);
163
+ return homogeneousOf(raw);
164
+ }
165
+ }
166
+ function listOnly(route) {
167
+ return `route: ${route} takes \`participants\` as a list of at least two entries, one per participant in the shared world.`;
168
+ }
169
+ function countOf(raw) {
170
+ const count = posInt(raw);
171
+ if (count === undefined)
172
+ return invalid("`participants` must be a positive integer.");
173
+ return { ok: true, value: { count, source: [] } };
174
+ }
175
+ // `{ count, instruction }`: participants that share one instruction. The count stays a count, so
176
+ // `--count` still overrides it.
177
+ function homogeneousOf(raw) {
178
+ if (!isRecord(raw)) {
179
+ return invalid("`participants` is a count, an object `{ count, instruction }`, or a list of participants.");
180
+ }
181
+ const extra = Object.keys(raw).filter((key) => key !== "count" && key !== "instruction");
182
+ if (extra.length > 0) {
183
+ return invalid(`\`participants\` as an object takes only \`count\` and \`instruction\`, and every participant gets them. To set ${extra.map((key) => `\`${key}\``).join(", ")}, write participants as a list.`);
184
+ }
185
+ const participants = { source: [] };
186
+ if (raw.count !== undefined) {
187
+ const count = typeof raw.count === "number" ? posInt(raw.count) : undefined;
188
+ if (count === undefined)
189
+ return invalid("`participants.count` must be a positive integer.");
190
+ participants.count = count;
191
+ }
192
+ if (raw.instruction !== undefined)
193
+ participants.instruction = raw.instruction;
194
+ return { ok: true, value: participants };
195
+ }
196
+ // A list entry with `count: n` is a group: n participants `<id>-01` to `<id>-NN`, even when n is 1.
197
+ // The v2 parser then checks every entry and the expanded ids for collisions.
198
+ function entriesOf(raw) {
199
+ const entries = [];
200
+ const source = [];
201
+ for (const [index, entry] of raw.entries()) {
202
+ if (!isRecord(entry) || entry.count === undefined) {
203
+ entries.push(entry);
204
+ source.push(index);
205
+ continue;
206
+ }
207
+ const count = typeof entry.count === "number" ? posInt(entry.count) : undefined;
208
+ if (count === undefined) {
209
+ return invalid(`participants[${index}].count must be a positive integer.`);
210
+ }
211
+ const id = entry.id;
212
+ if (typeof id !== "string" ||
213
+ !PARTICIPANT_ID_PATTERN.test(id) ||
214
+ id.length > PARTICIPANT_ID_MAX_CHARS - 3) {
215
+ return invalid(`participants[${index}] has a count, so it needs an \`id\` matching ${PARTICIPANT_ID_PATTERN} of at most ${PARTICIPANT_ID_MAX_CHARS - 3} characters. Its participants are <id>-01 to <id>-${String(count).padStart(2, "0")}.`);
216
+ }
217
+ const shared = { ...entry };
218
+ delete shared.count;
219
+ for (let n = 1; n <= count; n += 1) {
220
+ entries.push({ ...shared, id: `${id}-${String(n).padStart(2, "0")}` });
221
+ source.push(index);
222
+ }
223
+ }
224
+ return { ok: true, value: { entries, source } };
225
+ }
226
+ // Scripted surfaces become the v2 count: 1 is desktop, 2 adds mobile.
227
+ function surfaceCount(route, raw) {
228
+ if (raw === undefined)
229
+ return { ok: true, value: undefined };
230
+ if (route !== "scripted") {
231
+ return invalid(`\`surfaces\` belongs to route: scripted. This study's route is ${route}; remove it.`);
232
+ }
233
+ if (Array.isArray(raw) && raw.length === 1 && raw[0] === "desktop")
234
+ return { ok: true, value: 1 };
235
+ if (Array.isArray(raw) && raw.length === 2 && raw[0] === "desktop" && raw[1] === "mobile") {
236
+ return { ok: true, value: 2 };
237
+ }
238
+ return invalid("`surfaces` must be [desktop] or [desktop, mobile].");
239
+ }
240
+ function capsKeysReason(route, raw) {
241
+ if (raw === undefined)
242
+ return undefined;
243
+ const read = CAPS_KEYS[route];
244
+ if (read.length === 0)
245
+ return `route: ${route} reads no \`caps\`; remove the block.`;
246
+ if (!isRecord(raw))
247
+ return undefined;
248
+ const unread = Object.keys(raw).filter((key) => !read.includes(key));
249
+ if (unread.length === 0)
250
+ return undefined;
251
+ return `route: ${route} reads only ${read.map((key) => `caps.${key}`).join(", ")}. Remove ${unread.map((key) => `caps.${key}`).join(", ")}.`;
252
+ }
253
+ // Add one key to a section. A section that is not an object passes through unchanged, so the v2
254
+ // parser refuses it with its own message.
255
+ function withKey(section, key, value) {
256
+ if (section === undefined)
257
+ return { [key]: value };
258
+ return isRecord(section) ? { ...section, [key]: value } : section;
259
+ }
@@ -1,10 +1,10 @@
1
1
  import type { LabConfigParseFailure, LabSubjectState } from "../types.js";
2
2
  /**
3
- * LITERAL non-secret subject env. Real apps need configuration before they will boot — a public base
4
- * URL, a transport selector, a feature flag — and none of that is secret. Routing it through
3
+ * Literal non-secret subject env. Real apps need configuration before they will boot: a public base
4
+ * URL, a transport selector, a feature flag, and none of that is secret. Routing it through
5
5
  * `subject.env` would force an adopter to carry a private env file just to reproduce a public study.
6
6
  *
7
- * These values ARE recorded in evidence (they are part of how the subject was configured), so a
7
+ * These values are recorded in evidence (they are part of how the subject was configured), so a
8
8
  * value that looks like a credential is refused here rather than committed to a public repo.
9
9
  */
10
10
  export declare function parseEnvValues(raw: unknown): {
@@ -14,8 +14,8 @@ export declare function parseEnvValues(raw: unknown): {
14
14
  /**
15
15
  * Structural parse of `subject.state` into a candidate LabSubjectState. Deliberately keeps
16
16
  * unrecognized `when`/`timeoutMs` values in the candidate (instead of silently dropping
17
- * them) so subjectStateInvalidReason rejects them — a state declaration that silently does
18
- * less than it says would violate invariant 6.
17
+ * them) so subjectStateInvalidReason rejects them: a state declaration that silently does
18
+ * less than it says would claim more than its mechanism does.
19
19
  */
20
20
  export declare function parseState(raw: unknown): {
21
21
  ok: true;
@@ -23,8 +23,8 @@ export declare function parseState(raw: unknown): {
23
23
  } | LabConfigParseFailure;
24
24
  /**
25
25
  * Semantic validation for `subject.state`, shared by parseLabConfig and the engine
26
- * (runCuaActorLab re-enforces it on configs that arrive through the library API). Returns
26
+ * (the route re-enforces it on configs that arrive through runLab). Returns
27
27
  * the failure message, or null when the declaration is valid. Reads the candidate
28
- * defensively — library callers can hand the engine arbitrarily-shaped objects.
28
+ * defensively: library callers can hand the engine arbitrarily-shaped objects.
29
29
  */
30
30
  export declare function subjectStateInvalidReason(state: LabSubjectState, env: readonly string[] | undefined): string | null;