humanish 0.107.0 → 0.109.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 (671) hide show
  1. package/ARCHITECTURE.md +73 -69
  2. package/CHANGELOG.md +27 -0
  3. package/CONTEXT.md +51 -44
  4. package/README.md +148 -175
  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-trace.js +2 -1
  14. package/dist/actors/codex/app-server-ui.js +1 -1
  15. package/dist/actors/codex/codex-admission.d.ts +91 -0
  16. package/dist/actors/codex/codex-admission.js +150 -0
  17. package/dist/actors/codex/restricted-executable.d.ts +4 -4
  18. package/dist/actors/codex/restricted-executable.js +8 -8
  19. package/dist/actors/codex/restricted-launch.d.ts +2 -1
  20. package/dist/actors/codex/restricted-launch.js +1 -1
  21. package/dist/actors/codex/restricted-participant.js +20 -18
  22. package/dist/actors/codex/restricted-policy.d.ts +2 -2
  23. package/dist/actors/codex/restricted-policy.js +2 -2
  24. package/dist/actors/codex/restricted-session.d.ts +3 -3
  25. package/dist/actors/codex/restricted-session.js +10 -5
  26. package/dist/actors/computer-use/actor.d.ts +18 -18
  27. package/dist/actors/computer-use/actor.js +4 -4
  28. package/dist/actors/computer-use/admission-limit.d.ts +3 -3
  29. package/dist/actors/computer-use/admission-limit.js +4 -4
  30. package/dist/actors/computer-use/executor-error.d.ts +2 -2
  31. package/dist/actors/computer-use/executor-error.js +3 -3
  32. package/dist/actors/computer-use/loop/actions.d.ts +3 -3
  33. package/dist/actors/computer-use/loop/actions.js +15 -15
  34. package/dist/actors/computer-use/loop/backstop.d.ts +3 -3
  35. package/dist/actors/computer-use/loop/backstop.js +14 -12
  36. package/dist/actors/computer-use/loop/debrief.js +2 -2
  37. package/dist/actors/computer-use/loop/ending.d.ts +6 -6
  38. package/dist/actors/computer-use/loop/ending.js +18 -17
  39. package/dist/actors/computer-use/loop/observation.d.ts +2 -2
  40. package/dist/actors/computer-use/loop/observation.js +15 -14
  41. package/dist/actors/computer-use/loop/provider-call.d.ts +1 -1
  42. package/dist/actors/computer-use/loop/provider-call.js +12 -12
  43. package/dist/actors/computer-use/loop/session.d.ts +4 -4
  44. package/dist/actors/computer-use/loop/session.js +4 -4
  45. package/dist/actors/computer-use/loop/spend.d.ts +1 -1
  46. package/dist/actors/computer-use/loop/spend.js +1 -1
  47. package/dist/actors/computer-use/loop/trace.js +11 -9
  48. package/dist/actors/computer-use/loop/types.d.ts +47 -45
  49. package/dist/actors/computer-use/loop/usage.d.ts +2 -2
  50. package/dist/actors/computer-use/loop/usage.js +2 -2
  51. package/dist/actors/computer-use/loop.d.ts +6 -1
  52. package/dist/actors/computer-use/loop.js +26 -4
  53. package/dist/actors/computer-use/openai-context.d.ts +37 -0
  54. package/dist/actors/computer-use/openai-context.js +214 -0
  55. package/dist/actors/computer-use/openai-provider.d.ts +13 -10
  56. package/dist/actors/computer-use/openai-provider.js +52 -41
  57. package/dist/actors/computer-use/openai-requests.d.ts +36 -10
  58. package/dist/actors/computer-use/openai-requests.js +82 -15
  59. package/dist/actors/computer-use/openai-wire.d.ts +19 -7
  60. package/dist/actors/computer-use/openai-wire.js +36 -34
  61. package/dist/actors/computer-use/provider-error.d.ts +4 -4
  62. package/dist/actors/computer-use/provider-error.js +6 -6
  63. package/dist/actors/contract.d.ts +82 -47
  64. package/dist/actors/contract.js +30 -24
  65. package/dist/actors/local-agent/claude-session.d.ts +1 -1
  66. package/dist/actors/local-agent/claude-session.js +13 -12
  67. package/dist/actors/local-agent/cli.d.ts +9 -7
  68. package/dist/actors/local-agent/cli.js +24 -28
  69. package/dist/actors/local-agent/readiness.d.ts +2 -2
  70. package/dist/actors/local-agent/readiness.js +4 -4
  71. package/dist/actors/reasoning-effort.js +8 -8
  72. package/dist/actors/registry.d.ts +12 -12
  73. package/dist/actors/registry.js +21 -20
  74. package/dist/actors/scripted-browser/actor.d.ts +10 -10
  75. package/dist/actors/scripted-browser/actor.js +17 -17
  76. package/dist/actors/scripted-browser/browser-command.js +1 -1
  77. package/dist/actors/scripted-browser/steps.js +1 -1
  78. package/dist/actors/scripted-browser/types.d.ts +1 -1
  79. package/dist/actors/stop-conditions.d.ts +1 -1
  80. package/dist/analysis/automatic-completion.d.ts +7 -7
  81. package/dist/analysis/automatic-completion.js +1 -1
  82. package/dist/analysis/automatic-config.d.ts +6 -6
  83. package/dist/analysis/automatic-config.js +1 -1
  84. package/dist/analysis/codex-config.d.ts +6 -2
  85. package/dist/analysis/codex-config.js +22 -14
  86. package/dist/analysis/execute.d.ts +1 -1
  87. package/dist/analysis/execute.js +1 -1
  88. package/dist/analysis/service.js +5 -5
  89. package/dist/analysis/types.d.ts +6 -0
  90. package/dist/browser-control/client.js +16 -16
  91. package/dist/browser-control/dispatcher.js +4 -4
  92. package/dist/browser-control/protocol.js +14 -14
  93. package/dist/browser-control/recording-transfer.js +4 -4
  94. package/dist/browser-control/transport.js +7 -7
  95. package/dist/cli/commands/analysis-signals.d.ts +2 -2
  96. package/dist/cli/commands/analyze.js +19 -16
  97. package/dist/cli/commands/codex.js +9 -9
  98. package/dist/cli/commands/comms.js +48 -35
  99. package/dist/cli/commands/feedback.js +20 -21
  100. package/dist/cli/commands/migrate.d.ts +4 -0
  101. package/dist/cli/commands/migrate.js +65 -0
  102. package/dist/cli/commands/observe.d.ts +0 -1
  103. package/dist/cli/commands/observe.js +136 -112
  104. package/dist/cli/commands/run-command.d.ts +10 -0
  105. package/dist/cli/commands/run-command.js +85 -0
  106. package/dist/cli/commands/runs.js +136 -139
  107. package/dist/cli/commands/setup.d.ts +2 -2
  108. package/dist/cli/commands/setup.js +27 -24
  109. package/dist/cli/commands/study-format.d.ts +9 -0
  110. package/dist/cli/commands/{lab-format.js → study-format.js} +39 -27
  111. package/dist/cli/commands/{lab-route-computer-use.d.ts → study-route-computer-use.d.ts} +6 -6
  112. package/dist/cli/commands/{lab-route-computer-use.js → study-route-computer-use.js} +17 -18
  113. package/dist/cli/commands/{lab-route-open.d.ts → study-route-open.d.ts} +4 -4
  114. package/dist/cli/commands/{lab-route-open.js → study-route-open.js} +2 -2
  115. package/dist/cli/commands/{lab-route-preview.d.ts → study-route-preview.d.ts} +7 -7
  116. package/dist/cli/commands/{lab-route-preview.js → study-route-preview.js} +7 -6
  117. package/dist/cli/commands/study-route-run.d.ts +19 -0
  118. package/dist/cli/commands/{lab-route-run.js → study-route-run.js} +6 -6
  119. package/dist/cli/commands/study-route-scripted.d.ts +17 -0
  120. package/dist/cli/commands/{lab-route-scripted.js → study-route-scripted.js} +4 -4
  121. package/dist/cli/commands/{lab-route-shared-world.d.ts → study-route-shared-world.d.ts} +5 -5
  122. package/dist/cli/commands/{lab-route-shared-world.js → study-route-shared-world.js} +10 -10
  123. package/dist/cli/commands/study-route-terminal.d.ts +17 -0
  124. package/dist/cli/commands/{lab-route-terminal.js → study-route-terminal.js} +4 -4
  125. package/dist/cli/commands/study-run.d.ts +9 -0
  126. package/dist/cli/commands/{lab-run.js → study-run.js} +44 -45
  127. package/dist/cli/commands/{lab-scorer.d.ts → study-scorer.d.ts} +7 -7
  128. package/dist/cli/commands/{lab-scorer.js → study-scorer.js} +2 -2
  129. package/dist/cli/commands/study.d.ts +3 -0
  130. package/dist/cli/commands/study.js +146 -0
  131. package/dist/cli/commands/tui.d.ts +4 -4
  132. package/dist/cli/commands/tui.js +31 -31
  133. package/dist/cli/commands/watch.js +71 -121
  134. package/dist/cli/deprecations.d.ts +6 -0
  135. package/dist/cli/deprecations.js +18 -0
  136. package/dist/cli/doctor.d.ts +18 -8
  137. package/dist/cli/doctor.js +114 -59
  138. package/dist/cli/first-run-path.d.ts +15 -13
  139. package/dist/cli/first-run-path.js +35 -33
  140. package/dist/cli/io.d.ts +44 -9
  141. package/dist/cli/io.js +98 -23
  142. package/dist/cli/observer-follow.d.ts +3 -3
  143. package/dist/cli/observer-follow.js +6 -7
  144. package/dist/cli/orientation.d.ts +10 -7
  145. package/dist/cli/orientation.js +89 -47
  146. package/dist/cli/product-sentence.d.ts +6 -0
  147. package/dist/cli/product-sentence.js +6 -0
  148. package/dist/cli/program.d.ts +6 -1
  149. package/dist/cli/program.js +111 -43
  150. package/dist/cli/telemetry.d.ts +16 -16
  151. package/dist/cli/telemetry.js +46 -44
  152. package/dist/cli.js +1 -1
  153. package/dist/comms/capture-surface.d.ts +13 -12
  154. package/dist/comms/capture-surface.js +23 -22
  155. package/dist/comms/catch-host.d.ts +7 -7
  156. package/dist/comms/catch-host.js +17 -17
  157. package/dist/comms/connections.d.ts +1 -1
  158. package/dist/comms/connections.js +2 -2
  159. package/dist/comms/email-catch.d.ts +3 -3
  160. package/dist/comms/email-catch.js +8 -8
  161. package/dist/comms/external-evidence.d.ts +5 -5
  162. package/dist/comms/external-evidence.js +5 -4
  163. package/dist/comms/extract.js +2 -2
  164. package/dist/comms/fake-inbox.d.ts +3 -3
  165. package/dist/comms/fake-inbox.js +3 -3
  166. package/dist/comms/receiving-render.d.ts +1 -1
  167. package/dist/comms/receiving-render.js +2 -2
  168. package/dist/comms/receiving-runtime.d.ts +4 -4
  169. package/dist/comms/sandbox-catch-script.d.ts +6 -6
  170. package/dist/comms/sandbox-catch-script.js +6 -6
  171. package/dist/comms/sandbox-catch.d.ts +38 -39
  172. package/dist/comms/sandbox-catch.js +36 -37
  173. package/dist/comms/setup.d.ts +2 -2
  174. package/dist/comms/setup.js +56 -25
  175. package/dist/comms/thread-evidence.d.ts +1 -1
  176. package/dist/comms/thread-evidence.js +6 -6
  177. package/dist/comms/types.d.ts +6 -6
  178. package/dist/comms/types.js +3 -3
  179. package/dist/evidence/frame-signature.d.ts +1 -1
  180. package/dist/evidence/frame-signature.js +4 -4
  181. package/dist/evidence/redaction.d.ts +3 -3
  182. package/dist/evidence/redaction.js +4 -4
  183. package/dist/feedback/draft.js +38 -80
  184. package/dist/feedback/export-bundle.js +1 -1
  185. package/dist/feedback/export.d.ts +5 -1
  186. package/dist/feedback/export.js +10 -10
  187. package/dist/feedback/feedback.d.ts +2 -2
  188. package/dist/feedback/feedback.js +21 -3
  189. package/dist/feedback/proof.d.ts +1 -1
  190. package/dist/feedback/proof.js +2 -2
  191. package/dist/guest/bootstrap.js +6 -6
  192. package/dist/guest/browser-tools.js +8 -8
  193. package/dist/guest/chromium-text.js +19 -19
  194. package/dist/guest/desktop-executor.d.ts +2 -2
  195. package/dist/guest/desktop-executor.js +20 -20
  196. package/dist/guest/desktop-keys.js +4 -4
  197. package/dist/guest/desktop-media.d.ts +2 -2
  198. package/dist/guest/desktop-media.js +16 -16
  199. package/dist/guest/desktop-native.d.ts +1 -1
  200. package/dist/guest/desktop-native.js +10 -10
  201. package/dist/guest/runtime-desktop.js +2 -2
  202. package/dist/guest/runtime-processes.d.ts +2 -2
  203. package/dist/guest/runtime-processes.js +2 -2
  204. package/dist/guest/runtime.js +6 -6
  205. package/dist/index.d.ts +14 -14
  206. package/dist/index.js +8 -8
  207. package/dist/keys/key-resolution.d.ts +5 -5
  208. package/dist/keys/key-resolution.js +23 -23
  209. package/dist/observer/artifact.d.ts +2 -2
  210. package/dist/observer/artifact.js +7 -7
  211. package/dist/observer/data.d.ts +6 -6
  212. package/dist/observer/data.js +5 -4
  213. package/dist/observer/exposure.d.ts +1 -1
  214. package/dist/observer/exposure.js +11 -11
  215. package/dist/observer/http.js +1 -1
  216. package/dist/observer/library.d.ts +3 -1
  217. package/dist/observer/library.js +8 -3
  218. package/dist/observer/listen.js +5 -4
  219. package/dist/observer/render.d.ts +1 -1
  220. package/dist/observer/render.js +17 -6
  221. package/dist/observer/run-routes.d.ts +4 -3
  222. package/dist/observer/run-routes.js +38 -13
  223. package/dist/observer/serve.d.ts +1 -1
  224. package/dist/observer/serve.js +7 -3
  225. package/dist/observer/tunnel.js +1 -1
  226. package/dist/observer-app.html +4 -4
  227. package/dist/routes/computer-use/bundle-parts.d.ts +9 -9
  228. package/dist/routes/computer-use/bundle-parts.js +18 -17
  229. package/dist/routes/computer-use/bundle.d.ts +2 -2
  230. package/dist/routes/computer-use/bundle.js +10 -9
  231. package/dist/routes/computer-use/e2b-desktop/comms.d.ts +6 -6
  232. package/dist/routes/computer-use/e2b-desktop/comms.js +18 -17
  233. package/dist/routes/computer-use/e2b-desktop/desktop.js +4 -4
  234. package/dist/routes/computer-use/e2b-desktop/fidelity.d.ts +5 -4
  235. package/dist/routes/computer-use/e2b-desktop/fidelity.js +11 -10
  236. package/dist/routes/computer-use/e2b-desktop/prepare.d.ts +1 -1
  237. package/dist/routes/computer-use/e2b-desktop/prepare.js +10 -10
  238. package/dist/routes/computer-use/e2b-desktop/start.js +7 -7
  239. package/dist/routes/computer-use/e2b-desktop/state.d.ts +2 -2
  240. package/dist/routes/computer-use/e2b-desktop/teardown.js +2 -2
  241. package/dist/routes/computer-use/external-comms.d.ts +4 -4
  242. package/dist/routes/computer-use/external-comms.js +1 -1
  243. package/dist/routes/computer-use/fanout-bundle.js +27 -26
  244. package/dist/routes/computer-use/fanout-records.js +12 -13
  245. package/dist/routes/computer-use/live-flush.d.ts +4 -4
  246. package/dist/routes/computer-use/live-flush.js +6 -6
  247. package/dist/routes/computer-use/live-phase.d.ts +5 -4
  248. package/dist/routes/computer-use/live-phase.js +8 -8
  249. package/dist/routes/computer-use/local-tree-pack.d.ts +5 -5
  250. package/dist/routes/computer-use/local-tree-pack.js +1 -1
  251. package/dist/routes/computer-use/local-vm.d.ts +8 -8
  252. package/dist/routes/computer-use/local-vm.js +3 -3
  253. package/dist/routes/computer-use/participant-desktop.d.ts +7 -7
  254. package/dist/routes/computer-use/participant-desktop.js +5 -5
  255. package/dist/routes/computer-use/participant-execution.d.ts +4 -4
  256. package/dist/routes/computer-use/participant-execution.js +10 -10
  257. package/dist/routes/computer-use/participant-model.d.ts +6 -0
  258. package/dist/routes/computer-use/participant-model.js +33 -15
  259. package/dist/routes/computer-use/participant-prompt.d.ts +15 -15
  260. package/dist/routes/computer-use/participant-prompt.js +9 -9
  261. package/dist/routes/computer-use/participant-runs.d.ts +16 -16
  262. package/dist/routes/computer-use/participant-runs.js +26 -25
  263. package/dist/routes/computer-use/plan.d.ts +12 -12
  264. package/dist/routes/computer-use/plan.js +42 -42
  265. package/dist/routes/computer-use/preflight.d.ts +5 -5
  266. package/dist/routes/computer-use/preflight.js +25 -25
  267. package/dist/routes/computer-use/rerun-selection.d.ts +1 -1
  268. package/dist/routes/computer-use/rerun-selection.js +4 -3
  269. package/dist/routes/computer-use/result.d.ts +4 -4
  270. package/dist/routes/computer-use/result.js +22 -21
  271. package/dist/routes/computer-use/route.d.ts +12 -11
  272. package/dist/routes/computer-use/route.js +31 -30
  273. package/dist/routes/computer-use/self-report.d.ts +7 -7
  274. package/dist/routes/computer-use/self-report.js +32 -32
  275. package/dist/routes/computer-use/setup.d.ts +23 -23
  276. package/dist/routes/computer-use/setup.js +28 -29
  277. package/dist/routes/computer-use/single-bundle.d.ts +11 -11
  278. package/dist/routes/computer-use/single-bundle.js +40 -41
  279. package/dist/routes/computer-use/subject-projection.d.ts +4 -4
  280. package/dist/routes/computer-use/subject-projection.js +6 -6
  281. package/dist/routes/computer-use/types.d.ts +71 -73
  282. package/dist/routes/computer-use/types.js +12 -12
  283. package/dist/routes/preview.d.ts +13 -7
  284. package/dist/routes/preview.js +11 -7
  285. package/dist/routes/scripted/bundle.d.ts +7 -7
  286. package/dist/routes/scripted/bundle.js +20 -18
  287. package/dist/routes/scripted/plan.d.ts +8 -8
  288. package/dist/routes/scripted/plan.js +13 -13
  289. package/dist/routes/scripted/result.d.ts +3 -3
  290. package/dist/routes/scripted/result.js +13 -13
  291. package/dist/routes/scripted/route.d.ts +9 -9
  292. package/dist/routes/scripted/route.js +15 -16
  293. package/dist/routes/scripted/scenario.d.ts +1 -1
  294. package/dist/routes/scripted/scenario.js +5 -5
  295. package/dist/routes/scripted/setup.d.ts +6 -6
  296. package/dist/routes/scripted/setup.js +8 -8
  297. package/dist/routes/scripted/subject.d.ts +5 -5
  298. package/dist/routes/scripted/subject.js +4 -4
  299. package/dist/routes/scripted/surface-records.d.ts +1 -1
  300. package/dist/routes/scripted/surface-records.js +4 -4
  301. package/dist/routes/scripted/surface-sessions.d.ts +3 -3
  302. package/dist/routes/scripted/types.d.ts +18 -20
  303. package/dist/routes/scripted/types.js +2 -2
  304. package/dist/routes/shared-world/bundle-records.js +11 -10
  305. package/dist/routes/shared-world/bundle.d.ts +5 -5
  306. package/dist/routes/shared-world/bundle.js +46 -43
  307. package/dist/routes/shared-world/checkpoints.d.ts +4 -4
  308. package/dist/routes/shared-world/checkpoints.js +2 -2
  309. package/dist/routes/shared-world/comms.d.ts +14 -14
  310. package/dist/routes/shared-world/comms.js +19 -18
  311. package/dist/routes/shared-world/external-public.d.ts +4 -3
  312. package/dist/routes/shared-world/external-public.js +16 -12
  313. package/dist/routes/shared-world/handoff.d.ts +7 -6
  314. package/dist/routes/shared-world/handoff.js +65 -57
  315. package/dist/routes/shared-world/lobby-code.d.ts +2 -2
  316. package/dist/routes/shared-world/lobby-code.js +9 -9
  317. package/dist/routes/shared-world/participant-specs.d.ts +10 -9
  318. package/dist/routes/shared-world/participant-specs.js +38 -31
  319. package/dist/routes/shared-world/plan.d.ts +9 -9
  320. package/dist/routes/shared-world/plan.js +16 -16
  321. package/dist/routes/shared-world/provenance.d.ts +2 -2
  322. package/dist/routes/shared-world/provenance.js +2 -2
  323. package/dist/routes/shared-world/provisioned.d.ts +8 -8
  324. package/dist/routes/shared-world/provisioned.js +18 -17
  325. package/dist/routes/shared-world/result.d.ts +6 -6
  326. package/dist/routes/shared-world/result.js +29 -29
  327. package/dist/routes/shared-world/route.d.ts +11 -11
  328. package/dist/routes/shared-world/route.js +40 -39
  329. package/dist/routes/shared-world/setup.d.ts +16 -16
  330. package/dist/routes/shared-world/setup.js +23 -23
  331. package/dist/routes/shared-world/types.d.ts +55 -53
  332. package/dist/routes/shared-world/types.js +4 -4
  333. package/dist/routes/terminal/adapter.d.ts +5 -5
  334. package/dist/routes/terminal/adapter.js +9 -9
  335. package/dist/routes/terminal/bundle.d.ts +13 -13
  336. package/dist/routes/terminal/bundle.js +28 -27
  337. package/dist/routes/terminal/credentials.d.ts +8 -8
  338. package/dist/routes/terminal/credentials.js +12 -12
  339. package/dist/routes/terminal/dry-run.d.ts +4 -4
  340. package/dist/routes/terminal/dry-run.js +12 -12
  341. package/dist/routes/terminal/encoding.js +2 -2
  342. package/dist/routes/terminal/ledger.d.ts +5 -5
  343. package/dist/routes/terminal/ledger.js +14 -14
  344. package/dist/routes/terminal/lifetime.d.ts +1 -1
  345. package/dist/routes/terminal/lifetime.js +1 -1
  346. package/dist/routes/terminal/live-finish.d.ts +2 -2
  347. package/dist/routes/terminal/live-finish.js +10 -10
  348. package/dist/routes/terminal/live-sandbox.d.ts +2 -2
  349. package/dist/routes/terminal/live-sandbox.js +15 -15
  350. package/dist/routes/terminal/participant-text.js +2 -1
  351. package/dist/routes/terminal/persona.d.ts +2 -2
  352. package/dist/routes/terminal/persona.js +3 -3
  353. package/dist/routes/terminal/plan.d.ts +7 -7
  354. package/dist/routes/terminal/plan.js +15 -15
  355. package/dist/routes/terminal/recorder.js +2 -2
  356. package/dist/routes/terminal/result.d.ts +5 -5
  357. package/dist/routes/terminal/result.js +14 -14
  358. package/dist/routes/terminal/route.d.ts +9 -9
  359. package/dist/routes/terminal/route.js +19 -20
  360. package/dist/routes/terminal/sandbox.d.ts +1 -1
  361. package/dist/routes/terminal/sandbox.js +1 -1
  362. package/dist/routes/terminal/session.d.ts +5 -5
  363. package/dist/routes/terminal/session.js +14 -13
  364. package/dist/routes/terminal/token-usage.d.ts +1 -1
  365. package/dist/routes/terminal/token-usage.js +7 -8
  366. package/dist/routes/terminal/trace.d.ts +4 -4
  367. package/dist/routes/terminal/trace.js +2 -2
  368. package/dist/routes/terminal/types.d.ts +48 -50
  369. package/dist/routes/terminal/types.js +5 -5
  370. package/dist/run/bundle-shape.js +3 -3
  371. package/dist/run/bundle.d.ts +49 -49
  372. package/dist/run/bundle.js +3 -2
  373. package/dist/run/cost-summary.js +3 -3
  374. package/dist/run/costs.d.ts +23 -0
  375. package/dist/run/costs.js +79 -44
  376. package/dist/run/detail.d.ts +6 -6
  377. package/dist/run/detail.js +4 -4
  378. package/dist/run/dry-run-inputs.d.ts +1 -1
  379. package/dist/run/dry-run-inputs.js +2 -2
  380. package/dist/run/dry-run.js +20 -20
  381. package/dist/run/judge.d.ts +19 -13
  382. package/dist/run/judge.js +18 -8
  383. package/dist/run/outcomes.d.ts +1 -1
  384. package/dist/run/participant.d.ts +1 -1
  385. package/dist/run/paths.d.ts +1 -1
  386. package/dist/run/paths.js +1 -1
  387. package/dist/run/preflight-receipts.js +1 -1
  388. package/dist/run/pricing.d.ts +11 -6
  389. package/dist/run/pricing.js +22 -15
  390. package/dist/run/projection.d.ts +46 -46
  391. package/dist/run/projection.js +57 -57
  392. package/dist/run/reclaim.d.ts +1 -1
  393. package/dist/run/reclaim.js +3 -3
  394. package/dist/run/results.d.ts +5 -5
  395. package/dist/run/run-cost.d.ts +53 -0
  396. package/dist/run/run-cost.js +105 -0
  397. package/dist/run/run-index-analysis.d.ts +15 -0
  398. package/dist/run/run-index-analysis.js +77 -0
  399. package/dist/run/run-index.d.ts +19 -7
  400. package/dist/run/run-index.js +77 -64
  401. package/dist/run/run-not-found.d.ts +5 -0
  402. package/dist/run/run-not-found.js +15 -0
  403. package/dist/run/run.d.ts +6 -5
  404. package/dist/run/run.js +3 -3
  405. package/dist/run/sandbox-receipts.d.ts +2 -2
  406. package/dist/run/sandbox-receipts.js +3 -3
  407. package/dist/run/shared-world-evidence.d.ts +37 -36
  408. package/dist/run/shared-world-evidence.js +4 -4
  409. package/dist/run/shared-world-shape.d.ts +1 -1
  410. package/dist/run/shared-world-shape.js +7 -7
  411. package/dist/run/stats.d.ts +14 -8
  412. package/dist/run/stats.js +121 -63
  413. package/dist/run/status.d.ts +16 -29
  414. package/dist/run/status.js +10 -29
  415. package/dist/run/stored-runs.js +2 -1
  416. package/dist/run/stream-shape.js +1 -1
  417. package/dist/run/streams.d.ts +7 -7
  418. package/dist/run/study-provenance.d.ts +49 -0
  419. package/dist/run/study-provenance.js +66 -0
  420. package/dist/run/study-result.d.ts +11 -0
  421. package/dist/run/study-result.js +9 -0
  422. package/dist/run/text.d.ts +2 -0
  423. package/dist/run/text.js +4 -0
  424. package/dist/run-study.d.ts +112 -0
  425. package/dist/{run-lab.js → run-study.js} +52 -47
  426. package/dist/{lab → study}/adapter-extension.d.ts +15 -27
  427. package/dist/{lab → study}/adapter-extension.js +10 -45
  428. package/dist/{lab → study}/adapter-scorer-loader.d.ts +17 -17
  429. package/dist/{lab → study}/adapter-scorer-loader.js +34 -34
  430. package/dist/study/composition-rules.d.ts +7 -0
  431. package/dist/{lab → study}/composition-rules.js +53 -53
  432. package/dist/study/config.d.ts +15 -0
  433. package/dist/{lab → study}/config.js +98 -41
  434. package/dist/study/convert.d.ts +33 -0
  435. package/dist/study/convert.js +432 -0
  436. package/dist/{lab → study}/device-presets.d.ts +10 -10
  437. package/dist/{lab → study}/device-presets.js +11 -11
  438. package/dist/study/discover.d.ts +75 -0
  439. package/dist/{lab → study}/discover.js +134 -95
  440. package/dist/{lab → study}/doctor.d.ts +19 -12
  441. package/dist/{lab → study}/doctor.js +58 -20
  442. package/dist/study/field-paths.d.ts +12 -0
  443. package/dist/study/field-paths.js +126 -0
  444. package/dist/study/files.d.ts +26 -0
  445. package/dist/study/files.js +44 -0
  446. package/dist/{lab → study}/init-templates.d.ts +2 -2
  447. package/dist/study/init-templates.js +421 -0
  448. package/dist/{lab → study}/init.d.ts +2 -2
  449. package/dist/{lab → study}/init.js +46 -11
  450. package/dist/study/keys.d.ts +4 -0
  451. package/dist/{lab → study}/keys.js +46 -12
  452. package/dist/study/migrate.d.ts +48 -0
  453. package/dist/study/migrate.js +408 -0
  454. package/dist/study/owned-files.d.ts +38 -0
  455. package/dist/study/owned-files.js +182 -0
  456. package/dist/{lab → study}/parse/actors.d.ts +5 -5
  457. package/dist/{lab → study}/parse/actors.js +14 -14
  458. package/dist/study/parse/comms.d.ts +12 -0
  459. package/dist/{lab → study}/parse/comms.js +5 -5
  460. package/dist/study/parse/execution.d.ts +19 -0
  461. package/dist/{lab → study}/parse/execution.js +13 -13
  462. package/dist/study/parse/study-v3.d.ts +21 -0
  463. package/dist/study/parse/study-v3.js +259 -0
  464. package/dist/study/parse/subject-state.d.ts +30 -0
  465. package/dist/{lab → study}/parse/subject-state.js +19 -19
  466. package/dist/{lab → study}/parse/subject.d.ts +3 -3
  467. package/dist/{lab → study}/parse/subject.js +34 -34
  468. package/dist/{lab → study}/parse/values.d.ts +3 -3
  469. package/dist/{lab → study}/parse/values.js +3 -3
  470. package/dist/{lab → study}/persona-resolve.d.ts +8 -8
  471. package/dist/{lab → study}/persona-resolve.js +6 -6
  472. package/dist/{lab → study}/plan-base.d.ts +7 -7
  473. package/dist/{lab → study}/plan-base.js +1 -1
  474. package/dist/{lab → study}/plan-participants.d.ts +12 -12
  475. package/dist/{lab → study}/plan-participants.js +4 -4
  476. package/dist/{lab → study}/plan-types.d.ts +31 -31
  477. package/dist/{lab → study}/plan-types.js +2 -2
  478. package/dist/study/plan.d.ts +13 -0
  479. package/dist/study/plan.js +101 -0
  480. package/dist/study/preflight-probes.d.ts +3 -0
  481. package/dist/{lab → study}/preflight-probes.js +20 -19
  482. package/dist/study/preflight-result.d.ts +8 -0
  483. package/dist/{lab → study}/preflight-result.js +5 -4
  484. package/dist/study/preflight.d.ts +94 -0
  485. package/dist/{lab → study}/preflight.js +12 -12
  486. package/dist/{lab → study}/requirements.d.ts +3 -3
  487. package/dist/{lab → study}/requirements.js +1 -1
  488. package/dist/study/route-inputs.d.ts +28 -0
  489. package/dist/{lab → study}/route-inputs.js +1 -1
  490. package/dist/{lab → study}/routing.d.ts +39 -33
  491. package/dist/{lab → study}/routing.js +60 -29
  492. package/dist/{lab/run-lab-events.d.ts → study/run-study-events.d.ts} +10 -13
  493. package/dist/{lab/run-lab-events.js → study/run-study-events.js} +3 -9
  494. package/dist/{lab/run-lab-homes.d.ts → study/run-study-homes.d.ts} +21 -18
  495. package/dist/{lab/run-lab-homes.js → study/run-study-homes.js} +1 -1
  496. package/dist/study/run-study-options.d.ts +28 -0
  497. package/dist/{lab/run-lab-options.js → study/run-study-options.js} +29 -25
  498. package/dist/{lab/lab-deps.d.ts → study/study-deps.d.ts} +8 -13
  499. package/dist/{lab/lab-deps.js → study/study-deps.js} +2 -2
  500. package/dist/{lab → study}/summary.d.ts +16 -15
  501. package/dist/{lab → study}/summary.js +21 -21
  502. package/dist/{lab → study}/tasks.d.ts +18 -18
  503. package/dist/{lab → study}/tasks.js +14 -14
  504. package/dist/{lab → study}/types.d.ts +202 -196
  505. package/dist/{lab → study}/types.js +3 -1
  506. package/dist/{lab → study}/validation.d.ts +27 -27
  507. package/dist/{lab → study}/validation.js +53 -51
  508. package/dist/study/warnings.d.ts +12 -0
  509. package/dist/{lab → study}/warnings.js +62 -34
  510. package/dist/subject/clone.d.ts +7 -7
  511. package/dist/subject/clone.js +2 -2
  512. package/dist/subject/desktop-cli.d.ts +1 -1
  513. package/dist/subject/desktop-cli.js +2 -2
  514. package/dist/subject/local-tree-archive.d.ts +2 -2
  515. package/dist/subject/local-tree-archive.js +4 -4
  516. package/dist/subject/local-tree.d.ts +4 -4
  517. package/dist/subject/node-bootstrap.js +1 -1
  518. package/dist/subject/runtime.js +9 -10
  519. package/dist/subject/serve.d.ts +5 -5
  520. package/dist/subject/serve.js +3 -3
  521. package/dist/subject/state.d.ts +4 -4
  522. package/dist/subject/state.js +1 -1
  523. package/dist/subject/steps.d.ts +6 -6
  524. package/dist/subject/steps.js +5 -5
  525. package/dist/substrates/command-failure.d.ts +3 -3
  526. package/dist/substrates/command-failure.js +3 -3
  527. package/dist/substrates/desktop-session.js +2 -2
  528. package/dist/substrates/detached.d.ts +6 -6
  529. package/dist/substrates/detached.js +7 -7
  530. package/dist/substrates/e2b/cdp-probe.d.ts +9 -9
  531. package/dist/substrates/e2b/cdp-probe.js +14 -17
  532. package/dist/substrates/e2b/desktop-browser.d.ts +6 -6
  533. package/dist/substrates/e2b/desktop-browser.js +9 -9
  534. package/dist/substrates/e2b/desktop-cdp.d.ts +10 -10
  535. package/dist/substrates/e2b/desktop-cdp.js +9 -9
  536. package/dist/substrates/e2b/desktop-executor.d.ts +2 -2
  537. package/dist/substrates/e2b/desktop-executor.js +13 -13
  538. package/dist/substrates/e2b/desktop-geometry.d.ts +4 -4
  539. package/dist/substrates/e2b/desktop-geometry.js +8 -8
  540. package/dist/substrates/e2b/desktop-media.d.ts +4 -4
  541. package/dist/substrates/e2b/desktop-media.js +1 -1
  542. package/dist/substrates/e2b/desktop-screenshot-cleanup.d.ts +3 -3
  543. package/dist/substrates/e2b/desktop-screenshot-cleanup.js +3 -3
  544. package/dist/substrates/e2b/lifetime.js +1 -1
  545. package/dist/substrates/e2b/sandbox.d.ts +4 -4
  546. package/dist/substrates/e2b/sandbox.js +2 -2
  547. package/dist/substrates/e2b/sdk.d.ts +12 -12
  548. package/dist/substrates/e2b/sdk.js +7 -7
  549. package/dist/substrates/local/runtime-config.d.ts +4 -4
  550. package/dist/substrates/local/runtime-config.js +4 -4
  551. package/dist/tui/actions.d.ts +1 -1
  552. package/dist/tui/actions.js +13 -13
  553. package/dist/tui/contract.d.ts +23 -23
  554. package/dist/tui/contract.js +11 -11
  555. package/dist/tui/launch.d.ts +7 -7
  556. package/dist/tui/launch.js +36 -31
  557. package/dist/tui/project.js +5 -5
  558. package/dist/tui-app.js +139 -139
  559. package/dist/verify/actor.d.ts +8 -8
  560. package/dist/verify/actor.js +36 -36
  561. package/dist/verify/artifacts.d.ts +2 -2
  562. package/dist/verify/artifacts.js +3 -3
  563. package/dist/verify/context-growth.d.ts +9 -0
  564. package/dist/verify/context-growth.js +62 -0
  565. package/dist/verify/costs.d.ts +6 -6
  566. package/dist/verify/costs.js +8 -8
  567. package/dist/verify/shared-world-concurrent.d.ts +2 -2
  568. package/dist/verify/shared-world-concurrent.js +79 -76
  569. package/dist/verify/shared-world.d.ts +2 -2
  570. package/dist/verify/shared-world.js +16 -16
  571. package/dist/verify/subject.d.ts +5 -5
  572. package/dist/verify/subject.js +19 -19
  573. package/dist/verify/verify.js +94 -44
  574. package/docs/README.md +25 -32
  575. package/docs/architecture/actor-contract.md +60 -91
  576. package/docs/architecture/browser-control.md +1 -1
  577. package/docs/architecture/desktop-sessions.md +7 -7
  578. package/docs/architecture/external-public-shared-world.md +68 -65
  579. package/docs/architecture/github-feedback-loop.md +2 -5
  580. package/docs/architecture/guest-desktop.md +2 -2
  581. package/docs/architecture/observer.md +34 -34
  582. package/docs/architecture/restricted-codex-analysis.md +34 -28
  583. package/docs/architecture/serve.md +34 -37
  584. package/docs/architecture/state-driven-executor.md +55 -58
  585. package/docs/architecture/task-protocol-support.md +6 -6
  586. package/docs/architecture/terminal-product-route.md +70 -110
  587. package/docs/contracts/README.md +24 -0
  588. package/docs/contracts/adapter-admission.md +3 -3
  589. package/docs/contracts/adapter-fixtures.md +4 -7
  590. package/docs/contracts/core.md +1 -3
  591. package/docs/contracts/errors.md +62 -0
  592. package/docs/contracts/feedback.md +8 -9
  593. package/docs/contracts/policy.md +7 -12
  594. package/docs/contracts/run-bundle.md +42 -44
  595. package/docs/contracts/schemas.md +446 -368
  596. package/docs/contracts/study-analysis.md +7 -4
  597. package/docs/contracts/study-costs.md +28 -9
  598. package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +1 -1
  599. package/docs/decisions/0002-observer-is-one-self-contained-file.md +2 -2
  600. package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +3 -3
  601. package/docs/principles/actor-fidelity.md +4 -4
  602. package/docs/principles/engineering.md +3 -3
  603. package/docs/principles/invariants-and-defaults.md +38 -38
  604. package/docs/principles/self-driving-harness.md +1 -5
  605. package/docs/principles/three-roles.md +1 -1
  606. package/docs/product/automatic-analysis.md +7 -7
  607. package/examples/README.md +1 -1
  608. package/examples/participant/README.md +5 -5
  609. package/examples/participant/run.mjs +11 -10
  610. package/examples/scorer/README.md +3 -3
  611. package/examples/scorer/run.mjs +11 -12
  612. package/examples/scorer/scorer.mjs +3 -3
  613. package/package.json +16 -17
  614. package/skills/humanish/SKILL.md +64 -60
  615. package/skills/humanish/agents/openai.yaml +1 -1
  616. package/AGENTS.md +0 -99
  617. package/CONTRIBUTING.md +0 -148
  618. package/dist/actors/codex/qualified-versions.d.ts +0 -72
  619. package/dist/actors/codex/qualified-versions.js +0 -132
  620. package/dist/cli/commands/lab-format.d.ts +0 -8
  621. package/dist/cli/commands/lab-route-run.d.ts +0 -19
  622. package/dist/cli/commands/lab-route-scripted.d.ts +0 -17
  623. package/dist/cli/commands/lab-route-terminal.d.ts +0 -17
  624. package/dist/cli/commands/lab-run.d.ts +0 -9
  625. package/dist/cli/commands/lab.d.ts +0 -3
  626. package/dist/cli/commands/lab.js +0 -199
  627. package/dist/cli/renamed-options.d.ts +0 -11
  628. package/dist/cli/renamed-options.js +0 -18
  629. package/dist/lab/composition-rules.d.ts +0 -7
  630. package/dist/lab/config.d.ts +0 -7
  631. package/dist/lab/discover.d.ts +0 -65
  632. package/dist/lab/init-templates.js +0 -416
  633. package/dist/lab/keys.d.ts +0 -2
  634. package/dist/lab/parse/comms.d.ts +0 -12
  635. package/dist/lab/parse/execution.d.ts +0 -19
  636. package/dist/lab/parse/subject-state.d.ts +0 -30
  637. package/dist/lab/plan.d.ts +0 -19
  638. package/dist/lab/plan.js +0 -132
  639. package/dist/lab/preflight-probes.d.ts +0 -3
  640. package/dist/lab/preflight-result.d.ts +0 -7
  641. package/dist/lab/preflight.d.ts +0 -92
  642. package/dist/lab/route-inputs.d.ts +0 -28
  643. package/dist/lab/run-lab-options.d.ts +0 -28
  644. package/dist/lab/warnings.d.ts +0 -2
  645. package/dist/observer/static.d.ts +0 -39
  646. package/dist/observer/static.js +0 -369
  647. package/dist/run-lab.d.ts +0 -112
  648. package/docs/architecture/comms-inbox.md +0 -122
  649. package/docs/architecture/desktop-recording.md +0 -80
  650. package/docs/architecture/local-browser-runtime.md +0 -178
  651. package/docs/architecture/observer-review.md +0 -144
  652. package/docs/architecture/participant-media.md +0 -90
  653. package/docs/architecture/project-layout.md +0 -62
  654. package/docs/architecture/real-email-receiving.md +0 -136
  655. package/docs/assets/humanish-drawdb-hero.png +0 -0
  656. package/docs/assets/humanish-observer-hero.png +0 -0
  657. package/docs/goals/current.md +0 -207
  658. package/docs/product/lobby-trivia-3player-external-public.md +0 -77
  659. package/docs/product/open-source-install-experience.md +0 -240
  660. package/docs/ramp/README.md +0 -295
  661. package/docs/release/open-source-readiness.md +0 -222
  662. package/docs/release/public-readiness-standard.md +0 -210
  663. package/docs/roadmap/world-class-open-source-v0.md +0 -311
  664. /package/dist/{lab → study}/init-paths.d.ts +0 -0
  665. /package/dist/{lab → study}/init-paths.js +0 -0
  666. /package/dist/{lab → study}/init-plan.d.ts +0 -0
  667. /package/dist/{lab → study}/init-plan.js +0 -0
  668. /package/dist/{lab → study}/participant-assignment.d.ts +0 -0
  669. /package/dist/{lab → study}/participant-assignment.js +0 -0
  670. /package/dist/{lab → study}/persona.d.ts +0 -0
  671. /package/dist/{lab → study}/persona.js +0 -0
@@ -0,0 +1,421 @@
1
+ import { DEFAULT_ANALYSIS_MAX_COST_USD } from "../analysis/automatic-config.js";
2
+ export const DEFAULT_LOCAL_BROWSER_STARTER = {
3
+ appUrl: "http://127.0.0.1:3000",
4
+ mission: "Use the app's primary flow. Explain anything confusing and stop when the task is complete or you are stuck.",
5
+ };
6
+ function localBrowserStudy(starter = DEFAULT_LOCAL_BROWSER_STARTER) {
7
+ return {
8
+ path: "humanish/studies/local-browser.yaml",
9
+ plane: "source",
10
+ contents: `schema: humanish.study.v3
11
+ id: local-browser
12
+ title: Local browser · your app · Codex account
13
+ description: >-
14
+ Runs one participant on your Codex account in an isolated local browser against your loopback
15
+ app. Needs no E2B or OpenAI API key: inference is remote and uses your Codex account quota.
16
+ Before the first live run, check the Codex login and the local runtime with humanish doctor
17
+ --study local-browser.
18
+ route: computer-use
19
+ mode: live
20
+ subject:
21
+ source: app-url
22
+ appUrl: ${JSON.stringify(starter.appUrl)}
23
+ actor:
24
+ type: local-agent
25
+ localAgent: codex
26
+ persona: synthetic-new-user
27
+ mission: ${JSON.stringify(starter.mission)}
28
+ execution:
29
+ target: local
30
+ concurrency: 1
31
+ timeoutMs: 120000
32
+ defaults:
33
+ open: true
34
+ `,
35
+ };
36
+ }
37
+ /**
38
+ * The flagship live study. A ChatGPT-account Codex participant has no API-dollar price, so preflight
39
+ * refuses a dollar cap on it (HUMANISH_COMPUTER_USE_UNPRICED_CAP); that variant is bounded by the
40
+ * session timeout instead of a cap it could never enforce.
41
+ */
42
+ function tryLiveStudy(actor, localAgent = "codex") {
43
+ const account = actor === "local-agent";
44
+ const needs = account
45
+ ? ` Needs E2B_API_KEY and the coding agent signed in on this machine. Cost: one small task, a few
46
+ E2B desktop minutes and the agent's account usage. humanish cannot put a dollar cap on account
47
+ usage, so the ten-minute session limit below bounds the run. When OPENAI_API_KEY is set, the
48
+ analysis after the run bills it: humanish refuses that request before it starts if its estimate
49
+ is over $${DEFAULT_ANALYSIS_MAX_COST_USD}. Set review.analysis: false to skip it.`
50
+ : ` Needs E2B_API_KEY and OPENAI_API_KEY. Cost: one small task with a $2 cap on estimated model
51
+ spend. The run stops before its next model request once the estimate passes $2, so the last
52
+ request can go slightly over, and hosted desktop time is billed separately. The analysis after
53
+ the run bills the same key outside that cap: humanish refuses it before it starts if its
54
+ estimate is over $${DEFAULT_ANALYSIS_MAX_COST_USD}. Set review.analysis: false to skip it.`;
55
+ const participant = account
56
+ ? `# Your machine has a coding agent signed in, so this study uses it: no provider API key, only
57
+ # E2B. To use a provider key instead, swap to \`type: openai-computer-use\` and add caps.maxUsd.
58
+ actor:
59
+ type: local-agent
60
+ localAgent: ${localAgent}`
61
+ : `actor:
62
+ type: openai-computer-use
63
+ # Bounds each model reply. Under the cap below it also bounds what a stalled or lost request
64
+ # can cost, so the run can retry it instead of stopping.
65
+ maxOutputTokens: 8192`;
66
+ const caps = account
67
+ ? ""
68
+ : `caps:
69
+ maxUsd: 2 # estimated model spend; the run stops before its next request once it passes this
70
+ `;
71
+ const timeout = account
72
+ ? " timeoutMs: 600000 # bounds the run; a dollar cap cannot be enforced on account usage"
73
+ : " timeoutMs: 600000";
74
+ return {
75
+ path: "humanish/studies/try-live.yaml",
76
+ plane: "source",
77
+ contents: `schema: humanish.study.v3
78
+ id: try-live
79
+ title: "Your first live study: one participant on a demo app"
80
+ description: >-
81
+ Runs one participant live on drawDB, a public open-source diagram editor that humanish clones
82
+ and serves in a hosted desktop, so it runs with nothing to configure. The app under study is
83
+ that demo, not yours. The other starter studies need your app's details first.
84
+ ${needs}
85
+ A participant who gets stuck and says where is a useful result: humanish does not count that
86
+ run as a pass, and the friction the participant describes is the finding. To study your own
87
+ app, change subject; humanish/studies/cua-browser.yaml shows the shape.
88
+ route: computer-use
89
+ mode: live # the other starter studies start as dry runs
90
+ subject:
91
+ source: clone
92
+ repos:
93
+ - drawdb-io/drawdb
94
+ clone:
95
+ depth: 1
96
+ serve:
97
+ # Plain npm: humanish provides the Node runtime that the stock desktop image lacks.
98
+ install: npm install --no-audit --no-fund
99
+ build: npm run build
100
+ start: npx vite preview --host 127.0.0.1 --port 3000
101
+ url: http://127.0.0.1:3000/
102
+ ${participant}
103
+ persona: synthetic-new-user
104
+ mission: >-
105
+ You have never seen this diagram tool before. Add two tables and give them meaningful names,
106
+ then stop and say what you did, what confused you, and where you hesitated.
107
+ ${caps}execution:
108
+ target: e2b-desktop
109
+ ${timeout}
110
+ defaults:
111
+ open: true
112
+ `,
113
+ };
114
+ }
115
+ export const starterFiles = [
116
+ {
117
+ path: "humanish/README.md",
118
+ plane: "source",
119
+ contents: `# humanish
120
+
121
+ This directory holds the committed definitions of this app's humanish studies: study files,
122
+ personas and scenarios.
123
+
124
+ Keep this directory public-safe:
125
+
126
+ - synthetic personas only;
127
+ - synthetic fixtures only;
128
+ - env var names only, never values;
129
+ - no PII, PHI, secrets, raw private transcripts, private screenshots, customer data, or patient data.
130
+
131
+ Generated run bundles, screenshots, traces, logs, and local overrides belong in ignored \`.humanish/\`.
132
+
133
+ Studies:
134
+
135
+ - committed reusable studies live in humanish/studies/*.yaml;
136
+ - private or machine-local studies live in ignored .humanish/studies/*.yaml or
137
+ .humanish/local/studies/*.yaml;
138
+ - run a study with \`humanish run <study>\`, or \`humanish watch <study>\` to stay attached;
139
+ - files under humanish/labs/ move there with \`humanish migrate\`.
140
+
141
+ Format standard:
142
+
143
+ - human-authored humanish source uses .yaml;
144
+ - generated artifacts, synthetic fixtures, and event streams use .json or .ndjson;
145
+ - .yml is reserved for outside ecosystem files such as GitHub Actions, not humanish source.
146
+ `,
147
+ },
148
+ {
149
+ path: "humanish/personas/synthetic-new-user.yaml",
150
+ plane: "source",
151
+ contents: `schema: humanish.persona.v1
152
+ id: synthetic-new-user
153
+ name: Synthetic New User
154
+ summary: A privacy-safe first-time user evaluating the app with realistic but synthetic needs.
155
+ traits:
156
+ patience: medium
157
+ technical_confidence: medium
158
+ constraints:
159
+ - Do not use real personal data.
160
+ - Do not use production accounts.
161
+ - Treat all credentials as env var names only.
162
+ `,
163
+ },
164
+ {
165
+ path: "humanish/personas/skeptical-power-user.yaml",
166
+ plane: "source",
167
+ contents: `schema: humanish.persona.v1
168
+ id: skeptical-power-user
169
+ name: Skeptical Power User
170
+ summary: A privacy-safe experienced user looking for speed, reversibility, and clear proof.
171
+ traits:
172
+ patience: low
173
+ technical_confidence: high
174
+ accessibility_needs: keyboard_first
175
+ constraints:
176
+ - Do not use real personal data.
177
+ - Prefer synthetic fixture inputs.
178
+ - Flag unclear recovery paths.
179
+ `,
180
+ },
181
+ {
182
+ path: "humanish/scenarios/first-run-smoke.yaml",
183
+ plane: "source",
184
+ contents: `schema: humanish.scenario.v1
185
+ id: first-run-smoke
186
+ title: First-run smoke
187
+ persona: synthetic-new-user
188
+ goal: Reach the first meaningful product state without using private data.
189
+ mode: dry-run
190
+ steps:
191
+ - name: Open the app
192
+ expectation: The app shell is reachable.
193
+ - name: Complete the first synthetic action
194
+ expectation: The user sees a clear next state.
195
+ - name: Capture review notes
196
+ expectation: Notes are public-safe and evidence-backed.
197
+ `,
198
+ },
199
+ {
200
+ path: "humanish/scenarios/onboarding-regression.yaml",
201
+ plane: "source",
202
+ contents: `schema: humanish.scenario.v1
203
+ id: onboarding-regression
204
+ title: Onboarding regression
205
+ persona: skeptical-power-user
206
+ goal: Exercise onboarding friction using synthetic inputs and explicit recovery checks.
207
+ mode: dry-run
208
+ steps:
209
+ - name: Start onboarding
210
+ expectation: Required information is clear.
211
+ - name: Use synthetic fixture data
212
+ expectation: No real user data is entered.
213
+ - name: Check recovery path
214
+ expectation: The user can back out or retry safely.
215
+ `,
216
+ },
217
+ {
218
+ path: "humanish/studies/first-run.yaml",
219
+ plane: "source",
220
+ contents: `schema: humanish.study.v3
221
+ id: first-run
222
+ title: First-run synthetic Observer
223
+ description: >-
224
+ Does a dry run: writes a synthetic run bundle and Observer for four participants. Needs no browser,
225
+ model or keys, and costs nothing. Run it with humanish run first-run.
226
+ route: preview
227
+ mode: dry-run
228
+ subject:
229
+ source: this-repo
230
+ actor:
231
+ type: synthetic-persona
232
+ participants: 4
233
+ defaults:
234
+ open: true
235
+ `,
236
+ },
237
+ tryLiveStudy("openai-computer-use"),
238
+ localBrowserStudy(),
239
+ {
240
+ path: "humanish/studies/cua-browser.yaml",
241
+ plane: "source",
242
+ contents: `schema: humanish.study.v3
243
+ id: cua-browser
244
+ title: Computer-use browser study
245
+ description: >-
246
+ A computer-use participant drives your app in a hosted desktop browser and writes its evidence
247
+ to gitignored .humanish/. Starts as a dry run. For a live session, set mode to live
248
+ and pass OPENAI_API_KEY and E2B_API_KEY with --env-file. The clone subject below serves your
249
+ repo inside the sandbox, and the env names declared under subject get their values from
250
+ --env-file without the values being saved. Screenshots keep full fidelity by default; set
251
+ policies.redactScreenshots: true to blur them at capture for a bundle you can share as is.
252
+ Typed text is recorded as its length only.
253
+ route: computer-use
254
+ mode: dry-run
255
+ subject:
256
+ source: clone
257
+ repos: [your-org/your-app]
258
+ serve:
259
+ install: pnpm install --frozen-lockfile
260
+ build: pnpm build
261
+ start: pnpm start
262
+ url: http://127.0.0.1:3000/
263
+ # installTimeoutMs: 1200000 # bump for monorepo-scale installs/builds (default 600000)
264
+ # buildTimeoutMs: 1800000
265
+ # env: [DATABASE_URL]
266
+ # state: # the subject's state, recorded as provenance:
267
+ # seed: # ordered, bounded seed/migration/fixture steps (commands are
268
+ # - name: db-up # author-trusted; evidence records sha256 digests, never text)
269
+ # command: sudo service postgresql start && pg_isready -t 30
270
+ # when: before-start # before-build | before-start (default) | after-ready
271
+ # - name: db-migrate
272
+ # command: pnpm prisma migrate deploy
273
+ # timeoutMs: 300000 # per-step budget (default 300000)
274
+ # - name: admin-user # after-ready steps run against the running app
275
+ # command: curl -sf -X POST http://127.0.0.1:3000/api/test/bootstrap-admin
276
+ # when: after-ready
277
+ # # Or point at a shared DB you do not control; provenance records it as unpinned.
278
+ # # each name must also be declared in subject.env:
279
+ # # external: [DATABASE_URL]
280
+ # clone:
281
+ # keep: true # keep the sandbox after a failure to debug install or boot
282
+ # Alternative: drive a deployment you own (Vercel preview, staging) instead of a clone:
283
+ # source: app-url
284
+ # appUrl: https://your-preview.vercel.app/
285
+ actor:
286
+ type: openai-computer-use
287
+ persona: synthetic-new-user
288
+ mission: >-
289
+ Explore the app as a brand-new user trying to complete its primary flow. Note anything
290
+ confusing. Stop when the flow completes or you are stuck.
291
+ execution:
292
+ target: e2b-desktop
293
+ # 20 minutes, the longest this route allows, so a session ends when the participant finishes.
294
+ # The route clones, installs, builds and serves the subject before the participant starts, so
295
+ # the sandbox deadline is this budget plus 40 minutes of provisioning and teardown, and a sandbox
296
+ # lives at most 60 minutes. A value above 20 minutes fails the run before it starts; raise
297
+ # execution.desktop.sandboxTimeoutMs if you need more. The dollar caps limit spend.
298
+ timeoutMs: 1200000
299
+ desktop:
300
+ device: desktop # mobile | small-mobile | narrow-mobile | tablet | desktop | wide
301
+ # browser: chrome # default | chrome | chromium | firefox; concrete values fail closed
302
+ # Presets size the physical screen; Chrome's window width has a ~500px minimum.
303
+ # For mobile presets, opt into CSS viewport, touch, DPR and mobile user-agent emulation:
304
+ # fidelity: { mobileEmulation: true } # Chrome/Chromium only; desktop and tablet are unchanged
305
+ # The bundle records measured screen, page viewport and emulation fidelity separately.
306
+ # policies:
307
+ # redactScreenshots: true # blur saved frames (off by default: full fidelity for local use)
308
+ # allowPublicTargets: true # required to drive a non-loopback app-url (a deployment you own)
309
+ defaults:
310
+ open: true
311
+ `,
312
+ },
313
+ {
314
+ path: "humanish/studies/lobby-trivia-3player.yaml",
315
+ plane: "source",
316
+ contents: `schema: humanish.study.v3
317
+ id: lobby-trivia-3player
318
+ title: "Shared world on a public app: three mobile participants in one lobby"
319
+ description: >-
320
+ A worked example of the external-public shared-world route: three participants on mobile layouts
321
+ play the same multiplayer lobby on a public deployment at the same time. The public site is the
322
+ shared plane, so there is no clone, subject sandbox or seed. The host participant creates the
323
+ lobby, humanish reads the session code from the host's page URL, and the other participants get
324
+ it in their missions and join through the app's own join flow. Starts as a dry run, which writes
325
+ one evidence bundle at no cost. A live run needs OPENAI_API_KEY, E2B_API_KEY and mode: live. Point appUrl and publicTarget at a public deployment you own or operate. Mobile layout:
326
+ Chrome's physical window is at least 500px wide and has no touch emulation, desktop pixel ratio
327
+ and a desktop user agent. Set execution.desktop.fidelity.mobileEmulation to true to request the
328
+ preset's CSS viewport, touch, pixel ratio and mobile user agent on Chrome or Chromium; the bundle
329
+ records the measured geometry and emulation fidelity. A run on a public site proves less than a
330
+ provisioned one: its provenance is external-public, it carries no synthetic-data attestation and
331
+ no authoritative shared-state proof, and concurrency rests on overlapping sessions and every
332
+ participant reaching the same lobby.
333
+ route: shared-world
334
+ mode: dry-run # a live run opens three mobile-layout desktops against the public app
335
+ subject:
336
+ source: app-url # the public deployment is the shared plane: no clone and no getHost
337
+ appUrl: https://your-public-app.example/ # a deployment you own or operate
338
+ publicTarget: # required: your attestation that you own or operate this deployment
339
+ owner: your-org/your-app
340
+ authorized: true
341
+ policies:
342
+ allowPublicTargets: true # required: the shared plane is a public, non-loopback deployment
343
+ actor:
344
+ type: openai-computer-use
345
+ mission: You are one of several users on the same shared session at once. Play your role, then stop.
346
+ participants: # at least two participants, exactly one with host: true
347
+ - id: host
348
+ host: true # this participant creates the shared session
349
+ device: mobile # 414x896 preset; physical window width floors to 500 (see emulation above)
350
+ instruction: Create a shared lobby, wait for the others to join, then start and play.
351
+ - id: player-2
352
+ device: mobile
353
+ instruction: Join the lobby you're told the code for, then play.
354
+ - id: player-3
355
+ device: small-mobile
356
+ instruction: Join the lobby you're told the code for, then play.
357
+ execution:
358
+ target: e2b-desktop
359
+ # Each participant's session budget, which also bounds the host's handoff deadline. It has to
360
+ # cover provisioning, joining the shared session and play; 420000 ms (7 minutes) does.
361
+ timeoutMs: 420000
362
+ concurrency: 3 # all three participants at once, the default; lower it only to limit paid desktops
363
+ # desktop:
364
+ # fidelity: { mobileEmulation: true } # opt into mobile CSS viewport, touch, DPR and user agent
365
+ defaults:
366
+ open: true
367
+ `,
368
+ },
369
+ {
370
+ path: "humanish/coverage-map.md",
371
+ plane: "source",
372
+ contents: `# Coverage Map
373
+
374
+ This file should enumerate screens, roles, states, and paths before scenarios are treated as complete.
375
+
376
+ Current starter coverage is intentionally minimal and synthetic.
377
+ `,
378
+ },
379
+ {
380
+ path: "humanish/coverage-matrix.md",
381
+ plane: "source",
382
+ contents: `# Coverage Matrix
383
+
384
+ | Area | Persona | Happy Path | Sad Path | Status |
385
+ | --- | --- | --- | --- | --- |
386
+ | First run | synthetic-new-user | planned | planned | starter |
387
+ | Onboarding | skeptical-power-user | planned | planned | starter |
388
+ `,
389
+ },
390
+ ];
391
+ export const runtimeDirectories = [
392
+ { path: ".humanish/runs", plane: "runtime" },
393
+ { path: ".humanish/cache", plane: "runtime" },
394
+ { path: ".humanish/tmp", plane: "runtime" },
395
+ { path: ".humanish/logs", plane: "runtime" },
396
+ { path: ".humanish/studies", plane: "runtime" },
397
+ { path: ".humanish/local/studies", plane: "runtime" },
398
+ { path: ".humanish/local/personas", plane: "runtime" },
399
+ ];
400
+ export const humanishScripts = {
401
+ humanish: "humanish",
402
+ "humanish:doctor": "humanish doctor",
403
+ "humanish:run": "humanish run --dry-run",
404
+ "humanish:watch": "humanish watch",
405
+ "humanish:watch:ci": "humanish watch --json --no-open",
406
+ "humanish:verify": "humanish verify",
407
+ };
408
+ /**
409
+ * The starter files, with the live study written for the brain this machine can use.
410
+ *
411
+ * `local-agent` is not a variant of the study so much as a different participant: on a machine with
412
+ * a signed-in Codex and no provider key, the study runs with the operator's own agent and needs
413
+ * only E2B. Writing the other one there would hand someone homework instead of a first run.
414
+ */
415
+ export function starterFilesFor(actor, localBrowser = DEFAULT_LOCAL_BROWSER_STARTER, localAgent) {
416
+ return starterFiles.map((file) => file.path === "humanish/studies/local-browser.yaml"
417
+ ? localBrowserStudy(localBrowser)
418
+ : file.path === "humanish/studies/try-live.yaml"
419
+ ? tryLiveStudy(actor, localAgent)
420
+ : file);
421
+ }
@@ -5,7 +5,7 @@ export interface InitOptions {
5
5
  yes?: boolean;
6
6
  /** Injected so a test can decide what credentials this machine appears to have. */
7
7
  env?: NodeJS.ProcessEnv;
8
- /** Configure the starter local-browser lab without making the operator edit YAML. */
8
+ /** Configure the starter local-browser study without making the operator edit YAML. */
9
9
  localBrowser?: {
10
10
  appUrl: string;
11
11
  mission?: string;
@@ -26,7 +26,7 @@ export interface InitResult {
26
26
  changes: InitChange[];
27
27
  warnings: string[];
28
28
  /**
29
- * The next one or two commands, already resolved against THIS machine's credentials. Present on
29
+ * The next one or two commands, already resolved against this machine's credentials. Present on
30
30
  * an applied init; absent on a dry-run or a failure, where there is no "next" yet.
31
31
  */
32
32
  nextSteps?: string[];
@@ -8,6 +8,7 @@ import { assertPreparedSelectedOutputDirectory, prepareContainedOutputDirectory,
8
8
  import { validateCwd } from "../run/project.js";
9
9
  import { pathExists, readTextIfExists, validateInitProjectPaths } from "./init-paths.js";
10
10
  import { planGitignore, planPackageJson } from "./init-plan.js";
11
+ import { otherStudyFiles, STUDY_DIRECTORIES, studyFileStem } from "./files.js";
11
12
  const INIT_RESPONSE_SCHEMA = "humanish.init-result.v1";
12
13
  export async function runInit(options) {
13
14
  const requestedCwd = path.resolve(options.cwd);
@@ -37,9 +38,9 @@ export async function runInit(options) {
37
38
  return failed(initialPathCheck);
38
39
  }
39
40
  await planAgentsFile(preparedProjectRoot, cwd, plan);
40
- // The starter live lab is written for the brain this machine can actually use. Shipping it as
41
+ // The starter live study is written for the brain this machine can actually use. Shipping it as
41
42
  // openai-computer-use on a machine with no provider key but a signed-in Codex would hand someone
42
- // a file that asks for a credential they were just told they do not need (#505).
43
+ // a file that asks for a credential they were just told they do not need.
43
44
  const machine = await firstRunEnvironment(options.env ?? process.env, requestedCwd);
44
45
  const starterActor = starterActorFor(machine);
45
46
  await planStarterFiles(preparedProjectRoot, cwd, starterFilesFor(starterActor, localBrowser.value, starterLocalAgentFor(machine)), options.localBrowser !== undefined, plan);
@@ -77,16 +78,16 @@ export async function runInit(options) {
77
78
  cwd: requestedCwd,
78
79
  changes: plan.changes,
79
80
  warnings: plan.warnings,
80
- // Resolved against THIS machine, because a next step that cannot work is worse than none.
81
+ // Resolved against this machine, because a next step that cannot work is worse than none.
81
82
  ...(mode === "applied" ? { nextSteps: firstRunGuidance(machine) } : {}),
82
83
  };
83
84
  }
84
- // Leave instructions for the NEXT agent. AGENTS.md is the cross-vendor convention (agents.md) —
85
- // Codex, Claude Code, Cursor, Aider and others read it — and increasingly the thing that runs
85
+ // Leave instructions for the next agent. `AGENTS.md` is the cross-vendor convention (agents.md);
86
+ // Codex, Claude Code, Cursor, Aider and others read it, and increasingly the thing that runs
86
87
  // `humanish init` is a coding agent doing setup on someone's behalf. Without this, the agent
87
88
  // that arrives tomorrow finds a humanish/ directory and no idea what to do with it.
88
89
  //
89
- // APPEND-ONLY and idempotent: an existing AGENTS.md is a file someone wrote, so humanish adds its
90
+ // Append-only and idempotent: an existing `AGENTS.md` is a file someone wrote, so humanish adds its
90
91
  // own section once and never rewrites theirs.
91
92
  async function planAgentsFile(preparedProjectRoot, cwd, plan) {
92
93
  const agentsPath = "AGENTS.md";
@@ -136,10 +137,25 @@ async function planStarterFiles(preparedProjectRoot, cwd, files, localBrowserReq
136
137
  const existing = await readTextIfExists(preparedProjectRoot, file.path);
137
138
  if (existing !== null &&
138
139
  localBrowserRequested &&
139
- file.path === "humanish/labs/local-browser.yaml") {
140
- plan.warnings.push("Skipped --local-browser/--local-mission: humanish/labs/local-browser.yaml already exists and init never overwrites it.");
140
+ file.path === "humanish/studies/local-browser.yaml") {
141
+ plan.warnings.push("Skipped --local-browser/--local-mission: humanish/studies/local-browser.yaml already exists and init never overwrites it.");
141
142
  }
142
- if (existing === null) {
143
+ const others = existing === null ? await otherStudyFilesFor(preparedProjectRoot, file.path) : [];
144
+ if (others.length > 0) {
145
+ const stem = studyFileStem(path.basename(file.path));
146
+ plan.changes.push({
147
+ path: file.path,
148
+ action: "skip",
149
+ target: file.plane,
150
+ reason: `${others.join(", ")} already uses the name ${stem}`,
151
+ });
152
+ // humanish no longer reads a labs/ file, but migrate needs its name free to move it.
153
+ const legacy = others.some((other) => other.split(path.sep).includes("labs"));
154
+ plan.warnings.push(legacy
155
+ ? `Skipped ${file.path}: ${others.join(", ")} already uses the name ${stem}. Move it to a studies/ directory; humanish migrate converts and moves a v2 file.`
156
+ : `Skipped ${file.path}: ${others.join(", ")} already uses the name ${stem}, and a second file with that name would make \`run ${stem}\` fail.`);
157
+ }
158
+ else if (existing === null) {
143
159
  plan.changes.push({
144
160
  path: file.path,
145
161
  action: "create",
@@ -172,6 +188,25 @@ async function planStarterFiles(preparedProjectRoot, cwd, files, localBrowserReq
172
188
  }
173
189
  }
174
190
  }
191
+ /**
192
+ * The study files that already use a starter study's name in any study directory, other than the
193
+ * starter's own path. An unsafe path counts as present, so init skips the starter rather than
194
+ * failing.
195
+ */
196
+ async function otherStudyFilesFor(preparedProjectRoot, relativePath) {
197
+ const stem = studyFileStem(path.basename(relativePath));
198
+ const inStudyDirectory = STUDY_DIRECTORIES.some((directory) => directory.relativeDir === path.dirname(relativePath));
199
+ if (stem === undefined || !inStudyDirectory)
200
+ return [];
201
+ return otherStudyFiles(stem, async (candidate) => {
202
+ try {
203
+ return (await readTextIfExists(preparedProjectRoot, candidate)) !== null;
204
+ }
205
+ catch {
206
+ return true;
207
+ }
208
+ }, relativePath);
209
+ }
175
210
  /** Create each ignored runtime directory that is missing. */
176
211
  async function planRuntimeDirectories(preparedProjectRoot, cwd, plan) {
177
212
  for (const directory of runtimeDirectories) {
@@ -236,7 +271,7 @@ function validateLocalBrowserStarter(input) {
236
271
  return { ok: true, value };
237
272
  }
238
273
  /**
239
- * What this machine can run, for the starter lab and the next steps. Local CLI status is
274
+ * What this machine can run, for the starter study and the next steps. Local CLI status is
240
275
  * classified without returning its output or reading its credential file. Provider keys go
241
276
  * through the same discovery chain as every other command (process env, the project overlay,
242
277
  * `e2b auth login`, the `humanish keys set` store) and only their presence is read, never a value.
@@ -254,7 +289,7 @@ async function firstRunEnvironment(env, cwd) {
254
289
  const present = (name) => keys.some((key) => key.name === name && key.source !== null);
255
290
  let hasDesktopSdk = false;
256
291
  try {
257
- // Resolution from the PROJECT, not from wherever humanish itself lives.
292
+ // Resolution starts from the project, wherever humanish itself is installed.
258
293
  const { createRequire } = await import("node:module");
259
294
  createRequire(path.join(process.cwd(), "package.json")).resolve("@e2b/desktop");
260
295
  hasDesktopSdk = true;
@@ -0,0 +1,4 @@
1
+ /** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
2
+ export declare function findUnknownV2Key(raw: unknown): string | undefined;
3
+ /** The first key in a humanish.study.v3 document that the format does not have; undefined if none. */
4
+ export declare function findUnknownStudyKey(raw: unknown): string | undefined;
@@ -1,4 +1,4 @@
1
- // Every mapping key parseLabConfig reads, by path. A key that is not listed fails the lab as
1
+ // Every mapping key parseStudy reads, by path. A key that is not listed fails the study as
2
2
  // unknown, so a typo stops the run instead of being dropped. `true` marks a leaf: its parser
3
3
  // checks the value (scalars, lists, and mappings that check their own keys). Each level satisfies
4
4
  // the parsed type's keys, so a field added to a Lab* interface must be added here too.
@@ -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,
@@ -163,7 +166,7 @@ const EMAIL = {
163
166
  allowedOrigins: true,
164
167
  recipients: { lane: true, address: true },
165
168
  };
166
- const LAB_KEYS = {
169
+ const V2_KEYS = {
167
170
  schema: true,
168
171
  id: true,
169
172
  title: true,
@@ -200,14 +203,45 @@ const LAB_KEYS = {
200
203
  defaults: { open: true },
201
204
  comms: { email: EMAIL },
202
205
  };
203
- /** The first key in `raw` that LAB_KEYS does not list, as an error message; undefined if none. */
204
- export function findUnknownLabKey(raw) {
205
- return walk(raw, LAB_KEYS, "");
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: V2_KEYS.policies,
229
+ review: V2_KEYS.review,
230
+ defaults: V2_KEYS.defaults,
231
+ comms: V2_KEYS.comms,
232
+ };
233
+ /** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
234
+ export function findUnknownV2Key(raw) {
235
+ return walk(raw, V2_KEYS, "", "study");
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} ${unknown.length === 1 ? "field" : "fields"}${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) {