humanish 0.105.0 → 0.106.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 (1408) hide show
  1. package/AGENTS.md +89 -94
  2. package/ARCHITECTURE.md +146 -0
  3. package/CHANGELOG.md +502 -0
  4. package/CONTEXT.md +51 -0
  5. package/CONTRIBUTING.md +111 -9
  6. package/README.md +213 -124
  7. package/SECURITY.md +1 -1
  8. package/TELEMETRY.md +112 -0
  9. package/dist/actors/affordance.d.ts +56 -0
  10. package/dist/actors/affordance.js +155 -0
  11. package/dist/actors/agent-session.js +42 -0
  12. package/dist/actors/codex/app-server-actor-trace.d.ts +11 -0
  13. package/dist/actors/codex/app-server-actor-trace.js +211 -0
  14. package/dist/actors/codex/app-server-client.d.ts +39 -0
  15. package/dist/actors/codex/app-server-client.js +116 -0
  16. package/dist/actors/codex/app-server-trace.d.ts +154 -0
  17. package/dist/actors/codex/app-server-trace.js +424 -0
  18. package/dist/actors/codex/app-server-ui.d.ts +40 -0
  19. package/dist/actors/codex/app-server-ui.js +328 -0
  20. package/dist/actors/codex/app-server.d.ts +44 -0
  21. package/dist/actors/codex/app-server.js +268 -0
  22. package/dist/actors/codex/qualified-versions.d.ts +45 -0
  23. package/dist/actors/codex/qualified-versions.js +87 -0
  24. package/dist/actors/codex/restricted-executable.d.ts +17 -0
  25. package/dist/actors/codex/restricted-executable.js +147 -0
  26. package/dist/actors/codex/restricted-launch.d.ts +116 -0
  27. package/dist/actors/codex/restricted-launch.js +222 -0
  28. package/dist/actors/codex/restricted-participant-policy.d.ts +46 -0
  29. package/dist/actors/codex/restricted-participant-policy.js +82 -0
  30. package/dist/actors/codex/restricted-participant.d.ts +20 -0
  31. package/dist/actors/codex/restricted-participant.js +432 -0
  32. package/dist/actors/codex/restricted-policy.d.ts +70 -0
  33. package/dist/actors/codex/restricted-policy.js +326 -0
  34. package/dist/actors/codex/restricted-session.d.ts +65 -0
  35. package/dist/actors/codex/restricted-session.js +432 -0
  36. package/dist/actors/codex/restricted-transport.d.ts +73 -0
  37. package/dist/actors/codex/restricted-transport.js +335 -0
  38. package/dist/actors/codex/restricted-turn.d.ts +62 -0
  39. package/dist/actors/codex/restricted-turn.js +284 -0
  40. package/dist/actors/computer-use/actor.d.ts +77 -0
  41. package/dist/actors/computer-use/actor.js +78 -0
  42. package/dist/actors/computer-use/admission-limit.d.ts +12 -0
  43. package/dist/actors/computer-use/admission-limit.js +19 -0
  44. package/dist/actors/computer-use/executor-error.js +48 -0
  45. package/dist/actors/computer-use/loop/actions.d.ts +26 -0
  46. package/dist/actors/computer-use/loop/actions.js +245 -0
  47. package/dist/actors/computer-use/loop/backstop.d.ts +42 -0
  48. package/dist/actors/computer-use/loop/backstop.js +202 -0
  49. package/dist/actors/computer-use/loop/debrief.d.ts +19 -0
  50. package/dist/actors/computer-use/loop/debrief.js +164 -0
  51. package/dist/actors/computer-use/loop/ending.d.ts +86 -0
  52. package/dist/actors/computer-use/loop/ending.js +316 -0
  53. package/dist/actors/computer-use/loop/observation.d.ts +55 -0
  54. package/dist/actors/computer-use/loop/observation.js +246 -0
  55. package/dist/actors/computer-use/loop/output-limit.d.ts +11 -0
  56. package/dist/actors/computer-use/loop/output-limit.js +21 -0
  57. package/dist/actors/computer-use/loop/provider-call.d.ts +19 -0
  58. package/dist/actors/computer-use/loop/provider-call.js +208 -0
  59. package/dist/actors/computer-use/loop/race.d.ts +34 -0
  60. package/dist/actors/computer-use/loop/race.js +79 -0
  61. package/dist/actors/computer-use/loop/session.d.ts +77 -0
  62. package/dist/actors/computer-use/loop/session.js +135 -0
  63. package/dist/actors/computer-use/loop/spend.d.ts +30 -0
  64. package/dist/actors/computer-use/loop/spend.js +85 -0
  65. package/dist/actors/computer-use/loop/trace.d.ts +35 -0
  66. package/dist/actors/computer-use/loop/trace.js +140 -0
  67. package/dist/actors/computer-use/loop/types.d.ts +404 -0
  68. package/dist/actors/computer-use/loop/types.js +1 -0
  69. package/dist/actors/computer-use/loop/usage.d.ts +102 -0
  70. package/dist/actors/computer-use/loop/usage.js +355 -0
  71. package/dist/actors/computer-use/loop.d.ts +11 -0
  72. package/dist/actors/computer-use/loop.js +248 -0
  73. package/dist/actors/computer-use/openai-provider.d.ts +107 -0
  74. package/dist/actors/computer-use/openai-provider.js +472 -0
  75. package/dist/actors/computer-use/openai-requests.d.ts +35 -0
  76. package/dist/actors/computer-use/openai-requests.js +74 -0
  77. package/dist/actors/computer-use/openai-wire.d.ts +67 -0
  78. package/dist/actors/computer-use/openai-wire.js +365 -0
  79. package/dist/actors/computer-use/provider-error.d.ts +27 -0
  80. package/dist/actors/computer-use/provider-error.js +86 -0
  81. package/dist/actors/computer-use/speech.js +6 -0
  82. package/dist/actors/contract.d.ts +315 -0
  83. package/dist/actors/contract.js +201 -0
  84. package/dist/actors/goal-source.js +38 -0
  85. package/dist/actors/local-agent/claude-session.d.ts +43 -0
  86. package/dist/actors/local-agent/claude-session.js +370 -0
  87. package/dist/actors/local-agent/cli.d.ts +115 -0
  88. package/dist/actors/local-agent/cli.js +413 -0
  89. package/dist/actors/local-agent/readiness.d.ts +31 -0
  90. package/dist/actors/local-agent/readiness.js +45 -0
  91. package/dist/actors/openai-endpoint.d.ts +2 -0
  92. package/dist/actors/openai-endpoint.js +2 -0
  93. package/dist/actors/output-token-limit.js +4 -0
  94. package/dist/actors/reasoning-effort.js +33 -0
  95. package/dist/actors/registry.d.ts +59 -0
  96. package/dist/actors/registry.js +89 -0
  97. package/dist/actors/scripted-browser/actor.d.ts +49 -0
  98. package/dist/actors/scripted-browser/actor.js +428 -0
  99. package/dist/actors/scripted-browser/browser-command.d.ts +1 -0
  100. package/dist/actors/scripted-browser/browser-command.js +48 -0
  101. package/dist/actors/scripted-browser/journey.d.ts +9 -0
  102. package/dist/actors/scripted-browser/journey.js +171 -0
  103. package/dist/actors/scripted-browser/steps.d.ts +60 -0
  104. package/dist/actors/scripted-browser/steps.js +384 -0
  105. package/dist/actors/scripted-browser/types.d.ts +135 -0
  106. package/dist/actors/scripted-browser/types.js +26 -0
  107. package/dist/actors/stop-cause.d.ts +9 -0
  108. package/dist/actors/stop-cause.js +42 -0
  109. package/dist/actors/stop-conditions.d.ts +40 -0
  110. package/dist/actors/stop-conditions.js +65 -0
  111. package/dist/analysis/automatic-completion.d.ts +38 -0
  112. package/dist/analysis/automatic-completion.js +90 -0
  113. package/dist/analysis/automatic-config.d.ts +41 -0
  114. package/dist/analysis/automatic-config.js +135 -0
  115. package/dist/analysis/automatic.d.ts +17 -0
  116. package/dist/analysis/automatic.js +279 -0
  117. package/dist/analysis/codex-config.d.ts +8 -0
  118. package/dist/analysis/codex-config.js +69 -0
  119. package/dist/analysis/evidence-sources.d.ts +23 -0
  120. package/dist/analysis/evidence-sources.js +251 -0
  121. package/dist/analysis/evidence.d.ts +18 -0
  122. package/dist/analysis/evidence.js +482 -0
  123. package/dist/analysis/execute.d.ts +52 -0
  124. package/dist/analysis/execute.js +504 -0
  125. package/dist/analysis/image-tokens.d.ts +1 -0
  126. package/dist/analysis/image-tokens.js +41 -0
  127. package/dist/analysis/job.d.ts +97 -0
  128. package/dist/analysis/job.js +311 -0
  129. package/dist/analysis/load.d.ts +3 -0
  130. package/dist/analysis/load.js +11 -0
  131. package/dist/analysis/provider.d.ts +57 -0
  132. package/dist/analysis/provider.js +227 -0
  133. package/dist/analysis/restricted-codex.d.ts +19 -0
  134. package/dist/analysis/restricted-codex.js +23 -0
  135. package/dist/analysis/service.d.ts +72 -0
  136. package/dist/analysis/service.js +399 -0
  137. package/dist/analysis/sharing.d.ts +11 -0
  138. package/dist/analysis/sharing.js +44 -0
  139. package/dist/analysis/store-executions.d.ts +27 -0
  140. package/dist/analysis/store-executions.js +224 -0
  141. package/dist/analysis/store.d.ts +31 -0
  142. package/dist/analysis/store.js +315 -0
  143. package/dist/analysis/types.d.ts +217 -0
  144. package/dist/analysis/types.js +12 -0
  145. package/dist/analysis/validation.d.ts +225 -0
  146. package/dist/analysis/validation.js +562 -0
  147. package/dist/browser-control/client.d.ts +18 -0
  148. package/dist/browser-control/client.js +239 -0
  149. package/dist/browser-control/dispatcher.d.ts +19 -0
  150. package/dist/browser-control/dispatcher.js +221 -0
  151. package/dist/browser-control/protocol.d.ts +492 -0
  152. package/dist/browser-control/protocol.js +360 -0
  153. package/dist/browser-control/recording-transfer.d.ts +3 -0
  154. package/dist/browser-control/recording-transfer.js +48 -0
  155. package/dist/browser-control/transport.d.ts +27 -0
  156. package/dist/browser-control/transport.js +189 -0
  157. package/dist/cli/commands/analysis-signals.d.ts +7 -0
  158. package/dist/cli/commands/analysis-signals.js +25 -0
  159. package/dist/cli/commands/analyze.d.ts +3 -0
  160. package/dist/cli/commands/analyze.js +212 -0
  161. package/dist/cli/commands/codex.d.ts +3 -0
  162. package/dist/cli/commands/codex.js +145 -0
  163. package/dist/cli/commands/comms.d.ts +3 -0
  164. package/dist/cli/commands/comms.js +225 -0
  165. package/dist/cli/commands/feedback.d.ts +3 -0
  166. package/dist/cli/commands/feedback.js +138 -0
  167. package/dist/cli/commands/lab-format.d.ts +8 -0
  168. package/dist/cli/commands/lab-format.js +105 -0
  169. package/dist/cli/commands/lab-route-computer-use.d.ts +20 -0
  170. package/dist/cli/commands/lab-route-computer-use.js +251 -0
  171. package/dist/cli/commands/lab-route-open.d.ts +27 -0
  172. package/dist/cli/commands/lab-route-open.js +37 -0
  173. package/dist/cli/commands/lab-route-preview.d.ts +20 -0
  174. package/dist/cli/commands/lab-route-preview.js +74 -0
  175. package/dist/cli/commands/lab-route-run.d.ts +17 -0
  176. package/dist/cli/commands/lab-route-run.js +31 -0
  177. package/dist/cli/commands/lab-route-scripted.d.ts +19 -0
  178. package/dist/cli/commands/lab-route-scripted.js +50 -0
  179. package/dist/cli/commands/lab-route-shared-world.d.ts +20 -0
  180. package/dist/cli/commands/lab-route-shared-world.js +135 -0
  181. package/dist/cli/commands/lab-route-terminal.d.ts +19 -0
  182. package/dist/cli/commands/lab-route-terminal.js +49 -0
  183. package/dist/cli/commands/lab-run.d.ts +9 -0
  184. package/dist/cli/commands/lab-run.js +166 -0
  185. package/dist/cli/commands/lab-scorer.d.ts +30 -0
  186. package/dist/cli/commands/lab-scorer.js +17 -0
  187. package/dist/cli/commands/lab.d.ts +3 -0
  188. package/dist/cli/commands/lab.js +193 -0
  189. package/dist/cli/commands/observe.d.ts +4 -0
  190. package/dist/cli/commands/observe.js +345 -0
  191. package/dist/cli/commands/runs.d.ts +11 -0
  192. package/dist/cli/commands/runs.js +314 -0
  193. package/dist/cli/commands/setup.d.ts +11 -0
  194. package/dist/cli/commands/setup.js +325 -0
  195. package/dist/cli/commands/tui.d.ts +32 -0
  196. package/dist/cli/commands/tui.js +305 -0
  197. package/dist/cli/commands/watch.d.ts +3 -0
  198. package/dist/cli/commands/watch.js +245 -0
  199. package/dist/cli/doctor.d.ts +39 -0
  200. package/dist/cli/doctor.js +263 -0
  201. package/dist/cli/first-run-path.d.ts +67 -0
  202. package/dist/cli/first-run-path.js +150 -0
  203. package/dist/cli/io.d.ts +85 -0
  204. package/dist/cli/io.js +232 -0
  205. package/dist/cli/observer-follow.d.ts +56 -0
  206. package/dist/cli/observer-follow.js +160 -0
  207. package/dist/cli/orientation.d.ts +30 -0
  208. package/dist/cli/orientation.js +114 -0
  209. package/dist/cli/program.d.ts +15 -0
  210. package/dist/cli/program.js +338 -0
  211. package/dist/cli/secret-prompt.js +39 -0
  212. package/dist/cli/telemetry.js +351 -0
  213. package/dist/cli.js +2 -4
  214. package/dist/comms/agentmail.d.ts +29 -0
  215. package/dist/comms/agentmail.js +568 -0
  216. package/dist/comms/capture-surface.d.ts +54 -0
  217. package/dist/comms/capture-surface.js +468 -0
  218. package/dist/comms/catch-host.d.ts +52 -0
  219. package/dist/comms/catch-host.js +192 -0
  220. package/dist/comms/connections.d.ts +20 -0
  221. package/dist/comms/connections.js +116 -0
  222. package/dist/comms/email-catch.d.ts +49 -0
  223. package/dist/comms/email-catch.js +236 -0
  224. package/dist/comms/extract.d.ts +7 -0
  225. package/dist/comms/extract.js +65 -0
  226. package/dist/comms/fake-inbox.d.ts +39 -0
  227. package/dist/comms/fake-inbox.js +136 -0
  228. package/dist/comms/images.d.ts +7 -0
  229. package/dist/comms/images.js +53 -0
  230. package/dist/comms/lease-store.d.ts +91 -0
  231. package/dist/comms/lease-store.js +600 -0
  232. package/dist/comms/providers.d.ts +30 -0
  233. package/dist/comms/providers.js +24 -0
  234. package/dist/comms/receiving-common.d.ts +17 -0
  235. package/dist/comms/receiving-common.js +82 -0
  236. package/dist/comms/receiving-evidence.d.ts +4 -0
  237. package/dist/comms/receiving-evidence.js +59 -0
  238. package/dist/comms/receiving-recovery.d.ts +22 -0
  239. package/dist/comms/receiving-recovery.js +100 -0
  240. package/dist/comms/receiving-render.d.ts +16 -0
  241. package/dist/comms/receiving-render.js +409 -0
  242. package/dist/comms/receiving-runtime.d.ts +41 -0
  243. package/dist/comms/receiving-runtime.js +66 -0
  244. package/dist/comms/receiving-surface.d.ts +8 -0
  245. package/dist/comms/receiving-surface.js +156 -0
  246. package/dist/comms/receiving-types.d.ts +122 -0
  247. package/dist/comms/receiving-types.js +3 -0
  248. package/dist/comms/receiving.d.ts +33 -0
  249. package/dist/comms/receiving.js +508 -0
  250. package/dist/comms/sandbox-catch-script.d.ts +16 -0
  251. package/dist/comms/sandbox-catch-script.js +331 -0
  252. package/dist/comms/sandbox-catch.d.ts +214 -0
  253. package/dist/comms/sandbox-catch.js +451 -0
  254. package/dist/comms/setup.d.ts +65 -0
  255. package/dist/comms/setup.js +259 -0
  256. package/dist/comms/thread-evidence.d.ts +28 -0
  257. package/dist/comms/thread-evidence.js +30 -0
  258. package/dist/comms/types.d.ts +76 -0
  259. package/dist/comms/types.js +8 -0
  260. package/dist/deprecated.d.ts +90 -0
  261. package/dist/deprecated.js +106 -0
  262. package/dist/evidence/browser-hygiene.js +42 -0
  263. package/dist/evidence/desktop-recorder.d.ts +39 -0
  264. package/dist/evidence/desktop-recorder.js +340 -0
  265. package/dist/evidence/desktop-recording-artifact.d.ts +5 -0
  266. package/dist/evidence/desktop-recording-artifact.js +53 -0
  267. package/dist/evidence/desktop-recording-types.d.ts +27 -0
  268. package/dist/evidence/desktop-recording-types.js +16 -0
  269. package/dist/evidence/encoded-text.d.ts +27 -0
  270. package/dist/evidence/encoded-text.js +243 -0
  271. package/dist/evidence/frame-signature.d.ts +17 -0
  272. package/dist/evidence/frame-signature.js +111 -0
  273. package/dist/evidence/image.d.ts +24 -0
  274. package/dist/evidence/image.js +170 -0
  275. package/dist/evidence/plain-text.d.ts +8 -0
  276. package/dist/evidence/plain-text.js +16 -0
  277. package/dist/evidence/redaction.d.ts +110 -0
  278. package/dist/evidence/redaction.js +329 -0
  279. package/dist/feedback/draft.d.ts +58 -0
  280. package/dist/feedback/draft.js +309 -0
  281. package/dist/feedback/export-bundle.d.ts +6 -0
  282. package/dist/feedback/export-bundle.js +585 -0
  283. package/dist/feedback/export.d.ts +50 -0
  284. package/dist/feedback/export.js +392 -0
  285. package/dist/feedback/feedback.d.ts +37 -0
  286. package/dist/feedback/feedback.js +282 -0
  287. package/dist/feedback/proof.d.ts +8 -0
  288. package/dist/feedback/proof.js +56 -0
  289. package/dist/guest/bootstrap.d.ts +55 -0
  290. package/dist/guest/bootstrap.js +348 -0
  291. package/dist/guest/browser-tools.d.ts +8 -0
  292. package/dist/guest/browser-tools.js +72 -0
  293. package/dist/guest/chromium-text.d.ts +62 -0
  294. package/dist/guest/chromium-text.js +386 -0
  295. package/dist/guest/desktop-executor.d.ts +57 -0
  296. package/dist/guest/desktop-executor.js +256 -0
  297. package/dist/guest/desktop-keys.d.ts +8 -0
  298. package/dist/guest/desktop-keys.js +89 -0
  299. package/dist/guest/desktop-media.d.ts +70 -0
  300. package/dist/guest/desktop-media.js +385 -0
  301. package/dist/guest/desktop-native.d.ts +14 -0
  302. package/dist/guest/desktop-native.js +156 -0
  303. package/dist/guest/media-config.d.ts +15 -0
  304. package/dist/guest/media-config.js +16 -0
  305. package/dist/guest/runtime-desktop.d.ts +45 -0
  306. package/dist/guest/runtime-desktop.js +420 -0
  307. package/dist/guest/runtime-processes.d.ts +28 -0
  308. package/dist/guest/runtime-processes.js +94 -0
  309. package/dist/guest/runtime.d.ts +31 -0
  310. package/dist/guest/runtime.js +124 -0
  311. package/dist/guest-media-worker.js +184 -48
  312. package/dist/guest-runtime-main.js +23 -10
  313. package/dist/guest-runtime-revision.js +1 -1
  314. package/dist/index.d.ts +32 -83
  315. package/dist/index.js +21 -42
  316. package/dist/keys/env-file.d.ts +15 -0
  317. package/dist/keys/env-file.js +109 -0
  318. package/dist/keys/key-resolution.d.ts +58 -0
  319. package/dist/keys/key-resolution.js +389 -0
  320. package/dist/lab/adapter-extension.d.ts +75 -0
  321. package/dist/lab/adapter-extension.js +253 -0
  322. package/dist/lab/adapter-scorer-loader.d.ts +43 -0
  323. package/dist/lab/adapter-scorer-loader.js +130 -0
  324. package/dist/lab/bag-overrides.d.ts +18 -0
  325. package/dist/lab/bag-overrides.js +121 -0
  326. package/dist/lab/composition-rules.d.ts +5 -0
  327. package/dist/lab/composition-rules.js +283 -0
  328. package/dist/lab/config.d.ts +7 -0
  329. package/dist/lab/config.js +208 -0
  330. package/dist/lab/device-presets.d.ts +97 -0
  331. package/dist/lab/device-presets.js +87 -0
  332. package/dist/lab/discover.d.ts +65 -0
  333. package/dist/lab/discover.js +422 -0
  334. package/dist/lab/doctor.d.ts +39 -0
  335. package/dist/lab/doctor.js +233 -0
  336. package/dist/lab/init-paths.d.ts +5 -0
  337. package/dist/lab/init-paths.js +89 -0
  338. package/dist/lab/init-plan.d.ts +20 -0
  339. package/dist/lab/init-plan.js +163 -0
  340. package/dist/lab/init-templates.d.ts +28 -0
  341. package/dist/lab/init-templates.js +416 -0
  342. package/dist/lab/init.d.ts +39 -0
  343. package/dist/lab/init.js +287 -0
  344. package/dist/lab/keys.d.ts +2 -0
  345. package/dist/lab/keys.js +264 -0
  346. package/dist/lab/parse/actors.d.ts +14 -0
  347. package/dist/lab/parse/actors.js +453 -0
  348. package/dist/lab/parse/comms.d.ts +12 -0
  349. package/dist/lab/parse/comms.js +246 -0
  350. package/dist/lab/parse/execution.d.ts +19 -0
  351. package/dist/lab/parse/execution.js +356 -0
  352. package/dist/lab/parse/subject-state.d.ts +30 -0
  353. package/dist/lab/parse/subject-state.js +179 -0
  354. package/dist/lab/parse/subject.d.ts +9 -0
  355. package/dist/lab/parse/subject.js +420 -0
  356. package/dist/lab/parse/values.d.ts +17 -0
  357. package/dist/lab/parse/values.js +65 -0
  358. package/dist/lab/participant-assignment.d.ts +12 -0
  359. package/dist/lab/participant-assignment.js +19 -0
  360. package/dist/lab/persona-resolve.d.ts +39 -0
  361. package/dist/lab/persona-resolve.js +118 -0
  362. package/dist/lab/persona.d.ts +39 -0
  363. package/dist/lab/persona.js +192 -0
  364. package/dist/lab/plan-base.d.ts +24 -0
  365. package/dist/lab/plan-base.js +113 -0
  366. package/dist/lab/plan-participants.d.ts +81 -0
  367. package/dist/lab/plan-participants.js +92 -0
  368. package/dist/lab/plan-types.d.ts +269 -0
  369. package/dist/lab/plan-types.js +5 -0
  370. package/dist/lab/plan.d.ts +27 -0
  371. package/dist/lab/plan.js +156 -0
  372. package/dist/lab/preflight-probes.d.ts +3 -0
  373. package/dist/lab/preflight-probes.js +312 -0
  374. package/dist/lab/preflight-result.d.ts +7 -0
  375. package/dist/lab/preflight-result.js +43 -0
  376. package/dist/lab/preflight.d.ts +95 -0
  377. package/dist/lab/preflight.js +117 -0
  378. package/dist/lab/route-inputs.d.ts +17 -0
  379. package/dist/lab/route-inputs.js +95 -0
  380. package/dist/lab/routing.d.ts +81 -0
  381. package/dist/lab/routing.js +214 -0
  382. package/dist/lab/run-lab-events.d.ts +69 -0
  383. package/dist/lab/run-lab-events.js +95 -0
  384. package/dist/lab/run-lab-options.d.ts +82 -0
  385. package/dist/lab/run-lab-options.js +399 -0
  386. package/dist/lab/summary.d.ts +60 -0
  387. package/dist/lab/summary.js +168 -0
  388. package/dist/lab/tasks.d.ts +91 -0
  389. package/dist/lab/tasks.js +139 -0
  390. package/dist/lab/types.d.ts +813 -0
  391. package/dist/lab/types.js +6 -0
  392. package/dist/lab/validation.d.ts +67 -0
  393. package/dist/lab/validation.js +387 -0
  394. package/dist/lab/warnings.d.ts +2 -0
  395. package/dist/lab/warnings.js +338 -0
  396. package/dist/observer/artifact.d.ts +23 -0
  397. package/dist/observer/artifact.js +235 -0
  398. package/dist/observer/data.d.ts +115 -0
  399. package/dist/observer/data.js +258 -0
  400. package/dist/observer/exposure.d.ts +64 -0
  401. package/dist/observer/exposure.js +143 -0
  402. package/dist/observer/http.js +50 -0
  403. package/dist/observer/library.js +190 -0
  404. package/dist/observer/listen.js +85 -0
  405. package/dist/observer/live.d.ts +9 -0
  406. package/dist/observer/live.js +25 -0
  407. package/dist/observer/pinned-files.d.ts +30 -0
  408. package/dist/observer/pinned-files.js +160 -0
  409. package/dist/observer/render.d.ts +69 -0
  410. package/dist/observer/render.js +350 -0
  411. package/dist/observer/run-inventory.d.ts +35 -0
  412. package/dist/observer/run-inventory.js +95 -0
  413. package/dist/observer/run-routes.d.ts +37 -0
  414. package/dist/observer/run-routes.js +373 -0
  415. package/dist/observer/serve.d.ts +97 -0
  416. package/dist/observer/serve.js +305 -0
  417. package/dist/observer/static.js +369 -0
  418. package/dist/observer/tunnel.js +112 -0
  419. package/dist/observer-app.html +9 -20
  420. package/dist/routes/computer-use/bundle-parts.d.ts +50 -0
  421. package/dist/routes/computer-use/bundle-parts.js +177 -0
  422. package/dist/routes/computer-use/bundle.d.ts +56 -0
  423. package/dist/routes/computer-use/bundle.js +184 -0
  424. package/dist/routes/computer-use/diagnostics.d.ts +38 -0
  425. package/dist/routes/computer-use/diagnostics.js +94 -0
  426. package/dist/routes/computer-use/e2b-desktop/comms.d.ts +65 -0
  427. package/dist/routes/computer-use/e2b-desktop/comms.js +225 -0
  428. package/dist/routes/computer-use/e2b-desktop/desktop.d.ts +3 -0
  429. package/dist/routes/computer-use/e2b-desktop/desktop.js +91 -0
  430. package/dist/routes/computer-use/e2b-desktop/fidelity.d.ts +65 -0
  431. package/dist/routes/computer-use/e2b-desktop/fidelity.js +156 -0
  432. package/dist/routes/computer-use/e2b-desktop/prepare.d.ts +18 -0
  433. package/dist/routes/computer-use/e2b-desktop/prepare.js +198 -0
  434. package/dist/routes/computer-use/e2b-desktop/start.d.ts +14 -0
  435. package/dist/routes/computer-use/e2b-desktop/start.js +193 -0
  436. package/dist/routes/computer-use/e2b-desktop/state.d.ts +65 -0
  437. package/dist/routes/computer-use/e2b-desktop/state.js +69 -0
  438. package/dist/routes/computer-use/e2b-desktop/teardown.d.ts +6 -0
  439. package/dist/routes/computer-use/e2b-desktop/teardown.js +152 -0
  440. package/dist/routes/computer-use/external-comms.d.ts +20 -0
  441. package/dist/routes/computer-use/external-comms.js +55 -0
  442. package/dist/routes/computer-use/fanout-bundle.d.ts +11 -0
  443. package/dist/routes/computer-use/fanout-bundle.js +274 -0
  444. package/dist/routes/computer-use/fanout-lanes.d.ts +15 -0
  445. package/dist/routes/computer-use/fanout-lanes.js +302 -0
  446. package/dist/routes/computer-use/in-process-desktop.d.ts +17 -0
  447. package/dist/routes/computer-use/in-process-desktop.js +40 -0
  448. package/dist/routes/computer-use/lanes.d.ts +35 -0
  449. package/dist/routes/computer-use/lanes.js +321 -0
  450. package/dist/routes/computer-use/legacy-lane-spec.d.ts +62 -0
  451. package/dist/routes/computer-use/legacy-lane-spec.js +39 -0
  452. package/dist/routes/computer-use/live-flush.d.ts +42 -0
  453. package/dist/routes/computer-use/live-flush.js +154 -0
  454. package/dist/routes/computer-use/local-tree-pack.d.ts +27 -0
  455. package/dist/routes/computer-use/local-tree-pack.js +47 -0
  456. package/dist/routes/computer-use/local-vm.d.ts +20 -0
  457. package/dist/routes/computer-use/local-vm.js +218 -0
  458. package/dist/routes/computer-use/participant-desktop.d.ts +63 -0
  459. package/dist/routes/computer-use/participant-desktop.js +33 -0
  460. package/dist/routes/computer-use/participant-facts.d.ts +6 -0
  461. package/dist/routes/computer-use/participant-facts.js +22 -0
  462. package/dist/routes/computer-use/participant-model.d.ts +30 -0
  463. package/dist/routes/computer-use/participant-model.js +222 -0
  464. package/dist/routes/computer-use/participant-prompt.d.ts +53 -0
  465. package/dist/routes/computer-use/participant-prompt.js +87 -0
  466. package/dist/routes/computer-use/participant-runs.d.ts +67 -0
  467. package/dist/routes/computer-use/participant-runs.js +255 -0
  468. package/dist/routes/computer-use/plan.d.ts +56 -0
  469. package/dist/routes/computer-use/plan.js +308 -0
  470. package/dist/routes/computer-use/preflight.d.ts +26 -0
  471. package/dist/routes/computer-use/preflight.js +85 -0
  472. package/dist/routes/computer-use/rerun-selection.d.ts +19 -0
  473. package/dist/routes/computer-use/rerun-selection.js +121 -0
  474. package/dist/routes/computer-use/result.d.ts +7 -0
  475. package/dist/routes/computer-use/result.js +292 -0
  476. package/dist/routes/computer-use/route.d.ts +33 -0
  477. package/dist/routes/computer-use/route.js +145 -0
  478. package/dist/routes/computer-use/run-lanes.d.ts +59 -0
  479. package/dist/routes/computer-use/run-lanes.js +135 -0
  480. package/dist/routes/computer-use/self-report.d.ts +21 -0
  481. package/dist/routes/computer-use/self-report.js +231 -0
  482. package/dist/routes/computer-use/setup.d.ts +136 -0
  483. package/dist/routes/computer-use/setup.js +371 -0
  484. package/dist/routes/computer-use/single-bundle.d.ts +97 -0
  485. package/dist/routes/computer-use/single-bundle.js +393 -0
  486. package/dist/routes/computer-use/subject-projection.d.ts +51 -0
  487. package/dist/routes/computer-use/subject-projection.js +159 -0
  488. package/dist/routes/computer-use/types.d.ts +539 -0
  489. package/dist/routes/computer-use/types.js +37 -0
  490. package/dist/routes/preview.d.ts +13 -0
  491. package/dist/routes/preview.js +36 -0
  492. package/dist/routes/scripted/bundle.d.ts +47 -0
  493. package/dist/routes/scripted/bundle.js +195 -0
  494. package/dist/routes/scripted/plan.d.ts +30 -0
  495. package/dist/routes/scripted/plan.js +95 -0
  496. package/dist/routes/scripted/result.d.ts +48 -0
  497. package/dist/routes/scripted/result.js +176 -0
  498. package/dist/routes/scripted/route.d.ts +28 -0
  499. package/dist/routes/scripted/route.js +218 -0
  500. package/dist/routes/scripted/scenario.d.ts +21 -0
  501. package/dist/routes/scripted/scenario.js +102 -0
  502. package/dist/routes/scripted/session-result.d.ts +8 -0
  503. package/dist/routes/scripted/session-result.js +60 -0
  504. package/dist/routes/scripted/setup.d.ts +45 -0
  505. package/dist/routes/scripted/setup.js +126 -0
  506. package/dist/routes/scripted/subject.d.ts +49 -0
  507. package/dist/routes/scripted/subject.js +156 -0
  508. package/dist/routes/scripted/surface-records.d.ts +26 -0
  509. package/dist/routes/scripted/surface-records.js +88 -0
  510. package/dist/routes/scripted/surface-sessions.d.ts +13 -0
  511. package/dist/routes/scripted/surface-sessions.js +40 -0
  512. package/dist/routes/scripted/types.d.ts +88 -0
  513. package/dist/routes/scripted/types.js +3 -0
  514. package/dist/routes/shared-world/bundle.d.ts +20 -0
  515. package/dist/routes/shared-world/bundle.js +446 -0
  516. package/dist/routes/shared-world/checkpoints.d.ts +25 -0
  517. package/dist/routes/shared-world/checkpoints.js +46 -0
  518. package/dist/routes/shared-world/comms.d.ts +68 -0
  519. package/dist/routes/shared-world/comms.js +176 -0
  520. package/dist/routes/shared-world/external-public.d.ts +21 -0
  521. package/dist/routes/shared-world/external-public.js +130 -0
  522. package/dist/routes/shared-world/handoff.d.ts +62 -0
  523. package/dist/routes/shared-world/handoff.js +348 -0
  524. package/dist/routes/shared-world/lobby-code.d.ts +25 -0
  525. package/dist/routes/shared-world/lobby-code.js +152 -0
  526. package/dist/routes/shared-world/plan.d.ts +27 -0
  527. package/dist/routes/shared-world/plan.js +174 -0
  528. package/dist/routes/shared-world/provenance.d.ts +27 -0
  529. package/dist/routes/shared-world/provenance.js +65 -0
  530. package/dist/routes/shared-world/provisioned.d.ts +57 -0
  531. package/dist/routes/shared-world/provisioned.js +505 -0
  532. package/dist/routes/shared-world/result.d.ts +18 -0
  533. package/dist/routes/shared-world/result.js +327 -0
  534. package/dist/routes/shared-world/route.d.ts +21 -0
  535. package/dist/routes/shared-world/route.js +277 -0
  536. package/dist/routes/shared-world/seat-records.d.ts +20 -0
  537. package/dist/routes/shared-world/seat-records.js +213 -0
  538. package/dist/routes/shared-world/seats.d.ts +38 -0
  539. package/dist/routes/shared-world/seats.js +208 -0
  540. package/dist/routes/shared-world/setup.d.ts +48 -0
  541. package/dist/routes/shared-world/setup.js +200 -0
  542. package/dist/routes/shared-world/types.d.ts +354 -0
  543. package/dist/routes/shared-world/types.js +29 -0
  544. package/dist/routes/terminal/adapter.d.ts +37 -0
  545. package/dist/routes/terminal/adapter.js +201 -0
  546. package/dist/routes/terminal/bundle.d.ts +79 -0
  547. package/dist/routes/terminal/bundle.js +305 -0
  548. package/dist/routes/terminal/credentials.d.ts +34 -0
  549. package/dist/routes/terminal/credentials.js +102 -0
  550. package/dist/routes/terminal/dry-run.d.ts +24 -0
  551. package/dist/routes/terminal/dry-run.js +157 -0
  552. package/dist/routes/terminal/encoding.js +79 -0
  553. package/dist/routes/terminal/ledger.d.ts +49 -0
  554. package/dist/routes/terminal/ledger.js +170 -0
  555. package/dist/routes/terminal/lifetime.d.ts +10 -0
  556. package/dist/routes/terminal/lifetime.js +22 -0
  557. package/dist/routes/terminal/live-finish.d.ts +31 -0
  558. package/dist/routes/terminal/live-finish.js +215 -0
  559. package/dist/routes/terminal/live-sandbox.d.ts +69 -0
  560. package/dist/routes/terminal/live-sandbox.js +452 -0
  561. package/dist/routes/terminal/persona.d.ts +20 -0
  562. package/dist/routes/terminal/persona.js +34 -0
  563. package/dist/routes/terminal/plan.d.ts +21 -0
  564. package/dist/routes/terminal/plan.js +115 -0
  565. package/dist/routes/terminal/recorder.d.ts +23 -0
  566. package/dist/routes/terminal/recorder.js +77 -0
  567. package/dist/routes/terminal/result.d.ts +47 -0
  568. package/dist/routes/terminal/result.js +114 -0
  569. package/dist/routes/terminal/route.d.ts +23 -0
  570. package/dist/routes/terminal/route.js +170 -0
  571. package/dist/routes/terminal/runtime-auth.d.ts +12 -0
  572. package/dist/routes/terminal/runtime-auth.js +23 -0
  573. package/dist/routes/terminal/runtime.d.ts +14 -0
  574. package/dist/routes/terminal/runtime.js +47 -0
  575. package/dist/routes/terminal/sandbox.d.ts +35 -0
  576. package/dist/routes/terminal/sandbox.js +126 -0
  577. package/dist/routes/terminal/session.d.ts +25 -0
  578. package/dist/routes/terminal/session.js +199 -0
  579. package/dist/routes/terminal/token-usage.d.ts +15 -0
  580. package/dist/routes/terminal/token-usage.js +93 -0
  581. package/dist/routes/terminal/trace.d.ts +40 -0
  582. package/dist/routes/terminal/trace.js +172 -0
  583. package/dist/routes/terminal/types.d.ts +342 -0
  584. package/dist/routes/terminal/types.js +31 -0
  585. package/dist/run/bundle-shape.d.ts +7 -0
  586. package/dist/run/bundle-shape.js +369 -0
  587. package/dist/run/bundle.d.ts +547 -0
  588. package/dist/run/bundle.js +44 -0
  589. package/dist/run/concurrency.js +24 -0
  590. package/dist/run/contained-output.d.ts +50 -0
  591. package/dist/run/contained-output.js +351 -0
  592. package/dist/run/cost-summary.d.ts +28 -0
  593. package/dist/run/cost-summary.js +213 -0
  594. package/dist/run/costs.js +140 -0
  595. package/dist/run/detail.d.ts +62 -0
  596. package/dist/run/detail.js +139 -0
  597. package/dist/run/dry-run-inputs.d.ts +9 -0
  598. package/dist/run/dry-run-inputs.js +90 -0
  599. package/dist/run/dry-run.d.ts +6 -0
  600. package/dist/run/dry-run.js +362 -0
  601. package/dist/run/feedback-shape.d.ts +7 -0
  602. package/dist/run/feedback-shape.js +89 -0
  603. package/dist/run/git-state.d.ts +35 -0
  604. package/dist/run/git-state.js +316 -0
  605. package/dist/run/git-workspace.d.ts +32 -0
  606. package/dist/run/git-workspace.js +450 -0
  607. package/dist/run/judge.d.ts +210 -0
  608. package/dist/run/judge.js +240 -0
  609. package/dist/run/locate.d.ts +22 -0
  610. package/dist/run/locate.js +122 -0
  611. package/dist/run/outcomes.d.ts +31 -0
  612. package/dist/run/outcomes.js +210 -0
  613. package/dist/run/participant-records.d.ts +28 -0
  614. package/dist/run/participant-records.js +63 -0
  615. package/dist/run/participant.d.ts +29 -0
  616. package/dist/run/participant.js +12 -0
  617. package/dist/run/paths.d.ts +79 -0
  618. package/dist/run/paths.js +356 -0
  619. package/dist/run/preflight-receipts.d.ts +46 -0
  620. package/dist/run/preflight-receipts.js +173 -0
  621. package/dist/run/pricing.d.ts +124 -0
  622. package/dist/run/pricing.js +298 -0
  623. package/dist/run/project.d.ts +8 -0
  624. package/dist/run/project.js +103 -0
  625. package/dist/run/projection.d.ts +184 -0
  626. package/dist/run/projection.js +356 -0
  627. package/dist/run/reclaim.d.ts +41 -0
  628. package/dist/run/reclaim.js +183 -0
  629. package/dist/run/results.d.ts +88 -0
  630. package/dist/run/results.js +3 -0
  631. package/dist/run/run-index.d.ts +107 -0
  632. package/dist/run/run-index.js +243 -0
  633. package/dist/run/run.d.ts +103 -0
  634. package/dist/run/run.js +199 -0
  635. package/dist/run/sandbox-receipts.d.ts +31 -0
  636. package/dist/run/sandbox-receipts.js +49 -0
  637. package/dist/run/shared-world-evidence.d.ts +220 -0
  638. package/dist/run/shared-world-evidence.js +49 -0
  639. package/dist/run/shared-world-shape.d.ts +26 -0
  640. package/dist/run/shared-world-shape.js +181 -0
  641. package/dist/run/stats.d.ts +82 -0
  642. package/dist/run/stats.js +258 -0
  643. package/dist/run/status.d.ts +111 -0
  644. package/dist/run/status.js +176 -0
  645. package/dist/run/stored-runs.d.ts +38 -0
  646. package/dist/run/stored-runs.js +273 -0
  647. package/dist/run/stream-shape.d.ts +4 -0
  648. package/dist/run/stream-shape.js +160 -0
  649. package/dist/run/streams.d.ts +257 -0
  650. package/dist/run/streams.js +12 -0
  651. package/dist/run/study-files.d.ts +35 -0
  652. package/dist/run/study-files.js +164 -0
  653. package/dist/run/terminal-contract.d.ts +25 -0
  654. package/dist/run/terminal-contract.js +47 -0
  655. package/dist/run/text.d.ts +2 -0
  656. package/dist/run/text.js +4 -0
  657. package/dist/run/transient-comms-secrets.js +72 -0
  658. package/dist/run/type-guards.d.ts +6 -0
  659. package/dist/run/type-guards.js +18 -0
  660. package/dist/run-lab.d.ts +110 -0
  661. package/dist/run-lab.js +148 -0
  662. package/dist/subject/clone.d.ts +37 -0
  663. package/dist/subject/clone.js +62 -0
  664. package/dist/subject/desktop-cli.d.ts +17 -0
  665. package/dist/subject/desktop-cli.js +51 -0
  666. package/dist/subject/local-tree-archive.d.ts +82 -0
  667. package/dist/subject/local-tree-archive.js +431 -0
  668. package/dist/subject/local-tree.d.ts +28 -0
  669. package/dist/subject/local-tree.js +58 -0
  670. package/dist/subject/node-bootstrap.d.ts +12 -0
  671. package/dist/subject/node-bootstrap.js +66 -0
  672. package/dist/subject/runtime.d.ts +13 -0
  673. package/dist/subject/runtime.js +50 -0
  674. package/dist/subject/serve.d.ts +39 -0
  675. package/dist/subject/serve.js +249 -0
  676. package/dist/subject/state.d.ts +23 -0
  677. package/dist/subject/state.js +58 -0
  678. package/dist/subject/steps.d.ts +37 -0
  679. package/dist/subject/steps.js +39 -0
  680. package/dist/substrates/command-failure.d.ts +23 -0
  681. package/dist/substrates/command-failure.js +45 -0
  682. package/dist/substrates/desktop-session.d.ts +41 -0
  683. package/dist/substrates/desktop-session.js +59 -0
  684. package/dist/substrates/detached.d.ts +55 -0
  685. package/dist/substrates/detached.js +139 -0
  686. package/dist/substrates/e2b/cdp-probe.d.ts +99 -0
  687. package/dist/substrates/e2b/cdp-probe.js +556 -0
  688. package/dist/substrates/e2b/desktop-browser.d.ts +81 -0
  689. package/dist/substrates/e2b/desktop-browser.js +410 -0
  690. package/dist/substrates/e2b/desktop-cdp.d.ts +77 -0
  691. package/dist/substrates/e2b/desktop-cdp.js +211 -0
  692. package/dist/substrates/e2b/desktop-executor.d.ts +92 -0
  693. package/dist/substrates/e2b/desktop-executor.js +302 -0
  694. package/dist/substrates/e2b/desktop-geometry.d.ts +83 -0
  695. package/dist/substrates/e2b/desktop-geometry.js +331 -0
  696. package/dist/substrates/e2b/desktop-media.d.ts +29 -0
  697. package/dist/substrates/e2b/desktop-media.js +151 -0
  698. package/dist/substrates/e2b/desktop-recording.d.ts +18 -0
  699. package/dist/substrates/e2b/desktop-recording.js +238 -0
  700. package/dist/substrates/e2b/desktop-resources.d.ts +11 -0
  701. package/dist/substrates/e2b/desktop-resources.js +35 -0
  702. package/dist/substrates/e2b/desktop-screenshot-cleanup.d.ts +15 -0
  703. package/dist/substrates/e2b/desktop-screenshot-cleanup.js +66 -0
  704. package/dist/substrates/e2b/lifetime.d.ts +12 -0
  705. package/dist/substrates/e2b/lifetime.js +18 -0
  706. package/dist/substrates/e2b/sandbox.d.ts +102 -0
  707. package/dist/substrates/e2b/sandbox.js +202 -0
  708. package/dist/substrates/e2b/sdk.d.ts +194 -0
  709. package/dist/substrates/e2b/sdk.js +168 -0
  710. package/dist/substrates/e2b/shell.d.ts +10 -0
  711. package/dist/substrates/e2b/shell.js +61 -0
  712. package/dist/substrates/local/captured-inbox.d.ts +6 -0
  713. package/dist/substrates/local/captured-inbox.js +87 -0
  714. package/dist/substrates/local/firecracker-desktop.d.ts +25 -0
  715. package/dist/substrates/local/firecracker-desktop.js +315 -0
  716. package/dist/substrates/local/runtime-config.d.ts +6 -0
  717. package/dist/substrates/local/runtime-config.js +91 -0
  718. package/dist/substrates/local/runtime-host.d.ts +30 -0
  719. package/dist/substrates/local/runtime-host.js +143 -0
  720. package/dist/substrates/local/runtime-release.d.ts +5 -0
  721. package/dist/substrates/local/runtime-release.js +32 -0
  722. package/dist/substrates/local/runtime-ssh.d.ts +9 -0
  723. package/dist/substrates/local/runtime-ssh.js +129 -0
  724. package/dist/substrates/local/runtime.d.ts +25 -0
  725. package/dist/substrates/local/runtime.js +190 -0
  726. package/dist/substrates/shell.d.ts +44 -0
  727. package/dist/substrates/shell.js +22 -0
  728. package/dist/tui/actions.d.ts +39 -0
  729. package/dist/tui/actions.js +203 -0
  730. package/dist/tui/contract.d.ts +144 -0
  731. package/dist/tui/contract.js +52 -0
  732. package/dist/tui/launch.js +172 -0
  733. package/dist/tui/project.d.ts +10 -0
  734. package/dist/tui/project.js +17 -0
  735. package/dist/tui-app.js +125 -125
  736. package/dist/verify/actor.d.ts +27 -0
  737. package/dist/verify/actor.js +292 -0
  738. package/dist/verify/artifacts.d.ts +47 -0
  739. package/dist/verify/artifacts.js +373 -0
  740. package/dist/verify/costs.d.ts +30 -0
  741. package/dist/verify/costs.js +129 -0
  742. package/dist/verify/liveness.d.ts +7 -0
  743. package/dist/verify/liveness.js +72 -0
  744. package/dist/verify/rerun.d.ts +3 -0
  745. package/dist/verify/rerun.js +46 -0
  746. package/dist/verify/shared-world-concurrent.d.ts +11 -0
  747. package/dist/verify/shared-world-concurrent.js +346 -0
  748. package/dist/verify/shared-world.d.ts +8 -0
  749. package/dist/verify/shared-world.js +295 -0
  750. package/dist/verify/subject.d.ts +17 -0
  751. package/dist/verify/subject.js +123 -0
  752. package/dist/verify/verify.d.ts +34 -0
  753. package/dist/verify/verify.js +333 -0
  754. package/docs/README.md +67 -0
  755. package/docs/architecture/actor-contract.md +215 -174
  756. package/docs/architecture/browser-control.md +51 -37
  757. package/docs/architecture/comms-inbox.md +8 -8
  758. package/docs/architecture/desktop-recording.md +2 -2
  759. package/docs/architecture/desktop-sessions.md +33 -21
  760. package/docs/architecture/external-public-shared-world.md +80 -64
  761. package/docs/architecture/github-feedback-loop.md +40 -27
  762. package/docs/architecture/guest-desktop.md +22 -10
  763. package/docs/architecture/local-browser-runtime.md +30 -21
  764. package/docs/architecture/observer-review.md +9 -9
  765. package/docs/architecture/observer.md +53 -57
  766. package/docs/architecture/participant-media.md +3 -3
  767. package/docs/architecture/project-layout.md +33 -137
  768. package/docs/architecture/real-email-receiving.md +7 -6
  769. package/docs/architecture/restricted-codex-analysis.md +211 -25
  770. package/docs/architecture/serve.md +89 -87
  771. package/docs/architecture/state-driven-executor.md +79 -64
  772. package/docs/architecture/task-protocol-support.md +18 -18
  773. package/docs/architecture/terminal-product-lane.md +92 -86
  774. package/docs/contracts/adapter-admission.md +8 -4
  775. package/docs/contracts/adapter-fixtures.md +6 -6
  776. package/docs/contracts/core.md +22 -24
  777. package/docs/contracts/feedback.md +33 -23
  778. package/docs/contracts/policy.md +28 -31
  779. package/docs/contracts/run-bundle.md +168 -82
  780. package/docs/contracts/schemas.md +563 -379
  781. package/docs/contracts/study-analysis.md +2 -2
  782. package/docs/contracts/study-costs.md +12 -12
  783. package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +36 -0
  784. package/docs/decisions/0002-observer-is-one-self-contained-file.md +30 -0
  785. package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +35 -0
  786. package/docs/decisions/0004-participant-desktops-stay-in-the-route.md +31 -0
  787. package/docs/decisions/README.md +16 -0
  788. package/docs/goals/current.md +22 -50
  789. package/docs/principles/actor-fidelity.md +37 -40
  790. package/docs/principles/engineering.md +81 -0
  791. package/docs/principles/invariants-and-defaults.md +48 -45
  792. package/docs/principles/self-driving-harness.md +11 -10
  793. package/docs/principles/three-roles.md +12 -13
  794. package/docs/product/automatic-analysis.md +39 -9
  795. package/docs/product/lobby-trivia-3player-external-public.md +14 -13
  796. package/docs/product/open-source-install-experience.md +34 -38
  797. package/docs/ramp/README.md +91 -218
  798. package/docs/release/open-source-readiness.md +5 -2
  799. package/docs/release/public-readiness-standard.md +1 -1
  800. package/examples/README.md +11 -0
  801. package/examples/participant/README.md +80 -0
  802. package/examples/participant/app.mjs +55 -0
  803. package/examples/participant/run.mjs +116 -0
  804. package/examples/scorer/README.md +29 -0
  805. package/examples/scorer/run.mjs +63 -0
  806. package/examples/scorer/scorer.mjs +20 -0
  807. package/package.json +61 -57
  808. package/skills/humanish/SKILL.md +72 -60
  809. package/skills/humanish/agents/openai.yaml +2 -2
  810. package/dist/actor-contract.d.ts +0 -296
  811. package/dist/actor-contract.js +0 -340
  812. package/dist/actor-contract.js.map +0 -1
  813. package/dist/actor-goal-source.js +0 -32
  814. package/dist/actor-goal-source.js.map +0 -1
  815. package/dist/actor-registry.d.ts +0 -77
  816. package/dist/actor-registry.js +0 -106
  817. package/dist/actor-registry.js.map +0 -1
  818. package/dist/actor-stop-cause.d.ts +0 -9
  819. package/dist/actor-stop-cause.js +0 -42
  820. package/dist/actor-stop-cause.js.map +0 -1
  821. package/dist/adapter-extension.d.ts +0 -76
  822. package/dist/adapter-extension.js +0 -273
  823. package/dist/adapter-extension.js.map +0 -1
  824. package/dist/adapter-scorer-loader.d.ts +0 -42
  825. package/dist/adapter-scorer-loader.js +0 -129
  826. package/dist/adapter-scorer-loader.js.map +0 -1
  827. package/dist/affordance.d.ts +0 -53
  828. package/dist/affordance.js +0 -145
  829. package/dist/affordance.js.map +0 -1
  830. package/dist/agent-session.js +0 -43
  831. package/dist/agent-session.js.map +0 -1
  832. package/dist/argv.d.ts +0 -1
  833. package/dist/argv.js +0 -8
  834. package/dist/argv.js.map +0 -1
  835. package/dist/artifact-reference.d.ts +0 -22
  836. package/dist/artifact-reference.js +0 -47
  837. package/dist/artifact-reference.js.map +0 -1
  838. package/dist/automatic-analysis-completion.d.ts +0 -25
  839. package/dist/automatic-analysis-completion.js +0 -56
  840. package/dist/automatic-analysis-completion.js.map +0 -1
  841. package/dist/automatic-analysis-config.d.ts +0 -38
  842. package/dist/automatic-analysis-config.js +0 -68
  843. package/dist/automatic-analysis-config.js.map +0 -1
  844. package/dist/automatic-study-analysis.d.ts +0 -17
  845. package/dist/automatic-study-analysis.js +0 -195
  846. package/dist/automatic-study-analysis.js.map +0 -1
  847. package/dist/browser-control-client.d.ts +0 -18
  848. package/dist/browser-control-client.js +0 -176
  849. package/dist/browser-control-client.js.map +0 -1
  850. package/dist/browser-control-dispatcher.d.ts +0 -19
  851. package/dist/browser-control-dispatcher.js +0 -147
  852. package/dist/browser-control-dispatcher.js.map +0 -1
  853. package/dist/browser-control-protocol.d.ts +0 -472
  854. package/dist/browser-control-protocol.js +0 -185
  855. package/dist/browser-control-protocol.js.map +0 -1
  856. package/dist/browser-control-recording-transfer.d.ts +0 -4
  857. package/dist/browser-control-recording-transfer.js +0 -42
  858. package/dist/browser-control-recording-transfer.js.map +0 -1
  859. package/dist/browser-control-transport.d.ts +0 -27
  860. package/dist/browser-control-transport.js +0 -177
  861. package/dist/browser-control-transport.js.map +0 -1
  862. package/dist/browser-evidence-hygiene.js +0 -43
  863. package/dist/browser-evidence-hygiene.js.map +0 -1
  864. package/dist/chrome-cdp-probe.d.ts +0 -97
  865. package/dist/chrome-cdp-probe.js +0 -536
  866. package/dist/chrome-cdp-probe.js.map +0 -1
  867. package/dist/claude-agent-sdk.d.ts +0 -94
  868. package/dist/claude-agent-sdk.js +0 -367
  869. package/dist/claude-agent-sdk.js.map +0 -1
  870. package/dist/cli.js.map +0 -1
  871. package/dist/codex-app-server-ui.d.ts +0 -39
  872. package/dist/codex-app-server-ui.js +0 -357
  873. package/dist/codex-app-server-ui.js.map +0 -1
  874. package/dist/codex-app-server.d.ts +0 -144
  875. package/dist/codex-app-server.js +0 -754
  876. package/dist/codex-app-server.js.map +0 -1
  877. package/dist/command-failure.d.ts +0 -43
  878. package/dist/command-failure.js +0 -73
  879. package/dist/command-failure.js.map +0 -1
  880. package/dist/comms-agentmail.d.ts +0 -25
  881. package/dist/comms-agentmail.js +0 -418
  882. package/dist/comms-agentmail.js.map +0 -1
  883. package/dist/comms-catch-host.d.ts +0 -52
  884. package/dist/comms-catch-host.js +0 -178
  885. package/dist/comms-catch-host.js.map +0 -1
  886. package/dist/comms-connections.d.ts +0 -52
  887. package/dist/comms-connections.js +0 -123
  888. package/dist/comms-connections.js.map +0 -1
  889. package/dist/comms-email-catch.d.ts +0 -54
  890. package/dist/comms-email-catch.js +0 -212
  891. package/dist/comms-email-catch.js.map +0 -1
  892. package/dist/comms-evidence.d.ts +0 -27
  893. package/dist/comms-evidence.js +0 -31
  894. package/dist/comms-evidence.js.map +0 -1
  895. package/dist/comms-fake-inbox.d.ts +0 -45
  896. package/dist/comms-fake-inbox.js +0 -184
  897. package/dist/comms-fake-inbox.js.map +0 -1
  898. package/dist/comms-images.d.ts +0 -7
  899. package/dist/comms-images.js +0 -39
  900. package/dist/comms-images.js.map +0 -1
  901. package/dist/comms-inbox.d.ts +0 -66
  902. package/dist/comms-inbox.js +0 -389
  903. package/dist/comms-inbox.js.map +0 -1
  904. package/dist/comms-lease-store.d.ts +0 -91
  905. package/dist/comms-lease-store.js +0 -495
  906. package/dist/comms-lease-store.js.map +0 -1
  907. package/dist/comms-receiving-evidence.d.ts +0 -46
  908. package/dist/comms-receiving-evidence.js +0 -35
  909. package/dist/comms-receiving-evidence.js.map +0 -1
  910. package/dist/comms-receiving-inbox.d.ts +0 -16
  911. package/dist/comms-receiving-inbox.js +0 -410
  912. package/dist/comms-receiving-inbox.js.map +0 -1
  913. package/dist/comms-receiving-runtime.d.ts +0 -25
  914. package/dist/comms-receiving-runtime.js +0 -45
  915. package/dist/comms-receiving-runtime.js.map +0 -1
  916. package/dist/comms-receiving-types.d.ts +0 -98
  917. package/dist/comms-receiving-types.js +0 -2
  918. package/dist/comms-receiving-types.js.map +0 -1
  919. package/dist/comms-receiving.d.ts +0 -58
  920. package/dist/comms-receiving.js +0 -554
  921. package/dist/comms-receiving.js.map +0 -1
  922. package/dist/comms-sandbox-catch.d.ts +0 -229
  923. package/dist/comms-sandbox-catch.js +0 -737
  924. package/dist/comms-sandbox-catch.js.map +0 -1
  925. package/dist/comms-setup.d.ts +0 -40
  926. package/dist/comms-setup.js +0 -121
  927. package/dist/comms-setup.js.map +0 -1
  928. package/dist/comms-types.d.ts +0 -72
  929. package/dist/comms-types.js +0 -10
  930. package/dist/comms-types.js.map +0 -1
  931. package/dist/computer-use-actor.d.ts +0 -76
  932. package/dist/computer-use-actor.js +0 -68
  933. package/dist/computer-use-actor.js.map +0 -1
  934. package/dist/computer-use.d.ts +0 -429
  935. package/dist/computer-use.js +0 -1751
  936. package/dist/computer-use.js.map +0 -1
  937. package/dist/concurrency.js +0 -27
  938. package/dist/concurrency.js.map +0 -1
  939. package/dist/concurrent-shared-world-lab.d.ts +0 -186
  940. package/dist/concurrent-shared-world-lab.js +0 -2181
  941. package/dist/concurrent-shared-world-lab.js.map +0 -1
  942. package/dist/core/git-state.d.ts +0 -33
  943. package/dist/core/git-state.js +0 -277
  944. package/dist/core/git-state.js.map +0 -1
  945. package/dist/core/git-workspace.d.ts +0 -31
  946. package/dist/core/git-workspace.js +0 -447
  947. package/dist/core/git-workspace.js.map +0 -1
  948. package/dist/core/index.d.ts +0 -4
  949. package/dist/core/index.js +0 -3
  950. package/dist/core/index.js.map +0 -1
  951. package/dist/core/run-primitives.d.ts +0 -66
  952. package/dist/core/run-primitives.js +0 -120
  953. package/dist/core/run-primitives.js.map +0 -1
  954. package/dist/cua-actor-lab.d.ts +0 -855
  955. package/dist/cua-actor-lab.js +0 -3543
  956. package/dist/cua-actor-lab.js.map +0 -1
  957. package/dist/cua-admission-limit.d.ts +0 -12
  958. package/dist/cua-admission-limit.js +0 -20
  959. package/dist/cua-admission-limit.js.map +0 -1
  960. package/dist/cua-desktop-lane.d.ts +0 -35
  961. package/dist/cua-desktop-lane.js +0 -13
  962. package/dist/cua-desktop-lane.js.map +0 -1
  963. package/dist/cua-diagnostics.d.ts +0 -38
  964. package/dist/cua-diagnostics.js +0 -68
  965. package/dist/cua-diagnostics.js.map +0 -1
  966. package/dist/cua-executor-error.js +0 -48
  967. package/dist/cua-executor-error.js.map +0 -1
  968. package/dist/cua-provider-error.d.ts +0 -16
  969. package/dist/cua-provider-error.js +0 -35
  970. package/dist/cua-provider-error.js.map +0 -1
  971. package/dist/cua-speech.js +0 -2
  972. package/dist/cua-speech.js.map +0 -1
  973. package/dist/desktop-recorder.d.ts +0 -39
  974. package/dist/desktop-recorder.js +0 -226
  975. package/dist/desktop-recorder.js.map +0 -1
  976. package/dist/desktop-recording-artifact.d.ts +0 -5
  977. package/dist/desktop-recording-artifact.js +0 -54
  978. package/dist/desktop-recording-artifact.js.map +0 -1
  979. package/dist/desktop-recording-types.d.ts +0 -24
  980. package/dist/desktop-recording-types.js +0 -14
  981. package/dist/desktop-recording-types.js.map +0 -1
  982. package/dist/desktop-session.d.ts +0 -41
  983. package/dist/desktop-session.js +0 -47
  984. package/dist/desktop-session.js.map +0 -1
  985. package/dist/device-presets.d.ts +0 -67
  986. package/dist/device-presets.js +0 -55
  987. package/dist/device-presets.js.map +0 -1
  988. package/dist/doctor-lab.d.ts +0 -38
  989. package/dist/doctor-lab.js +0 -126
  990. package/dist/doctor-lab.js.map +0 -1
  991. package/dist/e2b-cua-desktop.d.ts +0 -3
  992. package/dist/e2b-cua-desktop.js +0 -708
  993. package/dist/e2b-cua-desktop.js.map +0 -1
  994. package/dist/e2b-cua-provisioning.d.ts +0 -314
  995. package/dist/e2b-cua-provisioning.js +0 -1219
  996. package/dist/e2b-cua-provisioning.js.map +0 -1
  997. package/dist/e2b-desktop-executor.d.ts +0 -101
  998. package/dist/e2b-desktop-executor.js +0 -297
  999. package/dist/e2b-desktop-executor.js.map +0 -1
  1000. package/dist/e2b-desktop-launch.d.ts +0 -243
  1001. package/dist/e2b-desktop-launch.js +0 -234
  1002. package/dist/e2b-desktop-launch.js.map +0 -1
  1003. package/dist/e2b-desktop-media.d.ts +0 -14
  1004. package/dist/e2b-desktop-media.js +0 -58
  1005. package/dist/e2b-desktop-media.js.map +0 -1
  1006. package/dist/e2b-desktop-recording.d.ts +0 -16
  1007. package/dist/e2b-desktop-recording.js +0 -162
  1008. package/dist/e2b-desktop-recording.js.map +0 -1
  1009. package/dist/e2b-desktop-resources.d.ts +0 -11
  1010. package/dist/e2b-desktop-resources.js +0 -33
  1011. package/dist/e2b-desktop-resources.js.map +0 -1
  1012. package/dist/e2b-desktop-screenshot-cleanup.d.ts +0 -15
  1013. package/dist/e2b-desktop-screenshot-cleanup.js +0 -67
  1014. package/dist/e2b-desktop-screenshot-cleanup.js.map +0 -1
  1015. package/dist/e2b-desktop-session.d.ts +0 -7
  1016. package/dist/e2b-desktop-session.js +0 -29
  1017. package/dist/e2b-desktop-session.js.map +0 -1
  1018. package/dist/e2b-detached.d.ts +0 -53
  1019. package/dist/e2b-detached.js +0 -130
  1020. package/dist/e2b-detached.js.map +0 -1
  1021. package/dist/e2b-terminal-lab.d.ts +0 -383
  1022. package/dist/e2b-terminal-lab.js +0 -2000
  1023. package/dist/e2b-terminal-lab.js.map +0 -1
  1024. package/dist/env-file.d.ts +0 -14
  1025. package/dist/env-file.js +0 -108
  1026. package/dist/env-file.js.map +0 -1
  1027. package/dist/export-bundle.d.ts +0 -7
  1028. package/dist/export-bundle.js +0 -441
  1029. package/dist/export-bundle.js.map +0 -1
  1030. package/dist/export.d.ts +0 -49
  1031. package/dist/export.js +0 -308
  1032. package/dist/export.js.map +0 -1
  1033. package/dist/feedback-proof.d.ts +0 -8
  1034. package/dist/feedback-proof.js +0 -44
  1035. package/dist/feedback-proof.js.map +0 -1
  1036. package/dist/feedback.d.ts +0 -76
  1037. package/dist/feedback.js +0 -496
  1038. package/dist/feedback.js.map +0 -1
  1039. package/dist/first-run-path.d.ts +0 -55
  1040. package/dist/first-run-path.js +0 -136
  1041. package/dist/first-run-path.js.map +0 -1
  1042. package/dist/frame-signature.d.ts +0 -24
  1043. package/dist/frame-signature.js +0 -128
  1044. package/dist/frame-signature.js.map +0 -1
  1045. package/dist/guest-bootstrap.d.ts +0 -55
  1046. package/dist/guest-bootstrap.js +0 -285
  1047. package/dist/guest-bootstrap.js.map +0 -1
  1048. package/dist/guest-browser-tools.d.ts +0 -8
  1049. package/dist/guest-browser-tools.js +0 -66
  1050. package/dist/guest-browser-tools.js.map +0 -1
  1051. package/dist/guest-chromium-text.d.ts +0 -27
  1052. package/dist/guest-chromium-text.js +0 -281
  1053. package/dist/guest-chromium-text.js.map +0 -1
  1054. package/dist/guest-desktop-executor.d.ts +0 -22
  1055. package/dist/guest-desktop-executor.js +0 -178
  1056. package/dist/guest-desktop-executor.js.map +0 -1
  1057. package/dist/guest-desktop-media.d.ts +0 -33
  1058. package/dist/guest-desktop-media.js +0 -281
  1059. package/dist/guest-desktop-media.js.map +0 -1
  1060. package/dist/guest-desktop-native.d.ts +0 -14
  1061. package/dist/guest-desktop-native.js +0 -131
  1062. package/dist/guest-desktop-native.js.map +0 -1
  1063. package/dist/guest-media-config.d.ts +0 -15
  1064. package/dist/guest-media-config.js +0 -8
  1065. package/dist/guest-media-config.js.map +0 -1
  1066. package/dist/guest-media-worker.js.map +0 -1
  1067. package/dist/guest-runtime-desktop.d.ts +0 -42
  1068. package/dist/guest-runtime-desktop.js +0 -314
  1069. package/dist/guest-runtime-desktop.js.map +0 -1
  1070. package/dist/guest-runtime-main.js.map +0 -1
  1071. package/dist/guest-runtime-revision.js.map +0 -1
  1072. package/dist/guest-runtime.d.ts +0 -31
  1073. package/dist/guest-runtime.js +0 -99
  1074. package/dist/guest-runtime.js.map +0 -1
  1075. package/dist/image-evidence.d.ts +0 -2
  1076. package/dist/image-evidence.js +0 -47
  1077. package/dist/image-evidence.js.map +0 -1
  1078. package/dist/index.js.map +0 -1
  1079. package/dist/init-templates.d.ts +0 -25
  1080. package/dist/init-templates.js +0 -541
  1081. package/dist/init-templates.js.map +0 -1
  1082. package/dist/init.d.ts +0 -38
  1083. package/dist/init.js +0 -532
  1084. package/dist/init.js.map +0 -1
  1085. package/dist/key-resolution.d.ts +0 -67
  1086. package/dist/key-resolution.js +0 -375
  1087. package/dist/key-resolution.js.map +0 -1
  1088. package/dist/lab-app-runner.d.ts +0 -78
  1089. package/dist/lab-app-runner.js +0 -403
  1090. package/dist/lab-app-runner.js.map +0 -1
  1091. package/dist/lab-config.d.ts +0 -968
  1092. package/dist/lab-config.js +0 -2714
  1093. package/dist/lab-config.js.map +0 -1
  1094. package/dist/lab-engine.d.ts +0 -97
  1095. package/dist/lab-engine.js +0 -285
  1096. package/dist/lab-engine.js.map +0 -1
  1097. package/dist/lab-preflight.d.ts +0 -70
  1098. package/dist/lab-preflight.js +0 -388
  1099. package/dist/lab-preflight.js.map +0 -1
  1100. package/dist/lab-summary.d.ts +0 -57
  1101. package/dist/lab-summary.js +0 -126
  1102. package/dist/lab-summary.js.map +0 -1
  1103. package/dist/labs.d.ts +0 -66
  1104. package/dist/labs.js +0 -401
  1105. package/dist/labs.js.map +0 -1
  1106. package/dist/listen.js +0 -84
  1107. package/dist/listen.js.map +0 -1
  1108. package/dist/local-agent-claude-session.d.ts +0 -45
  1109. package/dist/local-agent-claude-session.js +0 -203
  1110. package/dist/local-agent-claude-session.js.map +0 -1
  1111. package/dist/local-agent-cli.d.ts +0 -121
  1112. package/dist/local-agent-cli.js +0 -427
  1113. package/dist/local-agent-cli.js.map +0 -1
  1114. package/dist/local-captured-inbox.d.ts +0 -6
  1115. package/dist/local-captured-inbox.js +0 -65
  1116. package/dist/local-captured-inbox.js.map +0 -1
  1117. package/dist/local-firecracker-desktop.d.ts +0 -23
  1118. package/dist/local-firecracker-desktop.js +0 -191
  1119. package/dist/local-firecracker-desktop.js.map +0 -1
  1120. package/dist/local-firecracker-study.d.ts +0 -9
  1121. package/dist/local-firecracker-study.js +0 -135
  1122. package/dist/local-firecracker-study.js.map +0 -1
  1123. package/dist/local-runtime-config.d.ts +0 -6
  1124. package/dist/local-runtime-config.js +0 -60
  1125. package/dist/local-runtime-config.js.map +0 -1
  1126. package/dist/local-runtime-host.d.ts +0 -31
  1127. package/dist/local-runtime-host.js +0 -128
  1128. package/dist/local-runtime-host.js.map +0 -1
  1129. package/dist/local-runtime-release.d.ts +0 -7
  1130. package/dist/local-runtime-release.js +0 -33
  1131. package/dist/local-runtime-release.js.map +0 -1
  1132. package/dist/local-runtime-ssh.d.ts +0 -27
  1133. package/dist/local-runtime-ssh.js +0 -91
  1134. package/dist/local-runtime-ssh.js.map +0 -1
  1135. package/dist/local-runtime.d.ts +0 -25
  1136. package/dist/local-runtime.js +0 -133
  1137. package/dist/local-runtime.js.map +0 -1
  1138. package/dist/observer-data.d.ts +0 -114
  1139. package/dist/observer-data.js +0 -224
  1140. package/dist/observer-data.js.map +0 -1
  1141. package/dist/observer-library.js +0 -191
  1142. package/dist/observer-library.js.map +0 -1
  1143. package/dist/observer-serve.d.ts +0 -99
  1144. package/dist/observer-serve.js +0 -284
  1145. package/dist/observer-serve.js.map +0 -1
  1146. package/dist/observer-static.js +0 -357
  1147. package/dist/observer-static.js.map +0 -1
  1148. package/dist/observer.d.ts +0 -118
  1149. package/dist/observer.js +0 -947
  1150. package/dist/observer.js.map +0 -1
  1151. package/dist/openai-responses-cu.d.ts +0 -158
  1152. package/dist/openai-responses-cu.js +0 -714
  1153. package/dist/openai-responses-cu.js.map +0 -1
  1154. package/dist/orientation.d.ts +0 -29
  1155. package/dist/orientation.js +0 -95
  1156. package/dist/orientation.js.map +0 -1
  1157. package/dist/oss-lab.d.ts +0 -55
  1158. package/dist/oss-lab.js +0 -344
  1159. package/dist/oss-lab.js.map +0 -1
  1160. package/dist/oss-meta-lab-scoring.d.ts +0 -15
  1161. package/dist/oss-meta-lab-scoring.js +0 -145
  1162. package/dist/oss-meta-lab-scoring.js.map +0 -1
  1163. package/dist/oss-meta-lab.d.ts +0 -287
  1164. package/dist/oss-meta-lab.js +0 -4551
  1165. package/dist/oss-meta-lab.js.map +0 -1
  1166. package/dist/oss-remote-telemetry.d.ts +0 -77
  1167. package/dist/oss-remote-telemetry.js +0 -403
  1168. package/dist/oss-remote-telemetry.js.map +0 -1
  1169. package/dist/output-token-limit.js +0 -5
  1170. package/dist/output-token-limit.js.map +0 -1
  1171. package/dist/participant-assignment.d.ts +0 -12
  1172. package/dist/participant-assignment.js +0 -15
  1173. package/dist/participant-assignment.js.map +0 -1
  1174. package/dist/persona-resolve.d.ts +0 -44
  1175. package/dist/persona-resolve.js +0 -99
  1176. package/dist/persona-resolve.js.map +0 -1
  1177. package/dist/persona.d.ts +0 -39
  1178. package/dist/persona.js +0 -144
  1179. package/dist/persona.js.map +0 -1
  1180. package/dist/pi-agent-core.d.ts +0 -77
  1181. package/dist/pi-agent-core.js +0 -207
  1182. package/dist/pi-agent-core.js.map +0 -1
  1183. package/dist/pricing.d.ts +0 -145
  1184. package/dist/pricing.js +0 -277
  1185. package/dist/pricing.js.map +0 -1
  1186. package/dist/program.d.ts +0 -96
  1187. package/dist/program.js +0 -4030
  1188. package/dist/program.js.map +0 -1
  1189. package/dist/reasoning-effort.js +0 -26
  1190. package/dist/reasoning-effort.js.map +0 -1
  1191. package/dist/reclaim.d.ts +0 -31
  1192. package/dist/reclaim.js +0 -90
  1193. package/dist/reclaim.js.map +0 -1
  1194. package/dist/redaction.d.ts +0 -90
  1195. package/dist/redaction.js +0 -294
  1196. package/dist/redaction.js.map +0 -1
  1197. package/dist/restricted-codex-analysis.d.ts +0 -15
  1198. package/dist/restricted-codex-analysis.js +0 -13
  1199. package/dist/restricted-codex-analysis.js.map +0 -1
  1200. package/dist/restricted-codex-participant-policy.d.ts +0 -44
  1201. package/dist/restricted-codex-participant-policy.js +0 -43
  1202. package/dist/restricted-codex-participant-policy.js.map +0 -1
  1203. package/dist/restricted-codex-participant-run.d.ts +0 -20
  1204. package/dist/restricted-codex-participant-run.js +0 -79
  1205. package/dist/restricted-codex-participant-run.js.map +0 -1
  1206. package/dist/restricted-codex-participant.d.ts +0 -20
  1207. package/dist/restricted-codex-participant.js +0 -250
  1208. package/dist/restricted-codex-participant.js.map +0 -1
  1209. package/dist/restricted-codex-policy.d.ts +0 -64
  1210. package/dist/restricted-codex-policy.js +0 -176
  1211. package/dist/restricted-codex-policy.js.map +0 -1
  1212. package/dist/restricted-codex-session.d.ts +0 -44
  1213. package/dist/restricted-codex-session.js +0 -657
  1214. package/dist/restricted-codex-session.js.map +0 -1
  1215. package/dist/restricted-codex-transport.d.ts +0 -69
  1216. package/dist/restricted-codex-transport.js +0 -296
  1217. package/dist/restricted-codex-transport.js.map +0 -1
  1218. package/dist/run-detail.d.ts +0 -61
  1219. package/dist/run-detail.js +0 -137
  1220. package/dist/run-detail.js.map +0 -1
  1221. package/dist/run-index.d.ts +0 -71
  1222. package/dist/run-index.js +0 -206
  1223. package/dist/run-index.js.map +0 -1
  1224. package/dist/run-narration-secrets.js +0 -68
  1225. package/dist/run-narration-secrets.js.map +0 -1
  1226. package/dist/run-paths.d.ts +0 -56
  1227. package/dist/run-paths.js +0 -355
  1228. package/dist/run-paths.js.map +0 -1
  1229. package/dist/run-projection.d.ts +0 -186
  1230. package/dist/run-projection.js +0 -350
  1231. package/dist/run-projection.js.map +0 -1
  1232. package/dist/run-status.d.ts +0 -139
  1233. package/dist/run-status.js +0 -218
  1234. package/dist/run-status.js.map +0 -1
  1235. package/dist/run.d.ts +0 -1255
  1236. package/dist/run.js +0 -5949
  1237. package/dist/run.js.map +0 -1
  1238. package/dist/sandbox-receipts.d.ts +0 -19
  1239. package/dist/sandbox-receipts.js +0 -51
  1240. package/dist/sandbox-receipts.js.map +0 -1
  1241. package/dist/screenshot-image.d.ts +0 -11
  1242. package/dist/screenshot-image.js +0 -26
  1243. package/dist/screenshot-image.js.map +0 -1
  1244. package/dist/scripted-browser-actor.d.ts +0 -297
  1245. package/dist/scripted-browser-actor.js +0 -1399
  1246. package/dist/scripted-browser-actor.js.map +0 -1
  1247. package/dist/scripted-browser-lab.d.ts +0 -121
  1248. package/dist/scripted-browser-lab.js +0 -852
  1249. package/dist/scripted-browser-lab.js.map +0 -1
  1250. package/dist/secret-prompt.js +0 -36
  1251. package/dist/secret-prompt.js.map +0 -1
  1252. package/dist/selected-output-paths.d.ts +0 -52
  1253. package/dist/selected-output-paths.js +0 -366
  1254. package/dist/selected-output-paths.js.map +0 -1
  1255. package/dist/serve-exposure.d.ts +0 -62
  1256. package/dist/serve-exposure.js +0 -129
  1257. package/dist/serve-exposure.js.map +0 -1
  1258. package/dist/serve-http.js +0 -45
  1259. package/dist/serve-http.js.map +0 -1
  1260. package/dist/serve-tunnel.js +0 -113
  1261. package/dist/serve-tunnel.js.map +0 -1
  1262. package/dist/shared-world-lab.d.ts +0 -269
  1263. package/dist/shared-world-lab.js +0 -1424
  1264. package/dist/shared-world-lab.js.map +0 -1
  1265. package/dist/source-archive.d.ts +0 -82
  1266. package/dist/source-archive.js +0 -408
  1267. package/dist/source-archive.js.map +0 -1
  1268. package/dist/stats.d.ts +0 -82
  1269. package/dist/stats.js +0 -219
  1270. package/dist/stats.js.map +0 -1
  1271. package/dist/stop-conditions.d.ts +0 -39
  1272. package/dist/stop-conditions.js +0 -66
  1273. package/dist/stop-conditions.js.map +0 -1
  1274. package/dist/study-analysis-codex-config.d.ts +0 -11
  1275. package/dist/study-analysis-codex-config.js +0 -34
  1276. package/dist/study-analysis-codex-config.js.map +0 -1
  1277. package/dist/study-analysis-engine.d.ts +0 -47
  1278. package/dist/study-analysis-engine.js +0 -327
  1279. package/dist/study-analysis-engine.js.map +0 -1
  1280. package/dist/study-analysis-evidence.d.ts +0 -27
  1281. package/dist/study-analysis-evidence.js +0 -660
  1282. package/dist/study-analysis-evidence.js.map +0 -1
  1283. package/dist/study-analysis-job.d.ts +0 -97
  1284. package/dist/study-analysis-job.js +0 -214
  1285. package/dist/study-analysis-job.js.map +0 -1
  1286. package/dist/study-analysis-provider.d.ts +0 -40
  1287. package/dist/study-analysis-provider.js +0 -170
  1288. package/dist/study-analysis-provider.js.map +0 -1
  1289. package/dist/study-analysis-service.d.ts +0 -59
  1290. package/dist/study-analysis-service.js +0 -257
  1291. package/dist/study-analysis-service.js.map +0 -1
  1292. package/dist/study-analysis-sharing.d.ts +0 -11
  1293. package/dist/study-analysis-sharing.js +0 -35
  1294. package/dist/study-analysis-sharing.js.map +0 -1
  1295. package/dist/study-analysis-store.d.ts +0 -47
  1296. package/dist/study-analysis-store.js +0 -493
  1297. package/dist/study-analysis-store.js.map +0 -1
  1298. package/dist/study-analysis-validation.d.ts +0 -618
  1299. package/dist/study-analysis-validation.js +0 -384
  1300. package/dist/study-analysis-validation.js.map +0 -1
  1301. package/dist/study-analysis.d.ts +0 -206
  1302. package/dist/study-analysis.js +0 -4
  1303. package/dist/study-analysis.js.map +0 -1
  1304. package/dist/study-costs.js +0 -114
  1305. package/dist/study-costs.js.map +0 -1
  1306. package/dist/subject-runtime.d.ts +0 -23
  1307. package/dist/subject-runtime.js +0 -70
  1308. package/dist/subject-runtime.js.map +0 -1
  1309. package/dist/tasks.d.ts +0 -90
  1310. package/dist/tasks.js +0 -144
  1311. package/dist/tasks.js.map +0 -1
  1312. package/dist/telemetry.js +0 -324
  1313. package/dist/telemetry.js.map +0 -1
  1314. package/dist/terminal-agent-actor.d.ts +0 -41
  1315. package/dist/terminal-agent-actor.js +0 -25
  1316. package/dist/terminal-agent-actor.js.map +0 -1
  1317. package/dist/terminal-encoding.js +0 -80
  1318. package/dist/terminal-encoding.js.map +0 -1
  1319. package/dist/terminal-node-bootstrap.d.ts +0 -4
  1320. package/dist/terminal-node-bootstrap.js +0 -58
  1321. package/dist/terminal-node-bootstrap.js.map +0 -1
  1322. package/dist/terminal-participant-activity.d.ts +0 -6
  1323. package/dist/terminal-participant-activity.js +0 -34
  1324. package/dist/terminal-participant-activity.js.map +0 -1
  1325. package/dist/terminal-runtime-auth.d.ts +0 -13
  1326. package/dist/terminal-runtime-auth.js +0 -24
  1327. package/dist/terminal-runtime-auth.js.map +0 -1
  1328. package/dist/terminal-runtime.d.ts +0 -16
  1329. package/dist/terminal-runtime.js +0 -42
  1330. package/dist/terminal-runtime.js.map +0 -1
  1331. package/dist/terminal-token-usage.d.ts +0 -15
  1332. package/dist/terminal-token-usage.js +0 -94
  1333. package/dist/terminal-token-usage.js.map +0 -1
  1334. package/dist/tui-actions.d.ts +0 -39
  1335. package/dist/tui-actions.js +0 -178
  1336. package/dist/tui-actions.js.map +0 -1
  1337. package/dist/tui-contract.d.ts +0 -141
  1338. package/dist/tui-contract.js +0 -54
  1339. package/dist/tui-contract.js.map +0 -1
  1340. package/dist/tui-launch.js +0 -167
  1341. package/dist/tui-launch.js.map +0 -1
  1342. package/dist/tui-project.d.ts +0 -9
  1343. package/dist/tui-project.js +0 -18
  1344. package/dist/tui-project.js.map +0 -1
  1345. package/docs/architecture/examples/state-driven-local-app/README.md +0 -80
  1346. package/docs/architecture/examples/state-driven-local-app/app.mjs +0 -48
  1347. package/docs/architecture/examples/state-driven-local-app/runner.mjs +0 -105
  1348. package/docs/architecture/local-codex-tui-actor.md +0 -213
  1349. package/docs/architecture/oss-lab-poc.md +0 -250
  1350. package/docs/architecture/runtime-broker-core.md +0 -30
  1351. package/docs/release/0.100.0-local-setup.md +0 -31
  1352. package/docs/release/0.100.1-participant-continuity.md +0 -20
  1353. package/docs/release/0.101.0-codex-ui-tools.md +0 -28
  1354. package/docs/release/0.102.0-participant-media.md +0 -43
  1355. package/docs/release/0.103.0-desktop-recording.md +0 -29
  1356. package/docs/release/0.103.1-analysis-capture-allocation.md +0 -18
  1357. package/docs/release/0.103.2-analysis-ending-context.md +0 -21
  1358. package/docs/release/0.104.0-local-captured-inboxes.md +0 -29
  1359. package/docs/release/0.104.1-recording-startup.md +0 -24
  1360. package/docs/release/0.104.2-recording-timestamps.md +0 -32
  1361. package/docs/release/0.104.3-local-smtp-catch.md +0 -15
  1362. package/docs/release/0.105.0-participant-backgrounds.md +0 -50
  1363. package/docs/release/0.20.0-external-public-shared-world.md +0 -43
  1364. package/docs/release/0.84.0-redacted-evidence.md +0 -42
  1365. package/docs/release/0.84.1-portable-feedback.md +0 -20
  1366. package/docs/release/0.85.0-observer-review.md +0 -56
  1367. package/docs/release/0.85.1-observer-continuity.md +0 -20
  1368. package/docs/release/0.86.0-participant-evidence.md +0 -50
  1369. package/docs/release/0.86.1-task-preflight-saved-recordings.md +0 -40
  1370. package/docs/release/0.87.0-participant-endings-and-phone-review.md +0 -64
  1371. package/docs/release/0.88.0-study-diagnostics.md +0 -43
  1372. package/docs/release/0.88.1-completion-evidence-and-local-app.md +0 -79
  1373. package/docs/release/0.88.2-sequential-study-budgets.md +0 -57
  1374. package/docs/release/0.89.0-study-findings.md +0 -79
  1375. package/docs/release/0.89.1-analysis-finished-notice.md +0 -24
  1376. package/docs/release/0.90.0-automatic-analysis.md +0 -21
  1377. package/docs/release/0.91.0-analysis-quality-and-defaults.md +0 -33
  1378. package/docs/release/0.91.1-study-review-polish.md +0 -28
  1379. package/docs/release/0.92.0-grid-playback.md +0 -22
  1380. package/docs/release/0.93.0-global-playback.md +0 -26
  1381. package/docs/release/0.93.1-observer-review-controls.md +0 -24
  1382. package/docs/release/0.94.0-reliability.md +0 -48
  1383. package/docs/release/0.95.0-connections-setup.md +0 -38
  1384. package/docs/release/0.96.0-real-email-receiving.md +0 -47
  1385. package/docs/release/0.96.1-browser-navigation.md +0 -22
  1386. package/docs/release/0.97.0-codex-account-analysis.md +0 -45
  1387. package/docs/release/0.98.0-local-browser-studies.md +0 -31
  1388. package/docs/release/0.99.0-local-browser-mac.md +0 -34
  1389. package/docs/release/0.99.1-action-recovery.md +0 -19
  1390. /package/dist/{agent-session.d.ts → actors/agent-session.d.ts} +0 -0
  1391. /package/dist/{cua-executor-error.d.ts → actors/computer-use/executor-error.d.ts} +0 -0
  1392. /package/dist/{cua-speech.d.ts → actors/computer-use/speech.d.ts} +0 -0
  1393. /package/dist/{actor-goal-source.d.ts → actors/goal-source.d.ts} +0 -0
  1394. /package/dist/{output-token-limit.d.ts → actors/output-token-limit.d.ts} +0 -0
  1395. /package/dist/{reasoning-effort.d.ts → actors/reasoning-effort.d.ts} +0 -0
  1396. /package/dist/{secret-prompt.d.ts → cli/secret-prompt.d.ts} +0 -0
  1397. /package/dist/{telemetry.d.ts → cli/telemetry.d.ts} +0 -0
  1398. /package/dist/{browser-evidence-hygiene.d.ts → evidence/browser-hygiene.d.ts} +0 -0
  1399. /package/dist/{serve-http.d.ts → observer/http.d.ts} +0 -0
  1400. /package/dist/{observer-library.d.ts → observer/library.d.ts} +0 -0
  1401. /package/dist/{listen.d.ts → observer/listen.d.ts} +0 -0
  1402. /package/dist/{observer-static.d.ts → observer/static.d.ts} +0 -0
  1403. /package/dist/{serve-tunnel.d.ts → observer/tunnel.d.ts} +0 -0
  1404. /package/dist/{terminal-encoding.d.ts → routes/terminal/encoding.d.ts} +0 -0
  1405. /package/dist/{concurrency.d.ts → run/concurrency.d.ts} +0 -0
  1406. /package/dist/{study-costs.d.ts → run/costs.d.ts} +0 -0
  1407. /package/dist/{run-narration-secrets.d.ts → run/transient-comms-secrets.d.ts} +0 -0
  1408. /package/dist/{tui-launch.d.ts → tui/launch.d.ts} +0 -0
package/dist/program.js DELETED
@@ -1,4030 +0,0 @@
1
- import { resolveAutomaticAnalysis, automaticAnalysisBudget, formatAutomaticAnalysisBudget, DEFAULT_ANALYSIS_TIMEOUT_MS } from "./automatic-analysis-config.js";
2
- import { automaticAnalysisSucceeded } from "./automatic-analysis-completion.js";
3
- import { formatCuaDiagnostics, formatCuaStopCause } from "./cua-diagnostics.js";
4
- import { existsSync, readFileSync } from "node:fs";
5
- import { formatOrientationHuman, readOrientation } from "./orientation.js";
6
- import { readFile } from "node:fs/promises";
7
- import { dirname, resolve } from "node:path";
8
- import { fileURLToPath } from "node:url";
9
- import { Command, Option } from "commander";
10
- import { startCodexAppServerUi } from "./codex-app-server-ui.js";
11
- import { loadEnvFile } from "./env-file.js";
12
- import { discoverProviderKeys, listUserKeys, resolveKeyName, setUserKey, unsetUserKey, userKeyStorePath } from "./key-resolution.js";
13
- import { COMMS_PROVIDERS, readCommsSetup, saveCommsConnection } from "./comms-connections.js";
14
- import { checkCommsConnection, configureCommsLab } from "./comms-setup.js";
15
- import { inspectCommsRecovery, recoverCommsReceiving } from "./comms-receiving.js";
16
- import { resolveReceivingConnection } from "./comms-receiving-runtime.js";
17
- import { promptSecret } from "./secret-prompt.js";
18
- import { redactText } from "./redaction.js";
19
- import { draftFeedback, listFeedback, renderIssueMarkdown, renderIssueUrl, verifyFeedback } from "./feedback.js";
20
- import { runInit } from "./init.js";
21
- import { computeStats, formatStatsHuman } from "./stats.js";
22
- import { PortInUseError } from "./listen.js";
23
- import { DEFAULT_EXPORT_MAX_BYTES, exportRun, formatExportHuman } from "./export.js";
24
- import { buildPayload, disabledByEnvironment, durationBucket, readTelemetryState, sendTelemetry, telemetryStatePath, deriveStudyFacts, TELEMETRY_NOTICE, writeTelemetryState, isOwnCheckoutRun } from "./telemetry.js";
25
- import { inspectLabManifest, listLabManifests, resolveLabManifest } from "./labs.js";
26
- import { runLabPreflight } from "./lab-preflight.js";
27
- import { runLab, resolveLabDryRun, selectLabBackend } from "./lab-engine.js";
28
- import { loadAdapterScorer } from "./adapter-scorer-loader.js";
29
- import { CUA_ACTOR_LAB_SCHEMA } from "./cua-actor-lab.js";
30
- import { openTarget, renderObserver, serveObserver } from "./observer.js";
31
- import { SERVE_SCHEMA, serveObserverLibrary } from "./observer-serve.js";
32
- import { startExposedObserver, validateExposure } from "./serve-exposure.js";
33
- import { ServeTunnelError } from "./serve-tunnel.js";
34
- import { DEFAULT_OSS_REPOS, runOssLab } from "./oss-lab.js";
35
- import { cleanupOssMetaLabSandboxes, cleanupStaleOssMetaLabSandboxes, runOssMetaLab, startOssMetaLabLiveRefresh } from "./oss-meta-lab.js";
36
- import { cleanupRun, doctor, listRuns, readReview, runDryRun, verifyRun } from "./run.js";
37
- import { reclaimRunSandboxes } from "./reclaim.js";
38
- import { RunIndexCache, readRunIndex } from "./run-index.js";
39
- import { readLabSummary } from "./lab-summary.js";
40
- import { readProjectState } from "./tui-project.js";
41
- import { createTuiObserverSession, stopRun, TUI_ACTION_SCHEMA } from "./tui-actions.js";
42
- import { readRunDetail } from "./run-detail.js";
43
- import { launchRun, readLaunchLogTail } from "./tui-launch.js";
44
- import { TUI_MIN_NODE_MAJOR, nodeSupportsTui, tuiBundleUrl } from "./tui-contract.js";
45
- import { forTerminal } from "./terminal-encoding.js";
46
- import { analyzeStudy, correctStudyAnalysis, showStudyAnalysis } from "./study-analysis-service.js";
47
- import { listStudyAnalyses, listStudyAnalysisExecutions } from "./study-analysis-store.js";
48
- import { resolveRunPath } from "./run.js";
49
- import { detectAgentSession } from "./agent-session.js";
50
- import { runCommsCatchHost } from "./comms-catch-host.js";
51
- import { DEFAULT_SANDBOX_CATCH_PORT } from "./comms-sandbox-catch.js";
52
- export const CLI_RESPONSE_SCHEMA = "humanish.cli-response.v1";
53
- function readCliVersion() {
54
- const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
55
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
56
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.0.0";
57
- }
58
- const CLI_VERSION = readCliVersion();
59
- // Shared so the ~20 leaf commands that declare their own --json flag cannot drift
60
- // from each other in wording.
61
- const JSON_OPTION_DESCRIPTION = "Print a machine-readable JSON response.";
62
- // Transcode ONLY for a terminal. A pipe carries bytes to another program — mangling those would
63
- // corrupt a JSON payload for a reader that handles UTF-8 perfectly well — while a TTY carries them
64
- // to a font, through a locale that may not decode them. See src/terminal-encoding.ts for what a
65
- // participant actually read back off the screen.
66
- const forStream = (stream, text) => stream.isTTY === true ? forTerminal(text) : text;
67
- const defaultIo = {
68
- writeOut: (text) => process.stdout.write(forStream(process.stdout, text)),
69
- writeErr: (text) => process.stderr.write(forStream(process.stderr, text)),
70
- setExitCode: (code) => {
71
- process.exitCode = code;
72
- }
73
- };
74
- // Command boundary catch-all (fix set point 1): every leaf command is created
75
- // through HumanishCommand.createCommand, and HumanishCommand.action wraps the caller's
76
- // handler so an uncaught throw or promise rejection can never escape as a raw
77
- // Node crash. On failure it emits the same structured envelope --json commands
78
- // already promise (schema humanish.cli-response.v1, ok: false, error.code
79
- // HUMANISH_UNEXPECTED) or a single concise stderr line otherwise, then sets exit
80
- // code 2 through the existing CliIo.setExitCode seam. This is the single seam:
81
- // none of the ~20 .action() handlers below need their own try/catch for this.
82
- /**
83
- * Record that a command ran. Anonymous, allowlisted, and unable to affect the command: it is not
84
- * awaited, it is bounded by its own timeout, and every failure inside it is swallowed.
85
- *
86
- * The first time it would send anything, it prints the notice first — the convention Next.js and
87
- * the Vercel CLI set is that collection is default-on but never silent.
88
- */
89
- /**
90
- * Print the disclosure the first time humanish would collect anything, and remember that it was
91
- * shown. AWAITED by the caller: if this loses the race with process exit, a default-on collector
92
- * becomes a silent one.
93
- */
94
- async function announceTelemetryOnce(command, io) {
95
- try {
96
- if (commandPath(command).startsWith("telemetry"))
97
- return;
98
- if (disabledByEnvironment(process.env))
99
- return;
100
- const state = await readTelemetryState();
101
- if (!state.enabled || state.noticed)
102
- return;
103
- io.writeErr(`${TELEMETRY_NOTICE}\n`);
104
- await writeTelemetryState({ ...state, noticed: true });
105
- }
106
- catch {
107
- // Never blocks the command.
108
- }
109
- }
110
- /** The exit code the most recent command chose through CliIo.setExitCode; 0 until it says otherwise. */
111
- let lastExitCode = 0;
112
- async function recordCommandTelemetry(command, ok, durationMs, exitCode) {
113
- try {
114
- // `telemetry` itself is never measured: instrumenting the opt-out would be indecent.
115
- const name = commandPath(command);
116
- if (name.startsWith("telemetry"))
117
- return;
118
- if (disabledByEnvironment(process.env))
119
- return;
120
- // Our own checkout never reports, from whichever directory the command was started: see
121
- // isOwnCheckoutRun for the measured reason both walks exist.
122
- if (isOwnCheckoutRun(process.cwd(), dirname(fileURLToPath(import.meta.url)), (p) => readFileSync(p, "utf8")))
123
- return;
124
- const state = await readTelemetryState();
125
- if (!state.enabled)
126
- return;
127
- const payload = buildPayload({
128
- event: "cli_command",
129
- anonymousId: state.anonymousId,
130
- version: CLI_VERSION,
131
- // The study facts (mode, outcome, starter lab, brain, our own error code) were read off the
132
- // result document when it was written; see writeResult. Without them every `run` event
133
- // looked the same whether it was a dry run or the first live study that ever worked.
134
- properties: {
135
- ...studyFactsFor(command),
136
- command: name,
137
- ok: ok && exitCode === 0,
138
- exitCode,
139
- durationBucket: durationBucket(durationMs)
140
- }
141
- });
142
- if (process.env.HUMANISH_TELEMETRY_DEBUG !== undefined && process.env.HUMANISH_TELEMETRY_DEBUG !== "") {
143
- process.stderr.write(`humanish telemetry (debug, not sent): ${JSON.stringify(payload)}\n`);
144
- return;
145
- }
146
- await sendTelemetry(payload);
147
- }
148
- catch {
149
- // Metrics are our problem, never the operator's.
150
- }
151
- }
152
- /**
153
- * HUMANISH_DEBUG_HANDLES=1: after a command's handler settles, name what is still keeping the
154
- * process alive (#581). A live terminal run wrote its result 64 s in and the CLI stayed up for
155
- * sixteen more minutes; nothing in the bundle could say what held it, and an in-process probe of
156
- * the lane found nothing of ours. This is the one line that answers it next time: the resource
157
- * types Node reports, once, to stderr, after one macrotask so settled work has cleared.
158
- */
159
- function reportActiveHandles(command, io) {
160
- const flag = process.env.HUMANISH_DEBUG_HANDLES;
161
- if (flag === undefined || flag === "" || flag === "0")
162
- return;
163
- setImmediate(() => {
164
- const proc = process;
165
- const resources = proc.getActiveResourcesInfo?.() ?? [];
166
- const counts = new Map();
167
- for (const resource of resources)
168
- counts.set(resource, (counts.get(resource) ?? 0) + 1);
169
- const summary = [...counts.entries()].map(([type, count]) => `${type}×${count}`).join(", ") || "none";
170
- io.writeErr(`humanish debug: active resources after \`${commandPath(command) || "humanish"}\` settled: ${summary}\n`);
171
- }).unref?.();
172
- }
173
- /**
174
- * Write observer/index.html for a finished run the way `watch` does, so a bundle is the same
175
- * bundle whichever command produced it (#597). Never fails the run: a bundle without its Observer
176
- * is still evidence, and `export` can render one later.
177
- */
178
- async function renderObserverForRun(cwd, result) {
179
- if (!result.ok || result.runId === undefined)
180
- return;
181
- try {
182
- const rendered = await renderObserver(cwd, result.runId, { open: false });
183
- if (!rendered.ok)
184
- result.warnings.push(`observer/index.html was not written: ${rendered.error?.message ?? "render failed"}`);
185
- }
186
- catch (error) {
187
- result.warnings.push(`observer/index.html was not written: ${error instanceof Error ? error.message : String(error)}`);
188
- }
189
- }
190
- /** `lab run`, not `run`, so the two are distinguishable — and nothing else from the invocation. */
191
- function commandPath(command) {
192
- const parts = [];
193
- let current = command;
194
- while (current && current.name() !== "humanish") {
195
- parts.unshift(current.name());
196
- current = current.parent;
197
- }
198
- return parts.join(" ");
199
- }
200
- class HumanishCommand extends Command {
201
- cliIo;
202
- constructor(name, cliIo) {
203
- super(name);
204
- this.cliIo = cliIo;
205
- }
206
- createCommand(name) {
207
- return new HumanishCommand(name, this.cliIo);
208
- }
209
- action(fn) {
210
- const cliIo = this.cliIo;
211
- const wrapped = function (...args) {
212
- // ONE emission point for every command, at the same seam that already catches every throw.
213
- // Twenty .action() handlers each remembering to record a metric is twenty chances to forget
214
- // one, and a funnel with a hole in it is worse than no funnel.
215
- const startedAt = Date.now();
216
- lastExitCode = 0;
217
- // The NOTICE is awaited; the SEND is not. They have opposite requirements: disclosure must
218
- // never be lost (a silent default-on collector is indefensible), and a metric must never
219
- // make anyone wait. The first version put both in the fire-and-forget path, and the notice
220
- // lost the race with process exit — a real participant ran this build and never saw it.
221
- const noticed = announceTelemetryOnce(this, cliIo);
222
- const finish = (ok) => {
223
- void recordCommandTelemetry(this, ok, Date.now() - startedAt, lastExitCode);
224
- reportActiveHandles(this, cliIo);
225
- };
226
- try {
227
- // Reflect.apply (not fn.apply) sidesteps TS's special CallableFunction
228
- // overload for functions typed with an explicit `this` parameter, which
229
- // would otherwise reject the plain `Command` thisArg below.
230
- const result = Reflect.apply(fn, this, args);
231
- if (result && typeof result.then === "function") {
232
- return Promise.all([result, noticed]).then(() => { finish(true); }, (error) => {
233
- reportUnexpectedActionError(this, cliIo, error);
234
- finish(false);
235
- });
236
- }
237
- // A synchronous action still has to let the disclosure land before the process exits.
238
- return noticed.then(() => { finish(true); });
239
- return result;
240
- }
241
- catch (error) {
242
- reportUnexpectedActionError(this, cliIo, error);
243
- return undefined;
244
- }
245
- };
246
- return super.action(wrapped);
247
- }
248
- }
249
- // Fix set point 1 (general guard): writeResult marks its own invocation's root
250
- // command the moment it flushes a result to stdout. Keyed by the root Command
251
- // of the invocation (a fresh object every createProgram() call) rather than a
252
- // module-level boolean, so tests that construct multiple `createProgram()`
253
- // instances in the same process never see one instance's writes bleed into
254
- // another's, and nothing needs explicit cleanup -- the entry is released once
255
- // that Command tree is garbage collected.
256
- const invocationEnvelopeWritten = new WeakSet();
257
- function rootCommandOf(command) {
258
- let current = command;
259
- while (current.parent) {
260
- current = current.parent;
261
- }
262
- return current;
263
- }
264
- function markInvocationEnvelopeWritten(command) {
265
- invocationEnvelopeWritten.add(rootCommandOf(command));
266
- }
267
- function invocationEnvelopeAlreadyWritten(command) {
268
- return invocationEnvelopeWritten.has(rootCommandOf(command));
269
- }
270
- function reportUnexpectedActionError(command, io, error) {
271
- const message = redactText(error instanceof Error ? error.message : String(error));
272
- // A taken port is the most expected thing a serving command meets; it gets its own code rather
273
- // than the catch-all's (#484). The message already names the port and whose it is.
274
- const code = error instanceof PortInUseError ? "HUMANISH_PORT_IN_USE" : "HUMANISH_UNEXPECTED";
275
- if (wantsJson(command)) {
276
- if (invocationEnvelopeAlreadyWritten(command)) {
277
- // Some result already went to stdout for this invocation before the
278
- // failure landed -- e.g. `codex app-server --keep-open --json` writes its
279
- // "running" envelope via writeResult, then a later `await` can still
280
- // reject (codex-app-server-ui.ts's persistState() write can fail on
281
- // either branch of that command's completion handling). Appending a
282
- // second JSON document to stdout would break every JSON.parse(stdout)
283
- // consumer, so this failure goes to stderr instead, same as the non-json
284
- // branch below.
285
- io.writeErr(`${code}: ${message}\n`);
286
- io.setExitCode(2);
287
- return;
288
- }
289
- const envelope = {
290
- schema: CLI_RESPONSE_SCHEMA,
291
- ok: false,
292
- error: {
293
- code,
294
- message
295
- }
296
- };
297
- io.writeOut(`${JSON.stringify(envelope, null, 2)}\n`);
298
- }
299
- else {
300
- io.writeErr(`${code}: ${message}\n`);
301
- }
302
- io.setExitCode(2);
303
- }
304
- /** `unknown option '--x'` -> the sibling commands that DO declare `--x`. */
305
- function commandsDeclaring(root, flag) {
306
- const found = [];
307
- const walk = (command, trail) => {
308
- const names = [...trail, command.name()];
309
- if (trail.length > 0 && command.options.some((option) => option.long === flag || option.short === flag)) {
310
- found.push(names.slice(1).join(" "));
311
- }
312
- for (const child of command.commands)
313
- walk(child, names);
314
- };
315
- walk(root, []);
316
- return found;
317
- }
318
- /**
319
- * Enrich commander's flag rejections with where the flag actually lives. A bare "unknown option"
320
- * is accurate and unhelpful in the same way `no labs here yet` was: it reports a fact about this
321
- * command and says nothing the reader can act on. Silence is preserved when no sibling has it —
322
- * inventing a suggestion would be worse than none.
323
- */
324
- export function withSiblingFlagHint(text, root) {
325
- const match = /unknown option '([^']+)'/.exec(text);
326
- if (match === null)
327
- return text;
328
- const owners = commandsDeclaring(root, match[1]);
329
- if (owners.length === 0)
330
- return text;
331
- // Truncation is REPORTED, never silent: a list that quietly drops owners would send a reader
332
- // looking in the wrong place and think it had answered them.
333
- const shown = owners.slice(0, 3);
334
- const list = shown.map((owner) => `\`humanish ${owner}\``).join(", ");
335
- const rest = owners.length - shown.length;
336
- const tail = rest > 0 ? ` (and ${rest} more)` : "";
337
- return `${text.replace(/\n+$/, "")}\n${match[1]} is an option of ${list}${tail}, not of this command.\n`;
338
- }
339
- export function createProgram(io = {}) {
340
- const given = { ...defaultIo, ...io };
341
- // Telemetry's `ok` used to mean "the handler did not throw", which is true of nearly every
342
- // failure: commands report failure through their envelope and setExitCode(2), not by throwing.
343
- // 1,359 study events in two days and every one said ok. Record the exit code the command chose.
344
- const cliIo = {
345
- ...given,
346
- setExitCode: (code) => {
347
- lastExitCode = code;
348
- given.setExitCode(code);
349
- }
350
- };
351
- keyDiscoveryFn = io.keyDiscovery ?? discoverProviderKeys;
352
- tuiRuntime = { ...defaultTuiRuntime, ...io.tuiRuntime };
353
- const program = new HumanishCommand(undefined, cliIo);
354
- // Bare `humanish` orients instead of printing sixteen subcommands (#367). --help is untouched;
355
- // this is only what happens when no command was chosen at all.
356
- program.action(async (options) => {
357
- const state = await readOrientation(".");
358
- if (options.json === true) {
359
- cliIo.writeOut(`${JSON.stringify(state, null, 2)}\n`);
360
- }
361
- else {
362
- cliIo.writeOut(formatOrientationHuman(state));
363
- }
364
- cliIo.setExitCode(0);
365
- });
366
- program
367
- .name("humanish")
368
- .description("Open-source-safe persona simulation CLI and proof harness.")
369
- .version(CLI_VERSION)
370
- .showHelpAfterError()
371
- .option("--json", "Print machine-readable JSON responses where supported.")
372
- .configureOutput({
373
- writeOut: (text) => cliIo.writeOut(text),
374
- writeErr: (text) => cliIo.writeErr(text),
375
- // A rejected flag should name the command that WOULD have taken it. Found by a real
376
- // first-contact study (labs/first-contact.yaml): a participant reached for
377
- // `humanish run --no-open` by analogy with `lab run`, got a bare "unknown option", and
378
- // filed it as a documentation mismatch. The flag is genuinely absent — `run` opens
379
- // nothing — but "unknown" says that badly, because the reader's actual question is
380
- // "then where does it live?".
381
- outputError: (text, write) => write(withSiblingFlagHint(text, program))
382
- })
383
- .addHelpText("after", [
384
- "",
385
- "Examples:",
386
- " humanish watch",
387
- " humanish watch first-run",
388
- " humanish watch --lab .humanish/labs/local.yaml",
389
- " humanish watch --run latest --detach",
390
- " humanish watch --json --no-open",
391
- " humanish lab list",
392
- " humanish lab run first-run --json --no-open",
393
- " humanish verify --run latest --json",
394
- "",
395
- "Public-safety boundary:",
396
- " Humanish must not commit or emit PII, PHI, secrets, keys, raw private transcripts,",
397
- " private screenshots, or private upstream artifacts."
398
- ].join("\n"));
399
- registerInitCommand(program, cliIo);
400
- registerDoctorCommand(program, cliIo);
401
- registerTuiCommand(program, cliIo);
402
- registerTelemetryCommand(program, cliIo);
403
- registerKeysCommand(program, cliIo);
404
- registerRunCommand(program, cliIo);
405
- registerVerifyCommand(program, cliIo);
406
- registerCleanupCommand(program, cliIo);
407
- registerReviewCommand(program, cliIo);
408
- registerAnalyzeCommand(program, cliIo);
409
- registerRunsCommand(program, cliIo);
410
- registerStatsCommand(program, cliIo);
411
- registerExportCommand(program, cliIo);
412
- registerCommsCommands(program, cliIo);
413
- registerRuntimeCommands(program, cliIo);
414
- registerReclaimCommand(program, cliIo);
415
- registerWatchCommand(program, cliIo);
416
- registerObserveCommand(program, cliIo);
417
- registerServeCommand(program, cliIo);
418
- registerCodexCommands(program, cliIo);
419
- registerLabCommands(program, cliIo);
420
- registerFeedbackCommands(program, cliIo);
421
- return program;
422
- }
423
- function registerInitCommand(parent, io) {
424
- parent
425
- .command("init")
426
- .description("Set up committed humanish/ source files and ignored .humanish/ runtime state.")
427
- .summary("Set up humanish/ source and .humanish/ runtime state.")
428
- .option("--dry-run", "Print planned changes without writing files.")
429
- .option("--yes", "Apply safe generated changes without prompting.")
430
- .option("--local-browser <url>", "Set the local-browser starter to this loopback app URL (explicit port above 1023).")
431
- .option("--local-mission <text>", "Set the local-browser participant mission.")
432
- .option("--cwd <path>", "Target project directory.", ".")
433
- .option("--json", JSON_OPTION_DESCRIPTION)
434
- .action(async (options, command) => {
435
- const initOptions = {
436
- cwd: options.cwd,
437
- ...(options.dryRun === undefined ? {} : { dryRun: options.dryRun }),
438
- ...(options.yes === undefined ? {} : { yes: options.yes }),
439
- ...(options.localBrowser === undefined && options.localMission === undefined ? {} : {
440
- localBrowser: {
441
- appUrl: options.localBrowser ?? "http://127.0.0.1:3000",
442
- ...(options.localMission === undefined ? {} : { mission: options.localMission })
443
- }
444
- })
445
- };
446
- const result = await runInit(initOptions);
447
- if (wantsJson(command)) {
448
- io.writeOut(`${JSON.stringify(result, null, 2)}\n`);
449
- }
450
- else if (result.ok) {
451
- io.writeOut(formatInitHuman(result));
452
- }
453
- else {
454
- io.writeErr(formatInitHuman(result));
455
- }
456
- io.setExitCode(result.ok ? 0 : 2);
457
- });
458
- }
459
- function registerDoctorCommand(parent, io) {
460
- parent
461
- .command("doctor")
462
- .description("Explain project readiness and missing Humanish setup.")
463
- .summary("Explain project readiness and missing setup.")
464
- .option("--cwd <path>", "Target project directory.", ".")
465
- .option("--lab <lab>", "Check the selected lab's desktop, participant authentication and separate analysis requirements; no provider calls.")
466
- .option("--env-file <path>", "Load a local env file for these setup checks without printing values.")
467
- .option("--json", JSON_OPTION_DESCRIPTION)
468
- .action(async (options, command) => {
469
- if (options.envFile && !await applyEnvFileOption({ command, cwd: options.cwd, envFile: options.envFile, io }))
470
- return;
471
- const result = await doctor(options.cwd, options.lab ? { lab: options.lab } : {});
472
- writeResult(command, io, result, formatDoctorHuman);
473
- // Behavioral change: was exit 1, every other structured command uses 2.
474
- io.setExitCode(result.ok ? 0 : 2);
475
- });
476
- }
477
- const defaultTuiRuntime = {
478
- checkComms: checkCommsConnection,
479
- promptSecret,
480
- stdin: process.stdin,
481
- stdout: process.stdout,
482
- env: process.env,
483
- nodeVersion: process.version,
484
- loadTui: async (bundle) => {
485
- if (!existsSync(bundle))
486
- return null;
487
- return (await import(bundle.href));
488
- }
489
- };
490
- let tuiRuntime = defaultTuiRuntime;
491
- const TUI_RESULT_SCHEMA = "humanish.tui-result.v1";
492
- function refuseTui(command, io, refusal) {
493
- if (wantsJson(command)) {
494
- io.writeOut(`${JSON.stringify(refusal, null, 2)}\n`);
495
- }
496
- else {
497
- io.writeErr(`${refusal.error.message}\n`);
498
- }
499
- markInvocationEnvelopeWritten(command);
500
- io.setExitCode(2);
501
- }
502
- /**
503
- * The stakeholder surface (#455). Every other command is written so an agent can drive it; this one
504
- * is the opposite — it takes the screen and waits for a person.
505
- *
506
- * That inversion is why it refuses rather than degrades. An agent that runs `humanish tui` with a
507
- * piped stdout has asked for something that cannot exist, and the useful answer is a structured
508
- * error naming the command that WOULD have answered the question. A TUI that quietly rendered
509
- * frames into a pipe would poison a transcript with escape codes and look like a hang.
510
- */
511
- /**
512
- * `humanish telemetry status|enable|disable` — the opt-out the convention requires, plus a `status`
513
- * that prints the exact document that would be sent. "You can read what we collect" is what makes
514
- * default-on honest rather than merely lawful.
515
- */
516
- function registerTelemetryCommand(parent, io) {
517
- const telemetry = parent
518
- .command("telemetry")
519
- .description("Show or change anonymous usage collection.");
520
- telemetry
521
- .command("status", { isDefault: true })
522
- .description("What is collected, and whether it is on.")
523
- .option("--json", JSON_OPTION_DESCRIPTION)
524
- .action(async (options, command) => {
525
- const state = await readTelemetryState();
526
- const envOff = disabledByEnvironment(process.env);
527
- const sample = buildPayload({
528
- event: "cli_command",
529
- anonymousId: state.anonymousId,
530
- version: CLI_VERSION,
531
- properties: { command: "lab run", lab: "try-live", mode: "live", outcome: "incomplete", brain: "provider-key", durationBucket: "1-5m", ok: false, diagnosticCategory: "session_interrupted", stopCause: "spend_limit" }
532
- });
533
- const result = {
534
- schema: "humanish.telemetry-status.v1",
535
- ok: true,
536
- enabled: state.enabled && !envOff,
537
- disabledBy: envOff ? "environment" : state.enabled ? undefined : "config",
538
- statePath: telemetryStatePath(),
539
- example: sample
540
- };
541
- if (options.json === true || wantsJson(command)) {
542
- io.writeOut(`${JSON.stringify(result, null, 2)}\n`);
543
- }
544
- else {
545
- io.writeOut([
546
- `telemetry: ${result.enabled ? "on" : "off"}${envOff ? " (DO_NOT_TRACK / HUMANISH_TELEMETRY_DISABLED)" : ""}`,
547
- `state: ${result.statePath}`,
548
- "",
549
- "a complete example of what is sent — there are no other fields:",
550
- JSON.stringify(sample, null, 2),
551
- "",
552
- "never sent: labs you wrote, subjects, personas, missions, paths, run evidence, key names or values.",
553
- "",
554
- "humanish telemetry disable turns it off"
555
- ].join("\n") + "\n");
556
- }
557
- markInvocationEnvelopeWritten(command);
558
- });
559
- for (const [name, enabled] of [["enable", true], ["disable", false]]) {
560
- telemetry
561
- .command(name)
562
- .description(`Turn anonymous usage collection ${name === "enable" ? "on" : "off"}.`)
563
- .action(async (_options, command) => {
564
- const state = await readTelemetryState();
565
- // Writing `noticed` here too: someone who has just made an explicit choice does not need
566
- // to be told about the choice next time.
567
- await writeTelemetryState({ ...state, enabled, noticed: true });
568
- io.writeOut(`telemetry ${enabled ? "enabled" : "disabled"}\n`);
569
- markInvocationEnvelopeWritten(command);
570
- });
571
- }
572
- }
573
- function registerTuiCommand(parent, io) {
574
- parent
575
- .command("tui")
576
- .description("Open the interactive terminal surface for browsing labs and runs (humans only).")
577
- .summary("Human terminal for labs and runs; refuses detected agent sessions and non-TTY input/output. Agents: humanish lab list --json, humanish lab inspect <lab> --json, humanish runs --json.")
578
- .option("--cwd <path>", "Target project directory.", ".")
579
- .option("--env-file <path>", "Load a local env file for this terminal session and its runs without printing values.")
580
- .option("--force", "Open it anyway in a session that looks like an agent's.")
581
- .option("--json", JSON_OPTION_DESCRIPTION)
582
- .action(async (options, command) => {
583
- const { stdin, stdout } = tuiRuntime;
584
- // Production uses process.env, including SDKs used by existing cleanup actions. Tests inject
585
- // an isolated host context. Values stay behind the capability closures, never in view data.
586
- const sessionEnv = tuiRuntime.env;
587
- // An agent runner, even with a real terminal. `codex exec` allocates a PTY for the commands
588
- // it runs, so the TTY check below passes and the surface used to open: a study watched an
589
- // agent navigate the labs list and start a run it did not mean to start
590
- // (labs/handed-a-human-surface.yaml). A TTY says a terminal exists, not that anyone is
591
- // reading it. `--force` is the escape for the person who really is at this keyboard —
592
- // capturing frames from inside an agent session is exactly that case.
593
- const agent = options.force === true ? undefined : detectAgentSession(tuiRuntime.env);
594
- if (agent !== undefined) {
595
- refuseTui(command, io, {
596
- schema: TUI_RESULT_SCHEMA,
597
- ok: false,
598
- error: {
599
- code: "HUMANISH_TUI_AGENT_SESSION",
600
- message: `humanish tui is a surface for a person, and ${agent.marker} says this session belongs to ${agent.runner}. `
601
- + "It renders frames of escape codes into a transcript, and its keys can start runs. "
602
- + "`humanish runs --json` lists runs, `humanish lab list --json` lists the studies in this project, "
603
- + "and `humanish lab run <lab> --json` starts one. If you are a person at this keyboard, add --force."
604
- }
605
- });
606
- return;
607
- }
608
- if (stdin.isTTY !== true || stdout.isTTY !== true) {
609
- refuseTui(command, io, {
610
- schema: TUI_RESULT_SCHEMA,
611
- ok: false,
612
- error: {
613
- code: "HUMANISH_TUI_REQUIRES_TTY",
614
- message: "humanish tui needs an interactive terminal. For scripted or agent use, `humanish runs --json` lists the same runs and `humanish lab run --json` starts one."
615
- }
616
- });
617
- return;
618
- }
619
- if (!nodeSupportsTui(tuiRuntime.nodeVersion)) {
620
- refuseTui(command, io, {
621
- schema: TUI_RESULT_SCHEMA,
622
- ok: false,
623
- error: {
624
- code: "HUMANISH_TUI_UNSUPPORTED_NODE",
625
- message: `humanish tui needs Node ${TUI_MIN_NODE_MAJOR} or newer (this is ${tuiRuntime.nodeVersion}). Every other humanish command still works on this runtime.`
626
- }
627
- });
628
- return;
629
- }
630
- // The Ink app ships as a pre-built bundle beside the compiled CLI and is loaded ONLY here, so
631
- // no agent-facing command pays its parse cost.
632
- const bundle = tuiBundleUrl(import.meta.url);
633
- const runIndexCache = new RunIndexCache();
634
- const loaded = await tuiRuntime.loadTui(bundle);
635
- if (loaded === null) {
636
- refuseTui(command, io, {
637
- schema: TUI_RESULT_SCHEMA,
638
- ok: false,
639
- error: {
640
- code: "HUMANISH_TUI_BUNDLE_MISSING",
641
- message: `The terminal surface bundle is missing at ${bundle.pathname}. In a checkout, run \`pnpm build\`; in an install, this package is incomplete — please report it.`
642
- }
643
- });
644
- return;
645
- }
646
- const discoveredKeys = new Set();
647
- if (!await applyEnvFileOption({ command, cwd: options.cwd, envFile: options.envFile, io, env: sessionEnv,
648
- onDiscovered: names => names.forEach(name => discoveredKeys.add(name)) }))
649
- return;
650
- // Probe stored credentials afresh. Discovery fills must not become permanent env overrides
651
- // when a person replaces a stored key during this terminal session.
652
- const connectionEnv = () => {
653
- const env = { ...sessionEnv };
654
- for (const name of discoveredKeys)
655
- delete env[name];
656
- return env;
657
- };
658
- const observerSession = createTuiObserverSession(resolve(options.cwd));
659
- let exitCode = 0;
660
- let connectionNotice;
661
- let connectionCheck;
662
- try {
663
- for (;;) {
664
- const outcome = await loaded.startTui({
665
- ...(connectionNotice === undefined ? {} : { initialScreen: "connections", connectionNotice }),
666
- cwd: resolve(options.cwd),
667
- version: { cli: CLI_VERSION },
668
- capabilities: {
669
- comms: {
670
- read: async () => ({ ...await readCommsSetup(resolve(options.cwd), connectionEnv()), ...(connectionCheck ? { authentication: connectionCheck } : {}) }),
671
- save: () => saveCommsConnection(resolve(options.cwd)),
672
- check: async () => { connectionCheck = await tuiRuntime.checkComms({ cwd: resolve(options.cwd), env: connectionEnv(), online: true }); return connectionCheck; },
673
- labs: async () => (await listLabManifests(resolve(options.cwd))).labs.map(lab => ({ title: lab.title ?? lab.id, path: lab.path })),
674
- configure: (lab, apply, planToken) => configureCommsLab({ cwd: resolve(options.cwd), lab, connection: "agentmail", apply, ...(planToken ? { planToken } : {}) }),
675
- recovery: () => inspectCommsRecovery({ cwd: resolve(options.cwd) }),
676
- recover: async (runId, connectionName) => {
677
- try {
678
- const { connection, adapter } = await resolveReceivingConnection(resolve(options.cwd), connectionName, connectionEnv());
679
- return await recoverCommsReceiving({ cwd: resolve(options.cwd), runId, connectionName, apiKeyEnv: connection.apiKeyEnv, adapter });
680
- }
681
- catch {
682
- return { ok: false, message: "Could not recover email resources. Check the connection and retry." };
683
- }
684
- }
685
- },
686
- // One cache for the life of the surface: it refreshes on a cadence, and re-walking every
687
- // run tree each tick is the cost this index exists to avoid.
688
- readRunIndex: (target, readOptions) => readRunIndex(target, { ...readOptions, cache: runIndexCache }),
689
- listLabs: listLabManifests,
690
- startRun: launchOptions => launchRun({ ...launchOptions, env: sessionEnv }),
691
- readLaunchLog: readLaunchLogTail,
692
- readRunDetail,
693
- readLabSummary: (target, lab, readOptions) => readLabSummary(target, lab, { ...readOptions, env: sessionEnv }),
694
- readProjectState,
695
- openObserver: (target, observerPath) => observerSession.open(target, observerPath),
696
- reclaimRun: (target, runId) => reclaimRunSandboxes(target, runId),
697
- stopRun,
698
- initProject: async (target) => {
699
- const result = await runInit({ cwd: target, yes: true });
700
- return result.ok
701
- ? {
702
- schema: TUI_ACTION_SCHEMA,
703
- ok: true,
704
- message: `set up humanish here — ${result.changes.filter((change) => change.action !== "skip").length} files written`
705
- }
706
- : {
707
- schema: TUI_ACTION_SCHEMA,
708
- ok: false,
709
- message: result.error?.message ?? "humanish init could not set this directory up"
710
- };
711
- }
712
- },
713
- stdin,
714
- stdout
715
- });
716
- if (typeof outcome === "number") {
717
- exitCode = outcome;
718
- break;
719
- }
720
- if (outcome.action !== "agentmail-key") {
721
- exitCode = 1;
722
- break;
723
- }
724
- // startTui has unmounted: only the host reads the credential, then remounts the view.
725
- const value = await tuiRuntime.promptSecret("AgentMail API key", stdin, stdout);
726
- if (value === null) {
727
- connectionNotice = "Key entry cancelled. Nothing was changed.";
728
- continue;
729
- }
730
- try {
731
- setUserKey("AGENTMAIL_API_KEY", value, sessionEnv);
732
- // Refresh only a value filled implicitly by discovery; explicit env/file wins.
733
- if (discoveredKeys.has("AGENTMAIL_API_KEY"))
734
- delete sessionEnv.AGENTMAIL_API_KEY;
735
- const saved = await saveCommsConnection(resolve(options.cwd));
736
- connectionNotice = saved.ok ? "Key stored. Project connection saved."
737
- : `Key stored for your user. ${saved.message}`;
738
- stdout.write("Checking AgentMail authentication…\n");
739
- connectionCheck = await tuiRuntime.checkComms({ cwd: resolve(options.cwd), env: connectionEnv(), online: true });
740
- connectionNotice = saved.ok
741
- ? connectionCheck.authenticated === true ? "Key stored. Authentication passed."
742
- : connectionCheck.authenticated === false ? "Key stored. Authentication rejected; test it for details."
743
- : "Key stored. Authentication unknown; test it to retry."
744
- : `${connectionNotice} ${connectionCheck.message}`;
745
- }
746
- catch {
747
- connectionNotice = "Could not store the key. Use a single non-empty line and check key-store permissions.";
748
- }
749
- }
750
- }
751
- finally {
752
- await observerSession.close();
753
- }
754
- // The surface owned the screen; it has already told the operator whatever there was to say.
755
- markInvocationEnvelopeWritten(command);
756
- io.setExitCode(exitCode);
757
- });
758
- }
759
- // The discovery fn the env seam calls; injectable via createProgram for hermetic CLI tests.
760
- let keyDiscoveryFn = discoverProviderKeys;
761
- const KEYS_RESULT_SCHEMA = "humanish.keys-result.v1";
762
- function formatKeysHuman(result) {
763
- const lines = [`humanish keys ${result.ok ? "ok" : "failed"}`, `store: ${result.store}`, result.message];
764
- if (result.action === "list" && result.names.length > 0) {
765
- for (const name of result.names)
766
- lines.push(`- ${name}`);
767
- }
768
- return `${lines.join("\n")}\n`;
769
- }
770
- /** Read one secret line: from a piped stdin when --stdin, else a hidden TTY prompt. */
771
- async function readSecretValue(useStdin, promptLabel) {
772
- if (useStdin || !process.stdin.isTTY) {
773
- const chunks = [];
774
- for await (const chunk of process.stdin)
775
- chunks.push(chunk);
776
- const text = Buffer.concat(chunks).toString("utf8").trim();
777
- return text.length > 0 ? text : null;
778
- }
779
- return promptSecret(promptLabel, process.stdin, process.stderr);
780
- }
781
- function registerKeysCommand(parent, io) {
782
- const keys = parent
783
- .command("keys")
784
- .description("Manage the humanish user-level key store used by provider-key discovery (#436).")
785
- .summary("Manage the user-level provider key store.");
786
- keys
787
- .command("set")
788
- .argument("<vendor-or-name>", "A vendor alias (openai, e2b, anthropic, github, agentmail) or a raw ENV_NAME.")
789
- .description("Store one provider key in the user store (0600), prompted with hidden input.")
790
- .option("--stdin", "Read the value from stdin instead of prompting (for agents/pipes).")
791
- .option("--json", JSON_OPTION_DESCRIPTION)
792
- .action(async (vendorOrName, options, command) => {
793
- const name = resolveKeyName(vendorOrName);
794
- const storePath = userKeyStorePath(process.env);
795
- if (name === null) {
796
- const result = {
797
- schema: KEYS_RESULT_SCHEMA, ok: false, action: "set", store: storePath, names: [],
798
- message: `Not a vendor alias or valid env name: ${vendorOrName}. Vendors: openai, e2b, anthropic, github, agentmail.`
799
- };
800
- writeResult(command, io, result, formatKeysHuman);
801
- io.setExitCode(2);
802
- return;
803
- }
804
- const value = await readSecretValue(options.stdin === true, `Value for ${name}`);
805
- if (value === null) {
806
- const result = {
807
- schema: KEYS_RESULT_SCHEMA, ok: false, action: "set", store: storePath, names: [name],
808
- message: "No value provided; nothing written."
809
- };
810
- writeResult(command, io, result, formatKeysHuman);
811
- io.setExitCode(2);
812
- return;
813
- }
814
- try {
815
- const written = setUserKey(name, value, process.env);
816
- const result = {
817
- schema: KEYS_RESULT_SCHEMA, ok: true, action: "set", store: written.path, names: [name],
818
- message: `${name} stored (0600). Live commands resolve it automatically; remove with "humanish keys unset ${name}".`
819
- };
820
- writeResult(command, io, result, formatKeysHuman);
821
- io.setExitCode(0);
822
- }
823
- catch (error) {
824
- const result = {
825
- schema: KEYS_RESULT_SCHEMA, ok: false, action: "set", store: storePath, names: [name],
826
- message: error instanceof Error ? error.message : "Failed to write the key store."
827
- };
828
- writeResult(command, io, result, formatKeysHuman);
829
- io.setExitCode(2);
830
- }
831
- });
832
- keys
833
- .command("unset")
834
- .argument("<vendor-or-name>", "A vendor alias or raw ENV_NAME to remove from the store.")
835
- .description("Remove one key from the user store.")
836
- .option("--json", JSON_OPTION_DESCRIPTION)
837
- .action(async (vendorOrName, _options, command) => {
838
- const name = resolveKeyName(vendorOrName);
839
- const storePath = userKeyStorePath(process.env);
840
- const had = name !== null && unsetUserKey(name, process.env);
841
- const result = {
842
- schema: KEYS_RESULT_SCHEMA, ok: name !== null, action: "unset", store: storePath,
843
- names: name === null ? [] : [name],
844
- message: name === null
845
- ? `Not a vendor alias or valid env name: ${vendorOrName}.`
846
- : had ? `${name} removed from the store.` : `${name} was not in the store; nothing changed.`
847
- };
848
- writeResult(command, io, result, formatKeysHuman);
849
- io.setExitCode(name === null ? 2 : 0);
850
- });
851
- keys
852
- .command("list")
853
- .description("List the NAMES stored in the user store. Values are never printed.")
854
- .option("--json", JSON_OPTION_DESCRIPTION)
855
- .action(async (_options, command) => {
856
- const storePath = userKeyStorePath(process.env);
857
- const names = listUserKeys(process.env);
858
- const result = {
859
- schema: KEYS_RESULT_SCHEMA, ok: true, action: "list", store: storePath, names,
860
- message: names.length === 0
861
- ? "The store is empty. Add a key with `humanish keys set <vendor>`."
862
- : `${names.length} key name(s) stored. Values are never printed.`
863
- };
864
- writeResult(command, io, result, formatKeysHuman);
865
- io.setExitCode(0);
866
- });
867
- }
868
- function registerRunCommand(parent, io) {
869
- parent
870
- .command("run")
871
- .argument("[lab]", "Optional lab id or .yaml path.")
872
- .description("Run a lab (or a synthetic dry-run bundle). The everyday command.")
873
- .summary("Run a persona/scenario simulation or dry-run bundle.")
874
- .option("--dry-run", "Generate contract proof without browser, keys, or provider spend.")
875
- // `humanish run <lab>` and `humanish lab run <lab>` are the SAME operation on the same
876
- // dispatcher, but this one used to forward four options while its sibling forwarded all of
877
- // them — so `humanish run first-run --no-open` failed while `lab run` accepted it. A
878
- // participant hit exactly that and filed it as a documentation mismatch. Same command, same
879
- // flags (CLIG: "be consistent across subcommands").
880
- .option("--open", "Open the observer in the default browser.")
881
- .option("--no-open", "Render without opening a browser.")
882
- .option("--detach", "Render/open once and exit without an attached watch server.")
883
- .option("--port <port>", "Local observer server port when following.", "0")
884
- .option("--app-url <url>", "Capture live desktop/mobile browser evidence against a running loopback app URL.")
885
- .addOption(new Option("--actor <actor>", "Explicit live actor to run.").choices(["codex-tui", "codex-exec", "codex-app-server"]))
886
- .option("--sims <count>", "Simulation count. Codex exec runs requested lanes with bounded concurrency; Codex TUI supports 1.")
887
- .option("--timeout-ms <ms>", "Local actor timeout in milliseconds.", String(900_000))
888
- .option("--cwd <path>", "Target project directory.", ".")
889
- .option("--env-file <path>", "Load a local env file for this run without persisting values.")
890
- .option("--run-id <id>", "Explicit run id for deterministic fixture tests.")
891
- .option("--json", JSON_OPTION_DESCRIPTION)
892
- .action(async (lab, options, command) => {
893
- if (!await applyEnvFileOption({
894
- command,
895
- cwd: options.cwd,
896
- envFile: options.envFile,
897
- io
898
- })) {
899
- return;
900
- }
901
- if (lab) {
902
- if (options.appUrl !== undefined || options.actor !== undefined) {
903
- const result = {
904
- schema: "humanish.run-result.v1",
905
- ok: false,
906
- cwd: options.cwd,
907
- warnings: [],
908
- error: {
909
- code: "HUMANISH_APP_URL_OPTION_CONFLICT",
910
- message: "Use lab manifests with lab-compatible options only; --app-url and --actor belong to direct `humanish run`."
911
- }
912
- };
913
- writeResult(command, io, result, formatRunHuman);
914
- io.setExitCode(2);
915
- return;
916
- }
917
- await runLabCommand({
918
- command,
919
- io,
920
- lab,
921
- mode: "run",
922
- // Forwarded wholesale, exactly as `lab run` does. Cherry-picking a subset here is what
923
- // made the two commands disagree in the first place.
924
- options: options
925
- });
926
- return;
927
- }
928
- const simCount = options.sims === undefined ? undefined : parsePositiveInteger(options.sims);
929
- const timeoutMs = options.timeoutMs === undefined ? undefined : parseTimeoutMs(options.timeoutMs);
930
- if (options.sims !== undefined && simCount === null) {
931
- const result = {
932
- schema: "humanish.run-result.v1",
933
- ok: false,
934
- cwd: options.cwd,
935
- warnings: [],
936
- error: {
937
- code: "HUMANISH_INVALID_SIM_COUNT",
938
- message: "--sims must be a positive integer."
939
- }
940
- };
941
- writeResult(command, io, result, formatRunHuman);
942
- io.setExitCode(2);
943
- return;
944
- }
945
- if (options.timeoutMs !== undefined && timeoutMs === null) {
946
- const result = {
947
- schema: "humanish.run-result.v1",
948
- ok: false,
949
- cwd: options.cwd,
950
- warnings: [],
951
- error: {
952
- code: "HUMANISH_INVALID_TIMEOUT",
953
- message: "--timeout-ms must be an integer between 1 and 3600000."
954
- }
955
- };
956
- writeResult(command, io, result, formatRunHuman);
957
- io.setExitCode(2);
958
- return;
959
- }
960
- const result = await runDryRun({
961
- cwd: options.cwd,
962
- ...(options.actor === undefined ? {} : { actor: options.actor }),
963
- ...(options.appUrl === undefined ? {} : { appUrl: options.appUrl }),
964
- ...(options.dryRun === undefined ? {} : { dryRun: options.dryRun }),
965
- ...(options.runId === undefined ? {} : { runId: options.runId }),
966
- ...(simCount === undefined || simCount === null ? {} : { simCount }),
967
- ...(timeoutMs === undefined || timeoutMs === null ? {} : { timeoutMs })
968
- });
969
- await renderObserverForRun(options.cwd, result);
970
- writeResult(command, io, result, formatRunHuman);
971
- io.setExitCode(result.ok ? 0 : 2);
972
- });
973
- }
974
- function registerVerifyCommand(parent, io) {
975
- parent
976
- .command("verify")
977
- .description("Validate a run bundle and public-safety gates.")
978
- .summary("Validate a run bundle and public-safety gates.")
979
- .option("--run <id>", "Run id or latest pointer.", "latest")
980
- .option("--cwd <path>", "Target project directory.", ".")
981
- .option("--json", JSON_OPTION_DESCRIPTION)
982
- .action(async (options, command) => {
983
- const result = await verifyRun(options.cwd, options.run);
984
- writeResult(command, io, result, formatVerifyHuman);
985
- io.setExitCode(result.ok ? 0 : 2);
986
- });
987
- }
988
- function registerCleanupCommand(parent, io) {
989
- parent
990
- .command("cleanup")
991
- .description("Inspect recorded resource evidence and write cleanup.json; stored ids do not authorize provider mutation.")
992
- .summary("Write a resource cleanup inspection receipt.")
993
- .option("--run <id>", "Run id or latest pointer.", "latest")
994
- .option("--cwd <path>", "Target project directory.", ".")
995
- .option("--json", JSON_OPTION_DESCRIPTION)
996
- .action(async (options, command) => {
997
- const result = await cleanupRun(options.cwd, options.run);
998
- writeResult(command, io, result, formatCleanupHuman);
999
- io.setExitCode(result.ok ? 0 : 2);
1000
- });
1001
- }
1002
- function registerReviewCommand(parent, io) {
1003
- parent
1004
- .command("review")
1005
- .description("Build a review packet from verified run evidence.")
1006
- .summary("Build a review packet from verified run evidence.")
1007
- .option("--run <id>", "Run id or latest pointer.", "latest")
1008
- .option("--cwd <path>", "Target project directory.", ".")
1009
- .option("--json", JSON_OPTION_DESCRIPTION)
1010
- .action(async (options, command) => {
1011
- const result = await readReview(options.cwd, options.run);
1012
- writeResult(command, io, result, (value) => `${JSON.stringify(value, null, 2)}\n`);
1013
- io.setExitCode("ok" in result && result.ok === false ? 2 : 0);
1014
- });
1015
- }
1016
- /** Commander may collect a shared flag on the parent; only explicit values override leaf defaults. */
1017
- function analysisSelection(options, command) {
1018
- const parent = command.parent;
1019
- const selected = { ...options };
1020
- for (const key of ["cwd", "run"]) {
1021
- if (parent?.getOptionValueSource(key) === "cli" && command.getOptionValueSource(key) !== "cli") {
1022
- selected[key] = parent.getOptionValue(key);
1023
- }
1024
- }
1025
- return selected;
1026
- }
1027
- function registerAnalyzeCommand(parent, io) {
1028
- const analyze = parent.command("analyze")
1029
- .enablePositionalOptions()
1030
- .description("Analyze retained participant evidence into versioned findings. Selected text and captures go to the chosen remote analyst. Opening Observer never starts analysis.")
1031
- .summary("Generate evidence-linked study findings.")
1032
- .option("--run <id>", "Completed run id or latest pointer.", "latest")
1033
- .option("--cwd <path>", "Target project directory.", ".")
1034
- .option("--provider <name>", "Analyst: openai (default) or restricted codex account. No provider fallback.")
1035
- .option("--max-cost <usd>", "Required for OpenAI, including dry-run: USD admission estimate ceiling, not a billing cap. Unsupported for Codex.")
1036
- .option("--model <id>", "Supported vision model. OpenAI uses high effort; qualified Codex account analysis uses low effort.", "gpt-6-astra")
1037
- .option("--question <text>", "Additional reviewer question; does not change participant instructions.")
1038
- .option("--timeout-ms <ms>", "Request timeout, at most 600000 ms.", String(DEFAULT_ANALYSIS_TIMEOUT_MS))
1039
- .option("--max-output-tokens <n>", "OpenAI response-token limit including reasoning, 256–32768. Omit for admission-based sizing. Unsupported for Codex.")
1040
- .option("--dry-run", "Capture and validate local input and estimate admission; no request or analysis artifact.")
1041
- .option("--rerun", "Create a new immutable version even when the same input and configuration were analyzed.")
1042
- .option("--json", JSON_OPTION_DESCRIPTION)
1043
- .action(async (options, command) => {
1044
- const selected = resolveAutomaticAnalysis({
1045
- ...(options.provider === undefined ? {} : { provider: options.provider }), model: options.model,
1046
- ...(options.question === undefined ? {} : { question: options.question }), timeoutMs: Number(options.timeoutMs),
1047
- ...(options.provider === "codex" && options.maxCost === undefined ? {} : { maxCostUsd: Number(options.maxCost) }),
1048
- ...(options.maxOutputTokens === undefined ? {} : { maxOutputTokens: Number(options.maxOutputTokens) })
1049
- });
1050
- if (!selected.ok || !selected.config) {
1051
- const result = { schema: "humanish.analyze-result.v1", ok: false, run: options.run, dryRun: options.dryRun === true,
1052
- reused: false, warnings: [], error: { code: "ANALYSIS_CONFIG_INVALID", message: selected.ok ? "Analysis is disabled." : selected.message } };
1053
- writeResult(command, io, result, value => `${value.error.message}\n`);
1054
- io.setExitCode(2);
1055
- return;
1056
- }
1057
- const controller = new AbortController();
1058
- const cancel = () => controller.abort();
1059
- process.once("SIGINT", cancel);
1060
- try {
1061
- const result = await analyzeStudy(options.cwd, options.run, {
1062
- config: selected.config,
1063
- preferLargerOutput: selected.preferLargerOutput === true,
1064
- ...(options.dryRun === undefined ? {} : { dryRun: options.dryRun }),
1065
- ...(options.rerun === undefined ? {} : { rerun: options.rerun })
1066
- }, { signal: controller.signal, onProgress: (progress) => io.writeErr(`Analysis ${progress.phase}: ${progress.evidenceCount} evidence items, ${progress.captureCount} captures.\n`) });
1067
- writeResult(command, io, result, (value) => {
1068
- if (value.ok && value.dryRun && selected.config?.provider === "codex")
1069
- return "Local evidence and configuration passed admission. Codex CLI, login and model access were not checked. Dollar cost and output-token ceiling are unknown. No provider request sent.\n";
1070
- if (value.ok && value.dryRun)
1071
- return `Admission estimate: $${value.admission?.estimatedCostUsd ?? "unknown"}; output allowance: ${value.admission?.outputTokenAllowance ?? "unknown"} tokens including reasoning. No request sent.\n`;
1072
- const lines = [];
1073
- if (!value.ok)
1074
- lines.push(value.error?.message ?? "Analysis unavailable.", value.error?.code ?? "");
1075
- if (value.artifactPath)
1076
- lines.push(`${value.reused ? "Reused" : "Saved"} ${value.status} analysis: ${value.artifactPath}`);
1077
- if (value.executionReceiptPath)
1078
- lines.push(`Execution receipt: ${value.executionReceiptPath}`);
1079
- if (value.usage) {
1080
- lines.push(`Recorded attempt usage${value.usage.usageComplete ? "" : " (incomplete)"}: ${value.usage.inputTokens ?? "unknown"} input tokens, ${value.usage.outputTokens ?? "unknown"} output tokens.`);
1081
- lines.push(`Estimated attempt cost: ${value.usage.estimatedCostUsd === null ? "unknown" : `$${value.usage.estimatedCostUsd}`}.`);
1082
- }
1083
- if (value.reused)
1084
- lines.push("No new request sent.");
1085
- lines.push(...value.warnings);
1086
- return forTerminal(lines.filter(Boolean).join("\n") + "\n");
1087
- });
1088
- io.setExitCode(result.ok ? 0 : 2);
1089
- }
1090
- finally {
1091
- process.removeListener("SIGINT", cancel);
1092
- }
1093
- });
1094
- analyze.command("list").description("List immutable analysis versions, including failed attempts.")
1095
- .option("--run <id>", "Run id or latest pointer.", "latest")
1096
- .option("--cwd <path>", "Target project directory.", ".")
1097
- .option("--json", JSON_OPTION_DESCRIPTION)
1098
- .action(async (options, command) => {
1099
- options = analysisSelection(options, command);
1100
- const prepared = await resolveRunPath(resolve(options.cwd), options.run).catch(() => null);
1101
- const versions = prepared ? await listStudyAnalyses(prepared) : [];
1102
- const executions = prepared ? await listStudyAnalysisExecutions(prepared) : { receipts: [], warnings: [] };
1103
- const result = { schema: "humanish.analysis-history.v1", ok: prepared !== null, run: options.run,
1104
- executions: executions.receipts, warnings: executions.warnings,
1105
- versions: versions.map(({ id, state, analysis, warnings }) => ({ id, state, status: analysis?.status ?? null,
1106
- createdAt: analysis?.createdAt ?? null, findings: analysis?.result?.findings.length ?? null,
1107
- usage: analysis?.usage ?? null, warnings })) };
1108
- writeResult(command, io, result, (value) => forTerminal(JSON.stringify(value, null, 2) + "\n"));
1109
- io.setExitCode(result.ok ? 0 : 2);
1110
- });
1111
- analyze.command("show").description("Read validated analysis and correction history. Defaults to the latest usable version.")
1112
- .option("--run <id>", "Run id or latest pointer.", "latest")
1113
- .option("--id <id>", "Exact analysis version.")
1114
- .option("--cwd <path>", "Target project directory.", ".")
1115
- .option("--json", JSON_OPTION_DESCRIPTION)
1116
- .action(async (options, command) => {
1117
- options = analysisSelection(options, command);
1118
- const result = await showStudyAnalysis(options.cwd, options.run, options.id);
1119
- writeResult(command, io, result, (value) => forTerminal(JSON.stringify(value, null, 2) + "\n"));
1120
- io.setExitCode(result.state === "invalid" ? 2 : 0);
1121
- });
1122
- analyze.command("correct").description("Append a human review note bound to one exact finding version; original claims remain intact.")
1123
- .option("--run <id>", "Run id or latest pointer.", "latest")
1124
- .option("--cwd <path>", "Target project directory.", ".")
1125
- .requiredOption("--analysis <id>", "Analysis version to review.")
1126
- .requiredOption("--finding <id>", "Finding to review.")
1127
- .addOption(new Option("--status <status>", "Review disposition.").choices(["confirmed", "dismissed", "amended"]).makeOptionMandatory())
1128
- .requiredOption("--reason <text>", "Why this disposition is supported.")
1129
- .option("--claim <text>", "Replacement claim, required only for amended findings.")
1130
- .option("--json", JSON_OPTION_DESCRIPTION)
1131
- .action(async (options, command) => {
1132
- options = analysisSelection(options, command);
1133
- try {
1134
- const correction = await correctStudyAnalysis(options.cwd, options.run, { analysisId: options.analysis,
1135
- findingId: options.finding, status: options.status, reason: options.reason,
1136
- ...(options.claim === undefined ? {} : { replacementClaim: options.claim }) });
1137
- writeResult(command, io, { schema: "humanish.analysis-correction-result.v1", ok: true, correction }, (value) => `Saved correction ${value.correction.id}. Original analysis preserved.\n`);
1138
- io.setExitCode(0);
1139
- }
1140
- catch (error) {
1141
- const code = error instanceof Error && ["ANALYSIS_BUSY", "ANALYSIS_CORRECTION_HISTORY_UNAVAILABLE"].includes(error.message)
1142
- ? error.message : "ANALYSIS_CORRECTION_INVALID";
1143
- const message = code === "ANALYSIS_BUSY"
1144
- ? "Another analysis or correction holds this run's lock. Retry after it finishes."
1145
- : code === "ANALYSIS_CORRECTION_HISTORY_UNAVAILABLE"
1146
- ? "Correction history is unavailable or full. No correction was added; existing records were preserved."
1147
- : "Correction requires a current valid finding, a reason, and a replacement claim only for amended status. Sensitive text is rejected.";
1148
- writeResult(command, io, { schema: "humanish.analysis-correction-result.v1", ok: false,
1149
- error: { code, message } }, (value) => value.error.message + "\n");
1150
- io.setExitCode(2);
1151
- }
1152
- });
1153
- }
1154
- function registerExportCommand(parent, io) {
1155
- parent
1156
- .command("export")
1157
- .description("Export a run as self-contained Observer HTML, or a separately verified redacted bundle workspace. HTML requires share_ready unless --local-only; bundle format requires --redact-screenshots and preserves the original.")
1158
- .summary("Export Observer HTML or a redacted bundle workspace.")
1159
- .option("--run <id>", "Run id or 'latest'.", "latest")
1160
- .addOption(new Option("--format <format>", "Output format; bundle creates a new standalone workspace.").choices(["html", "bundle"]).default("html"))
1161
- .option("--redact-screenshots", "Bundle only: blur PNG screenshots in a verified copy; keep original evidence unchanged.")
1162
- .option("--out <path>", "HTML file or new bundle workspace. Default: .humanish/exports/<runId>.html or <runId>-redacted/.")
1163
- .option("--local-only", "Export a bundle that is not share_ready, with a LOCAL ONLY banner in the file.")
1164
- .option("--max-bytes <n>", "Refuse an export larger than this.", String(DEFAULT_EXPORT_MAX_BYTES))
1165
- .option("--cwd <path>", "Target project directory.", ".")
1166
- .option("--json", JSON_OPTION_DESCRIPTION)
1167
- .action(async (options, command) => {
1168
- const maxBytes = options.format === "bundle" ? Number(options.maxBytes) : Number.parseInt(options.maxBytes, 10);
1169
- const result = await exportRun(options.cwd, options.run, {
1170
- format: options.format,
1171
- ...(options.redactScreenshots === undefined ? {} : { redactScreenshots: options.redactScreenshots }),
1172
- ...(options.out === undefined ? {} : { out: options.out }),
1173
- ...(options.localOnly === undefined ? {} : { localOnly: options.localOnly }),
1174
- ...(options.format === "bundle" || (Number.isFinite(maxBytes) && maxBytes > 0) ? { maxBytes } : {})
1175
- });
1176
- writeResult(command, io, result, formatExportHuman);
1177
- io.setExitCode(result.ok ? 0 : 2);
1178
- });
1179
- }
1180
- function registerStatsCommand(parent, io) {
1181
- parent
1182
- .command("stats")
1183
- .description("Cost, outcome, and duration roll-ups across run history (#472). Estimates stay labelled; unknown costs count as unknown.")
1184
- .summary("Roll up cost, outcomes, and durations across runs.")
1185
- .option("--lab <id>", "Only runs from this lab id.")
1186
- .option("--since <date>", "Only runs that started on or after this ISO date or datetime.")
1187
- .option("--cwd <path>", "Target project directory.", ".")
1188
- .option("--json", JSON_OPTION_DESCRIPTION)
1189
- .action(async (options, command) => {
1190
- const result = await computeStats(options.cwd, {
1191
- ...(options.lab === undefined ? {} : { lab: options.lab }),
1192
- ...(options.since === undefined ? {} : { since: options.since })
1193
- });
1194
- writeResult(command, io, result, formatStatsHuman);
1195
- io.setExitCode(result.ok ? 0 : 2);
1196
- });
1197
- }
1198
- function registerRunsCommand(parent, io) {
1199
- parent
1200
- .command("runs")
1201
- .description("List local Humanish runs and latest pointers.")
1202
- .summary("List local Humanish runs and latest pointers.")
1203
- .option("--cwd <path>", "Target project directory.", ".")
1204
- .option("--json", JSON_OPTION_DESCRIPTION)
1205
- .action(async (options, command) => {
1206
- const result = await listRuns(options.cwd);
1207
- writeResult(command, io, result, formatRunsHuman);
1208
- io.setExitCode(result.ok ? 0 : 2);
1209
- });
1210
- }
1211
- function registerRuntimeCommands(parent, io) {
1212
- const runtime = parent.command("runtime").description("Prepare or inspect the local browser runtime.");
1213
- for (const action of ["status", "setup"]) {
1214
- runtime.command(action)
1215
- .description(action === "status" ? "Check local Docker, virtualization and the cached browser image without downloads." : "Download and install the local browser image. Does not start a study or use model quota.")
1216
- .option("--json", JSON_OPTION_DESCRIPTION)
1217
- .option("--media", "Prepare or inspect the optional camera and speech runtime.")
1218
- .action(async (_options, command) => {
1219
- const { localRuntimeStatus, prepareLocalRuntime } = await import("./local-runtime.js");
1220
- try {
1221
- if (action === "setup")
1222
- await prepareLocalRuntime({ media: _options.media === true, progress: message => io.writeErr(`${message}\n`) });
1223
- const status = await localRuntimeStatus({ media: _options.media === true });
1224
- const result = { schema: "humanish.runtime-result.v1", ...status };
1225
- writeResult(command, io, result, () => `${status.message}\n`);
1226
- io.setExitCode(status.ok ? 0 : 2);
1227
- }
1228
- catch (error) {
1229
- const message = error instanceof Error ? error.message : "Local runtime setup failed. Run humanish runtime setup to retry.";
1230
- const result = { schema: "humanish.runtime-result.v1", ok: false, error: { code: "HUMANISH_LOCAL_RUNTIME_SETUP_FAILED", message } };
1231
- writeResult(command, io, result, () => `${message}\n`);
1232
- io.setExitCode(2);
1233
- }
1234
- });
1235
- }
1236
- }
1237
- function registerCommsCommands(parent, io) {
1238
- const comms = parent
1239
- .command("comms")
1240
- .description("Local email capture, real receiving connections, checks and cleanup recovery.")
1241
- .summary("Off-app comms surfaces.");
1242
- comms.command("providers")
1243
- .description("List installed communication provider capabilities. No network requests.")
1244
- .option("--json", JSON_OPTION_DESCRIPTION)
1245
- .action((_options, command) => {
1246
- const result = { schema: "humanish.comms-providers.v1", ok: true, providers: COMMS_PROVIDERS };
1247
- writeResult(command, io, result, () => COMMS_PROVIDERS.map(provider => `${provider.label}: ${provider.limitation}\nKey: ${provider.keyEnv}\nSetup: ${provider.setupUrl}\n`).join("\n"));
1248
- });
1249
- const connections = comms.command("connections").description("Manage project-local non-secret connection profiles. A lab explicitly selects its receiving connection.");
1250
- connections.command("list")
1251
- .description("Show saved connections and local credential status; does not authenticate with a provider.")
1252
- .option("--cwd <path>", "Target project directory.", ".")
1253
- .option("--env-file <path>", "Load credentials for local status without printing values.")
1254
- .option("--json", JSON_OPTION_DESCRIPTION)
1255
- .action(async (options, command) => {
1256
- if (!await applyEnvFileOption({ command, cwd: options.cwd, envFile: options.envFile, io }))
1257
- return;
1258
- const result = await readCommsSetup(resolve(options.cwd), process.env);
1259
- writeResult(command, io, result, value => `${value.message}\nAgentMail key: ${value.credential.present ? "present" : "missing"}\n${value.connections.map(connection => `${connection.name}: ${connection.provider} (${connection.apiKeyEnv})\n`).join("")}`);
1260
- io.setExitCode(result.ok ? 0 : 2);
1261
- });
1262
- connections.command("add")
1263
- .argument("[name]", "Project connection name.", "agentmail")
1264
- .description("Save an AgentMail connection profile. Does not write a key, alter a lab or contact the provider.")
1265
- .option("--cwd <path>", "Target project directory.", ".")
1266
- .option("--provider <id>", "Installed provider id.", "agentmail")
1267
- .option("--api-key-env <name>", "Environment variable NAME, never its value.", "AGENTMAIL_API_KEY")
1268
- .option("--json", JSON_OPTION_DESCRIPTION)
1269
- .action(async (name, options, command) => {
1270
- const result = { schema: "humanish.comms-connection-result.v1", ...(options.provider === "agentmail"
1271
- ? await saveCommsConnection(resolve(options.cwd), name, options.apiKeyEnv)
1272
- : { ok: false, message: "Only AgentMail connection setup is currently available." }) };
1273
- writeResult(command, io, result, value => `${value.message}\n`);
1274
- io.setExitCode(result.ok ? 0 : 2);
1275
- });
1276
- comms.command("check")
1277
- .description("Check connection and credential presence; --online authenticates without creating inboxes.")
1278
- .option("--cwd <path>", "Target project directory.", ".")
1279
- .option("--connection <name>", "Saved connection name.", "agentmail")
1280
- .option("--lab <path>", "Check the connection selected by this exact lab.")
1281
- .option("--online", "Make a read-only provider authentication request.")
1282
- .option("--env-file <path>", "Load credentials without printing values.")
1283
- .option("--json", JSON_OPTION_DESCRIPTION)
1284
- .action(async (options, command) => {
1285
- if (!await applyEnvFileOption({ command, cwd: options.cwd, envFile: options.envFile, io }))
1286
- return;
1287
- let connection = options.connection;
1288
- if (options.lab) {
1289
- const lab = await resolveLabManifest(options.cwd, options.lab);
1290
- if (!lab.ok || lab.config.comms?.email?.kind !== "real") {
1291
- const result = { ok: false, message: "This lab does not select a real email connection." };
1292
- writeResult(command, io, result, value => `${value.message}\n`);
1293
- io.setExitCode(2);
1294
- return;
1295
- }
1296
- connection = lab.config.comms.email.connection;
1297
- }
1298
- const result = await checkCommsConnection({ cwd: resolve(options.cwd), connection, env: process.env, online: options.online === true });
1299
- writeResult(command, io, result, value => `${value.message}\n`);
1300
- io.setExitCode(result.ok ? 0 : 2);
1301
- });
1302
- comms.command("configure")
1303
- .description("Preview or save a local receiving-enabled copy of a supported lab. No provider requests.")
1304
- .requiredOption("--lab <path>", "Exact source lab path or handle.")
1305
- .option("--cwd <path>", "Target project directory.", ".")
1306
- .option("--connection <name>", "Saved connection name.", "agentmail")
1307
- .option("--apply", "Save the local copy; original lab remains unchanged.")
1308
- .option("--plan-token <digest>", "Require the source and destination to match a previous preview.")
1309
- .option("--json", JSON_OPTION_DESCRIPTION)
1310
- .action(async (options, command) => {
1311
- const result = await configureCommsLab({ ...options, cwd: resolve(options.cwd) });
1312
- writeResult(command, io, result, value => `${value.message}\n`);
1313
- io.setExitCode(result.ok ? 0 : 2);
1314
- });
1315
- comms.command("recover")
1316
- .description("Inspect interrupted email leases; --apply deletes only privately recorded resources owned by this project and account.")
1317
- .option("--cwd <path>", "Target project directory.", ".")
1318
- .option("--run <id>", "One run to inspect or recover.")
1319
- .option("--apply", "Recover the selected inactive run and verify mailbox deletion.")
1320
- .option("--env-file <path>", "Load credentials without printing values.")
1321
- .option("--json", JSON_OPTION_DESCRIPTION)
1322
- .action(async (options, command) => {
1323
- if (!await applyEnvFileOption({ command, cwd: options.cwd, envFile: options.envFile, io }))
1324
- return;
1325
- const cwd = resolve(options.cwd);
1326
- try {
1327
- const entries = (await inspectCommsRecovery({ cwd })).filter(entry => !options.run || entry.runId === options.run);
1328
- if (!options.apply) {
1329
- const result = { schema: "humanish.comms-recovery.v1", ok: true, entries };
1330
- writeResult(command, io, result, value => value.entries.length ? value.entries.map(entry => `${entry.runId}: ${entry.unresolvedCount} unresolved; ${entry.activeOwner === null ? "unknown owner" : entry.activeOwner ? "active owner" : "inactive"}\n`).join("") : "No recoverable email leases in this project.\n");
1331
- return;
1332
- }
1333
- if (!options.run || entries.length !== 1)
1334
- throw new Error("selection");
1335
- const entry = entries[0];
1336
- const { connection, adapter } = await resolveReceivingConnection(cwd, entry.connectionName, process.env);
1337
- const result = { schema: "humanish.comms-recovery-result.v1", ...await recoverCommsReceiving({ cwd, runId: options.run, connectionName: entry.connectionName, apiKeyEnv: connection.apiKeyEnv, adapter }) };
1338
- writeResult(command, io, result, value => `${value.message}\n`);
1339
- io.setExitCode(result.ok ? 0 : 2);
1340
- }
1341
- catch {
1342
- const result = { schema: "humanish.comms-recovery-result.v1", ok: false, message: "Recovery could not complete. Select one recorded run with --run, check its connection, and retry. No unrecorded resources are eligible." };
1343
- writeResult(command, io, result, value => `${value.message}\n`);
1344
- io.setExitCode(2);
1345
- }
1346
- });
1347
- comms
1348
- .command("catch")
1349
- .description("Run the email catch on THIS host so humanish can study an app it does not provision (#328). Your app posts its email sends here; the persona opens /inbox; humanish drains GET /deliveries and writes digest-only evidence. Point your lab's comms.email.external.catchBaseUrl at this server.")
1350
- .summary("Run the adopter-hosted email catch.")
1351
- .option("--port <port>", "Port for capture + inbox (default 8025).", String(DEFAULT_SANDBOX_CATCH_PORT))
1352
- .option("--dir <path>", "Directory for the deliveries log and rendered inbox.", ".humanish/comms-catch")
1353
- .option("--token <value>", "Require this bearer token on GET /deliveries (recommended when reachable off-host).")
1354
- .option("--smtp-port <port>", "Also capture SMTP mail on 127.0.0.1:<port>. Point your app's SMTP transport here.")
1355
- .option("--inbox-port <port>", "Also serve a READ-ONLY inbox listener on 0.0.0.0:<port>, so a persona on another machine can open /inbox. Without it the catch stays loopback-only.")
1356
- .option("--recipient <address>", "Only render mail sent to this address (repeatable). Default: render whatever the app actually mailed.", (value, previous = []) => [...previous, value])
1357
- .action(async (options) => {
1358
- const port = Number.parseInt(options.port, 10);
1359
- if (!Number.isInteger(port) || port <= 0 || port > 65_534) {
1360
- io.writeErr("--port must be an integer between 1 and 65534.\n");
1361
- io.setExitCode(2);
1362
- return;
1363
- }
1364
- let inboxPort;
1365
- if (options.inboxPort !== undefined) {
1366
- inboxPort = Number.parseInt(options.inboxPort, 10);
1367
- if (!Number.isInteger(inboxPort) || inboxPort <= 0 || inboxPort > 65_534) {
1368
- io.writeErr("--inbox-port must be an integer between 1 and 65534.\n");
1369
- io.setExitCode(2);
1370
- return;
1371
- }
1372
- if (inboxPort === port) {
1373
- io.writeErr("--inbox-port must differ from --port (the capture listener is loopback-only; the inbox listener is not).\n");
1374
- io.setExitCode(2);
1375
- return;
1376
- }
1377
- }
1378
- let smtpPort;
1379
- if (options.smtpPort !== undefined) {
1380
- smtpPort = Number(options.smtpPort);
1381
- if (!Number.isInteger(smtpPort) || smtpPort <= 0 || smtpPort > 65_534) {
1382
- io.writeErr("--smtp-port must be an integer between 1 and 65534.\n");
1383
- io.setExitCode(2);
1384
- return;
1385
- }
1386
- if (smtpPort === port || smtpPort === inboxPort) {
1387
- io.writeErr("--smtp-port must differ from --port and --inbox-port.\n");
1388
- io.setExitCode(2);
1389
- return;
1390
- }
1391
- }
1392
- await runCommsCatchHost({
1393
- port,
1394
- dir: options.dir,
1395
- ...(options.token ? { token: options.token } : {}),
1396
- ...(inboxPort === undefined ? {} : { inboxPort }),
1397
- ...(smtpPort === undefined ? {} : { smtpPort }),
1398
- ...(options.recipient && options.recipient.length > 0 ? { recipients: options.recipient } : {})
1399
- }, io);
1400
- });
1401
- }
1402
- function registerReclaimCommand(parent, io) {
1403
- parent
1404
- .command("reclaim")
1405
- .description("Kill an interrupted run's sandboxes by their journaled exact ids (the #358 salvage path — reads the run's sandbox-receipts.ndjson; never enumerates the E2B account). Needs E2B_API_KEY in the environment.")
1406
- .summary("Reclaim an interrupted run's sandboxes by recorded id.")
1407
- .option("--cwd <path>", "Target project directory.", ".")
1408
- .option("--run <id>", "Run id, or 'latest'.", "latest")
1409
- .option("--json", JSON_OPTION_DESCRIPTION)
1410
- .action(async (options, command) => {
1411
- const result = await reclaimRunSandboxes(options.cwd, options.run);
1412
- writeResult(command, io, result, formatReclaimHuman);
1413
- io.setExitCode(result.ok ? 0 : 2);
1414
- });
1415
- }
1416
- function formatReclaimHuman(result) {
1417
- const lines = [];
1418
- lines.push(`Reclaim ${result.runId}: ${result.ok ? "ok" : "FAILED"} — ${result.receiptCount} sandbox receipt(s).`);
1419
- for (const outcome of result.outcomes) {
1420
- lines.push(` ${outcome.sandboxId} (${outcome.laneId}): ${outcome.state}${outcome.detail ? ` — ${outcome.detail}` : ""}`);
1421
- }
1422
- for (const warning of result.warnings)
1423
- lines.push(` warning: ${warning}`);
1424
- if (result.error)
1425
- lines.push(` error: ${result.error.message}`);
1426
- return lines.join("\n");
1427
- }
1428
- function registerCodexCommands(parent, io) {
1429
- const codex = parent
1430
- .command("codex")
1431
- .description("Run Codex-native Humanish integration surfaces.")
1432
- .summary("Run Codex-native Humanish integration surfaces.");
1433
- codex
1434
- .command("app-server")
1435
- .description("Run a browser-visible Codex app-server actor surface and write redacted protocol artifacts.")
1436
- .option("--cwd <path>", "Target project directory.", ".")
1437
- .option("--prompt <text>", "Prompt to submit to Codex app-server.")
1438
- .option("--prompt-file <path>", "Read the Codex app-server prompt from a file.")
1439
- .option("--run-root <path>", "Artifact directory for redacted app-server evidence.")
1440
- .option("--state-file <path>", "State JSON file for external observers.")
1441
- .option("--timeout-ms <ms>", "Actor timeout in milliseconds.", String(900_000))
1442
- .option("--port <port>", "Local browser UI port.", "0")
1443
- .option("--model <model>", "Optional Codex model override.")
1444
- .addOption(new Option("--sandbox <mode>", "Turn sandbox policy.").choices(["read-only", "workspace-write", "danger-full-access"]).default("read-only"))
1445
- .option("--actor-command <command>", "Override app-server command. Defaults to codex app-server --listen stdio://.")
1446
- .option("--keep-open", "Keep the browser UI process alive after the actor finishes.")
1447
- .option("--json", JSON_OPTION_DESCRIPTION)
1448
- .action(async (options, command) => {
1449
- const timeoutMs = parseTimeoutMs(options.timeoutMs);
1450
- const port = parseObserverPort(options.port);
1451
- if (timeoutMs === null) {
1452
- const result = codexAppServerUiError(options.cwd, "HUMANISH_INVALID_TIMEOUT", "--timeout-ms must be an integer between 1 and 3600000.");
1453
- writeResult(command, io, result, formatCodexAppServerUiHuman);
1454
- io.setExitCode(2);
1455
- return;
1456
- }
1457
- if (port === null) {
1458
- const result = codexAppServerUiError(options.cwd, "HUMANISH_INVALID_PORT", "--port must be an integer between 0 and 65535.");
1459
- writeResult(command, io, result, formatCodexAppServerUiHuman);
1460
- io.setExitCode(2);
1461
- return;
1462
- }
1463
- const prompt = await readCodexAppServerPrompt(options);
1464
- if (!prompt) {
1465
- const result = codexAppServerUiError(options.cwd, "HUMANISH_CODEX_APP_SERVER_PROMPT_REQUIRED", "Provide --prompt or --prompt-file.");
1466
- writeResult(command, io, result, formatCodexAppServerUiHuman);
1467
- io.setExitCode(2);
1468
- return;
1469
- }
1470
- const controller = await startCodexAppServerUi({
1471
- ...(options.actorCommand === undefined ? {} : { actorCommand: options.actorCommand }),
1472
- cwd: options.cwd,
1473
- keepOpen: options.keepOpen === true,
1474
- ...(options.model === undefined ? {} : { model: options.model }),
1475
- port,
1476
- prompt,
1477
- ...(options.runRoot === undefined ? {} : { runRoot: options.runRoot }),
1478
- sandbox: options.sandbox,
1479
- ...(options.stateFile === undefined ? {} : { stateFile: options.stateFile }),
1480
- timeoutMs
1481
- });
1482
- const initial = {
1483
- schema: "humanish.codex-app-server-ui-result.v1",
1484
- ok: true,
1485
- cwd: resolve(options.cwd),
1486
- stateFile: controller.stateFile,
1487
- url: controller.url,
1488
- status: controller.initialState.status,
1489
- reason: controller.initialState.reason
1490
- };
1491
- if (options.keepOpen === true) {
1492
- writeResult(command, io, initial, formatCodexAppServerUiHuman);
1493
- io.setExitCode(0);
1494
- try {
1495
- // Local hardening for the known double-envelope path: the "running"
1496
- // envelope above has already reached stdout, so a rejection here
1497
- // (codex-app-server-ui.ts's persistState() write can fail on either
1498
- // branch of session completion) must not go through the
1499
- // command-boundary catch-all's --json branch, which would otherwise
1500
- // append a second JSON document to stdout. Handling it here directly
1501
- // means this known path stays correct even if that general guard is
1502
- // ever weakened; it does not replace it.
1503
- await controller.completion;
1504
- await new Promise((resolveWait) => {
1505
- process.once("SIGINT", () => {
1506
- void controller.close().finally(resolveWait);
1507
- });
1508
- });
1509
- }
1510
- catch (error) {
1511
- io.writeErr(`HUMANISH_UNEXPECTED: ${redactText(error instanceof Error ? error.message : String(error))}\n`);
1512
- io.setExitCode(2);
1513
- }
1514
- return;
1515
- }
1516
- const completed = await controller.completion;
1517
- const output = codexAppServerUiResultFromState(completed);
1518
- writeResult(command, io, output, formatCodexAppServerUiHuman);
1519
- io.setExitCode(output.ok ? 0 : 2);
1520
- });
1521
- }
1522
- function registerWatchCommand(parent, io) {
1523
- parent
1524
- .command("watch")
1525
- .argument("[lab]", "Optional lab id or .yaml path to run and observe.")
1526
- .description("Run sims, open the observer, and keep the shell attached.")
1527
- .summary("Run sims, open the observer, keep the shell attached.")
1528
- .option("--lab <id-or-path>", "Explicit lab id or .yaml path.")
1529
- .option("--run <id>", "Watch an existing run id or latest pointer.")
1530
- .option("--dry-run", "Lab only: render contract evidence without live provider spend.")
1531
- .option("--codex-app-server", "Lab only: use Codex app-server client mode for OSS headed desktops.")
1532
- .option("--sims <count>", "Start a fresh synthetic run with this many sims before rendering. Defaults to 4 when --run is omitted.")
1533
- .option("--count <count>", "Lab only: override headed desktop lane count.")
1534
- .option("--limit <count>", "Lab only: override smoke lab repo limit.")
1535
- .option("--repo <owner/repo>", "Lab only: GitHub repo slug. Repeatable.", collectRepeated, [])
1536
- .option("--repos <owner/repo,...>", "Lab only: comma-separated GitHub repo slugs.")
1537
- .option("--redact-repos", "Lab only: redact repo labels in durable artifacts.")
1538
- .option("--no-redact-repos", "Lab only: persist repo labels. Use only for public-safe runs.")
1539
- .option("--keep", "Lab only: keep disposable clone sandbox for debugging.")
1540
- .option("--scorer <path>", "Terminal/computer-use/shared-world labs only: repo-relative adopter scorer module (.mjs). Overrides review.scorer.ref. Executable code — review it as code.")
1541
- .option("--run-id <id>", "Explicit run id for deterministic fixture tests.")
1542
- .option("--cwd <path>", "Target project directory.", ".")
1543
- .option("--env-file <path>", "Load a local env file for this watch without persisting values.")
1544
- .option("--open", "Open the observer in the default browser.")
1545
- .option("--no-open", "Render without opening a browser.")
1546
- .addOption(new Option("--follow", "Deprecated; human output follows by default.").hideHelp())
1547
- .option("--detach", "Render/open once and exit without attached watch server.")
1548
- .option("--port <port>", "Local observer server port when following.", "0")
1549
- .option("--expose", "CUA lab only: expose the live run through an authenticated edge so you can watch from a phone. Requires edge auth.")
1550
- .addOption(new Option("--tunnel <provider>", "Spawn the external tunnel binary against the loopback port.").choices(["ngrok"]))
1551
- .option("--tunnel-domain <domain>", "Reserved domain passed to ngrok as --url (e.g. observer.example.com). Requires --tunnel.")
1552
- .addOption(new Option("--oauth <provider>", "Turn on ngrok edge OAuth. Requires --tunnel.").choices(["google"]))
1553
- .option("--allow-email <addr>", "Edge OAuth allow rule: permit this email. Repeatable. Requires --oauth.", collectRepeated, [])
1554
- .option("--allow-domain <domain>", "Edge OAuth allow rule: permit this domain. Repeatable. Requires --oauth.", collectRepeated, [])
1555
- .option("--public-url <origin>", "Bring-your-own authed edge (Cloudflare Access/Tailscale/manual). Binds loopback and trusts your edge. Requires --expose.")
1556
- .option("--safe", "Not applicable to watch: a live run is never share_ready, so --safe (a `serve` library filter) is rejected here. Restrict viewers with edge auth (--allow-email/--allow-domain).")
1557
- .option("--json", JSON_OPTION_DESCRIPTION)
1558
- .addHelpText("after", [
1559
- "",
1560
- "Happy path:",
1561
- " humanish watch",
1562
- " humanish watch first-run",
1563
- " humanish watch --lab .humanish/labs/local.yaml",
1564
- "",
1565
- "Watch a live CUA run from your phone (tunnel-edge auth):",
1566
- " humanish watch my-cua-lab --expose --tunnel ngrok --oauth google --allow-email you@example.com",
1567
- "",
1568
- "Agent/CI path:",
1569
- " humanish watch --json --no-open",
1570
- "",
1571
- "Existing evidence:",
1572
- " humanish watch --run latest --detach"
1573
- ].join("\n"))
1574
- .action(async (labArg, options, command) => {
1575
- const lab = options.lab ?? labArg;
1576
- if (options.lab !== undefined && labArg !== undefined) {
1577
- const result = {
1578
- schema: "humanish.run-result.v1",
1579
- ok: false,
1580
- cwd: options.cwd,
1581
- warnings: [],
1582
- error: {
1583
- code: "HUMANISH_WATCH_OPTION_CONFLICT",
1584
- message: "Use either positional lab or --lab, not both."
1585
- }
1586
- };
1587
- writeResult(command, io, result, formatRunHuman);
1588
- io.setExitCode(2);
1589
- return;
1590
- }
1591
- if (!await applyEnvFileOption({
1592
- command,
1593
- cwd: options.cwd,
1594
- envFile: options.envFile,
1595
- io
1596
- })) {
1597
- return;
1598
- }
1599
- if (lab) {
1600
- if (options.run !== undefined) {
1601
- const result = {
1602
- schema: "humanish.run-result.v1",
1603
- ok: false,
1604
- cwd: options.cwd,
1605
- warnings: [],
1606
- error: {
1607
- code: "HUMANISH_WATCH_OPTION_CONFLICT",
1608
- message: "Use either a lab to start evidence or --run to watch existing evidence, not both."
1609
- }
1610
- };
1611
- writeResult(command, io, result, formatRunHuman);
1612
- io.setExitCode(2);
1613
- return;
1614
- }
1615
- await runLabCommand({
1616
- command,
1617
- io,
1618
- lab,
1619
- mode: "watch",
1620
- options: {
1621
- cwd: options.cwd,
1622
- ...(options.count === undefined ? {} : { count: options.count }),
1623
- ...(options.codexAppServer === undefined ? {} : { codexAppServer: options.codexAppServer }),
1624
- ...(options.detach === undefined ? {} : { detach: options.detach }),
1625
- ...(options.dryRun === undefined ? {} : { dryRun: options.dryRun }),
1626
- ...(options.keep === undefined ? {} : { keep: options.keep }),
1627
- ...(options.limit === undefined ? {} : { limit: options.limit }),
1628
- ...(options.open === undefined ? {} : { open: options.open }),
1629
- port: options.port,
1630
- ...(options.redactRepos === undefined ? {} : { redactRepos: options.redactRepos }),
1631
- repo: options.repo,
1632
- ...(options.repos === undefined ? {} : { repos: options.repos }),
1633
- ...(options.runId === undefined ? {} : { runId: options.runId }),
1634
- ...(options.scorer === undefined ? {} : { scorer: options.scorer }),
1635
- ...(options.sims === undefined ? {} : { sims: options.sims }),
1636
- ...(options.expose === undefined ? {} : { expose: options.expose }),
1637
- ...(options.tunnel === undefined ? {} : { tunnel: options.tunnel }),
1638
- ...(options.tunnelDomain === undefined ? {} : { tunnelDomain: options.tunnelDomain }),
1639
- ...(options.oauth === undefined ? {} : { oauth: options.oauth }),
1640
- allowEmail: options.allowEmail,
1641
- allowDomain: options.allowDomain,
1642
- ...(options.publicUrl === undefined ? {} : { publicUrl: options.publicUrl }),
1643
- ...(options.safe === undefined ? {} : { safe: options.safe }),
1644
- ...(options.json === undefined ? {} : { json: options.json })
1645
- }
1646
- });
1647
- return;
1648
- }
1649
- // Exposure is only meaningful for a live CUA lab run (it serves the live desktop). The
1650
- // non-lab watch path (existing evidence, or a fresh synthetic run) has no live desktop to
1651
- // stream, so exposure flags there are refused rather than silently ignored — use `serve`.
1652
- if (watchExposeRequested(options)) {
1653
- const result = {
1654
- schema: "humanish.run-result.v1",
1655
- ok: false,
1656
- cwd: options.cwd,
1657
- warnings: [],
1658
- error: {
1659
- code: "HUMANISH_WATCH_OPTION_CONFLICT",
1660
- message: "--expose/--tunnel/--oauth apply only to a live CUA lab run; to expose finished evidence use `humanish serve --expose`."
1661
- }
1662
- };
1663
- writeResult(command, io, result, formatRunHuman);
1664
- io.setExitCode(2);
1665
- return;
1666
- }
1667
- const runOptionSource = typeof command.getOptionValueSource === "function"
1668
- ? command.getOptionValueSource("run")
1669
- : undefined;
1670
- const runWasOmitted = runOptionSource === undefined || runOptionSource === "default";
1671
- const simCount = options.sims === undefined ? undefined : parsePositiveInteger(options.sims);
1672
- const port = parseObserverPort(options.port);
1673
- if (options.sims !== undefined && simCount === null) {
1674
- const result = {
1675
- schema: "humanish.run-result.v1",
1676
- ok: false,
1677
- cwd: options.cwd,
1678
- warnings: [],
1679
- error: {
1680
- code: "HUMANISH_INVALID_SIM_COUNT",
1681
- message: "--sims must be a positive integer."
1682
- }
1683
- };
1684
- writeResult(command, io, result, formatRunHuman);
1685
- io.setExitCode(2);
1686
- return;
1687
- }
1688
- if (!runWasOmitted && options.sims !== undefined) {
1689
- const result = {
1690
- schema: "humanish.run-result.v1",
1691
- ok: false,
1692
- cwd: options.cwd,
1693
- warnings: [],
1694
- error: {
1695
- code: "HUMANISH_WATCH_OPTION_CONFLICT",
1696
- message: "Use either --run to watch existing evidence or --sims to start a fresh run, not both."
1697
- }
1698
- };
1699
- writeResult(command, io, result, formatRunHuman);
1700
- io.setExitCode(2);
1701
- return;
1702
- }
1703
- if (!runWasOmitted && options.runId !== undefined) {
1704
- const result = {
1705
- schema: "humanish.run-result.v1",
1706
- ok: false,
1707
- cwd: options.cwd,
1708
- warnings: [],
1709
- error: {
1710
- code: "HUMANISH_WATCH_OPTION_CONFLICT",
1711
- message: "--run-id only applies to fresh watch runs; remove --run or remove --run-id."
1712
- }
1713
- };
1714
- writeResult(command, io, result, formatRunHuman);
1715
- io.setExitCode(2);
1716
- return;
1717
- }
1718
- if (port === null) {
1719
- const result = {
1720
- schema: "humanish.run-result.v1",
1721
- ok: false,
1722
- cwd: options.cwd,
1723
- warnings: [],
1724
- error: {
1725
- code: "HUMANISH_INVALID_PORT",
1726
- message: "--port must be an integer between 0 and 65535."
1727
- }
1728
- };
1729
- writeResult(command, io, result, formatRunHuman);
1730
- io.setExitCode(2);
1731
- return;
1732
- }
1733
- const requestedSimCount = simCount ?? (runWasOmitted ? 4 : undefined);
1734
- let runInput = options.run ?? "latest";
1735
- if (requestedSimCount !== undefined && requestedSimCount !== null) {
1736
- const runResult = await runDryRun({
1737
- cwd: options.cwd,
1738
- dryRun: true,
1739
- simCount: requestedSimCount,
1740
- ...(options.runId === undefined ? {} : { runId: options.runId })
1741
- });
1742
- if (!runResult.ok || !runResult.runId) {
1743
- writeResult(command, io, runResult, formatRunHuman);
1744
- io.setExitCode(2);
1745
- return;
1746
- }
1747
- runInput = runResult.runId;
1748
- }
1749
- const wantsMachine = wantsJson(command);
1750
- const shouldOpen = options.open === false ? false : options.open === true ? true : !wantsMachine && process.stdout.isTTY === true;
1751
- const wantsFollow = !wantsMachine && options.detach !== true && (options.follow !== false);
1752
- const rendered = await renderObserver(options.cwd, runInput, { open: wantsFollow ? false : shouldOpen });
1753
- let server = null;
1754
- let result = rendered;
1755
- if (rendered.ok && wantsFollow) {
1756
- server = await serveObserver(rendered, { open: shouldOpen, port });
1757
- result = {
1758
- ...rendered,
1759
- observerUrl: server.url,
1760
- serverUrl: server.url,
1761
- opened: server.opened,
1762
- ...(server.openCommand ? { openCommand: server.openCommand } : {}),
1763
- warnings: [
1764
- ...rendered.warnings,
1765
- "Live observer server is polling observer-data.json with no-store caching.",
1766
- ...(server.warning ? [server.warning] : [])
1767
- ]
1768
- };
1769
- }
1770
- writeResult(command, io, result, formatObserverHuman);
1771
- io.setExitCode(result.ok ? 0 : 2);
1772
- if (result.ok && server) {
1773
- await followObserver(io, result, server);
1774
- }
1775
- });
1776
- }
1777
- function registerObserveCommand(parent, io) {
1778
- parent
1779
- .command("observe")
1780
- .description("Follow a run's saved evidence in Observer over loopback http://127.0.0.1.")
1781
- .summary("Follow a run's saved evidence over loopback http.")
1782
- .option("--run <id>", "Run id or latest pointer.", "latest")
1783
- .option("--port <port>", "Loopback port to bind on 127.0.0.1. Defaults to an ephemeral port.", "0")
1784
- .option("--cwd <path>", "Target project directory.", ".")
1785
- .option("--open", "Open the observer in the default browser.")
1786
- .option("--no-open", "Serve without opening a browser.")
1787
- .option("--json", JSON_OPTION_DESCRIPTION)
1788
- .addHelpText("after", [
1789
- "",
1790
- "Examples:",
1791
- " humanish observe",
1792
- " humanish observe --run latest",
1793
- " humanish observe --run <runId> --port 8732",
1794
- " humanish observe --no-open --json",
1795
- "",
1796
- "The server binds 127.0.0.1 only and exposes just the run's bundle directory.",
1797
- "It stays attached until Ctrl-C; file:// security policy and live refresh are why",
1798
- "loopback http is preferred over opening the index.html path directly."
1799
- ].join("\n"))
1800
- .action(async (options, command) => {
1801
- const port = parseObserverPort(options.port);
1802
- if (port === null) {
1803
- const result = {
1804
- schema: "humanish.run-result.v1",
1805
- ok: false,
1806
- cwd: options.cwd,
1807
- warnings: [],
1808
- error: {
1809
- code: "HUMANISH_INVALID_PORT",
1810
- message: "--port must be an integer between 0 and 65535."
1811
- }
1812
- };
1813
- writeResult(command, io, result, formatRunHuman);
1814
- io.setExitCode(2);
1815
- return;
1816
- }
1817
- const rendered = await renderObserver(options.cwd, options.run, { open: false });
1818
- if (!rendered.ok || !rendered.observerPath) {
1819
- writeResult(command, io, rendered, formatObserverHuman);
1820
- io.setExitCode(2);
1821
- return;
1822
- }
1823
- // Reuse the contained current-data projection, scoped to this run. A raw static
1824
- // server would miss runtime status and could replay stored iframe grants.
1825
- const wantsMachine = wantsJson(command);
1826
- const shouldOpen = options.open === false
1827
- ? false
1828
- : options.open === true
1829
- ? true
1830
- : !wantsMachine && process.stdout.isTTY === true;
1831
- const server = await serveObserver(rendered, { open: false, port, scope: "run" });
1832
- const openResult = shouldOpen ? openTarget(server.url) : { opened: false };
1833
- const result = {
1834
- ...rendered,
1835
- observerUrl: server.url,
1836
- serverUrl: server.url,
1837
- opened: openResult.opened,
1838
- ...(openResult.command ? { openCommand: openResult.command } : {}),
1839
- warnings: [
1840
- ...rendered.warnings,
1841
- "Observer is served read-only over loopback http on 127.0.0.1; only this run's bundle directory is exposed.",
1842
- ...(openResult.warning ? [openResult.warning] : [])
1843
- ]
1844
- };
1845
- writeResult(command, io, result, formatObserverHuman);
1846
- io.setExitCode(0);
1847
- await serveObserveUntilSignal(io, server, { json: wantsMachine });
1848
- });
1849
- }
1850
- async function serveObserveUntilSignal(io, server, options) {
1851
- // Keep the JSON envelope on stdout clean: route attach/stop chatter to stderr
1852
- // for machine output, and to stdout for humans.
1853
- const note = options.json ? io.writeErr : io.writeOut;
1854
- note(`serving: ${server.url}\n`);
1855
- note("serving: press Ctrl-C to stop\n");
1856
- await new Promise((resolveWait) => {
1857
- const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
1858
- const handlers = new Map();
1859
- let stopping = false;
1860
- const stop = (signal) => {
1861
- if (stopping) {
1862
- return;
1863
- }
1864
- stopping = true;
1865
- for (const [registeredSignal, handler] of handlers.entries()) {
1866
- process.removeListener(registeredSignal, handler);
1867
- }
1868
- io.setExitCode(exitCodeForSignal(signal));
1869
- void (async () => {
1870
- try {
1871
- await server.close();
1872
- }
1873
- catch (error) {
1874
- io.writeErr(`observe cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`);
1875
- }
1876
- note("observe stopped\n");
1877
- resolveWait();
1878
- })();
1879
- };
1880
- for (const signal of signals) {
1881
- const handler = () => stop(signal);
1882
- handlers.set(signal, handler);
1883
- process.once(signal, handler);
1884
- }
1885
- });
1886
- }
1887
- function registerServeCommand(parent, io) {
1888
- parent
1889
- .command("serve")
1890
- .description("Serve the local run library over loopback http, with optional tunnel-edge authenticated exposure.")
1891
- .summary("Serve the run library; optional tunnel-edge exposure.")
1892
- .option("--cwd <path>", "Target project directory.", ".")
1893
- .option("--port <port>", "Loopback port to bind on 127.0.0.1. Defaults to an ephemeral port.", "0")
1894
- .option("--run <id>", "Land on this run id (or latest) instead of the library index.")
1895
- .option("--safe", "Serve only runs whose verify shareSafety is share_ready; everything else is absent (fail-closed).")
1896
- .option("--expose", "Declare exposure intent. Requires edge auth (--oauth or --public-url) OR --safe.")
1897
- .addOption(new Option("--tunnel <provider>", "Spawn the external tunnel binary against the loopback port.").choices(["ngrok"]))
1898
- .option("--tunnel-domain <domain>", "Reserved domain passed to ngrok as --url (e.g. observer.example.com). Requires --tunnel.")
1899
- .addOption(new Option("--oauth <provider>", "Turn on ngrok edge OAuth. Requires --tunnel.").choices(["google"]))
1900
- .option("--allow-email <addr>", "Edge OAuth allow rule: permit this email. Repeatable. Requires --oauth.", collectRepeated, [])
1901
- .option("--allow-domain <domain>", "Edge OAuth allow rule: permit this domain. Repeatable. Requires --oauth.", collectRepeated, [])
1902
- .option("--public-url <origin>", "Bring-your-own authed edge (e.g. https://observer.example.com). Requires --expose; never affects binding.")
1903
- .option("--open", "Open the library in the default browser.")
1904
- .option("--no-open", "Serve without opening a browser.")
1905
- .option("--json", JSON_OPTION_DESCRIPTION)
1906
- .addHelpText("after", [
1907
- "",
1908
- "Happy path:",
1909
- " humanish serve",
1910
- " humanish serve --expose --tunnel ngrok --oauth google --allow-email you@example.com",
1911
- " humanish serve --safe --expose --tunnel ngrok",
1912
- " humanish serve --expose --public-url https://observer.example.com",
1913
- "",
1914
- "Agent/CI path:",
1915
- " humanish serve --json --no-open",
1916
- "",
1917
- "The server always binds 127.0.0.1; exposure only ever happens through an authenticated",
1918
- "edge (ngrok --oauth google, or an operator --public-url you secure) forwarding to the",
1919
- "loopback port. humanish carries no in-process auth — the gate lives at the edge. Live",
1920
- "desktop stream URLs are never served here; remote viewers see persisted evidence only.",
1921
- "--safe composes with any exposure for defense in depth."
1922
- ].join("\n"))
1923
- .action(async (options, command) => {
1924
- const wantsMachine = wantsJson(command);
1925
- const fail = (code, message) => {
1926
- const result = {
1927
- schema: SERVE_SCHEMA,
1928
- ok: false,
1929
- cwd: options.cwd,
1930
- mode: "loopback",
1931
- safe: options.safe === true,
1932
- host: "127.0.0.1",
1933
- runsListed: 0,
1934
- warnings: [],
1935
- error: { code, message }
1936
- };
1937
- writeResult(command, io, result, formatServeHuman);
1938
- io.setExitCode(2);
1939
- };
1940
- const port = parseObserverPort(options.port);
1941
- if (port === null) {
1942
- fail("HUMANISH_INVALID_PORT", "--port must be an integer between 0 and 65535.");
1943
- return;
1944
- }
1945
- // Fail-closed exposure matrix (shared validator; tunnel-edge auth only). All guards run before
1946
- // any bind/spawn.
1947
- const exposeValidation = validateExposure("serve", {
1948
- expose: options.expose === true,
1949
- ...(options.tunnel === undefined ? {} : { tunnel: options.tunnel }),
1950
- ...(options.tunnelDomain === undefined ? {} : { tunnelDomain: options.tunnelDomain }),
1951
- ...(options.oauth === undefined ? {} : { oauth: options.oauth }),
1952
- allowEmails: options.allowEmail,
1953
- allowDomains: options.allowDomain,
1954
- ...(options.publicUrl === undefined ? {} : { publicUrl: options.publicUrl }),
1955
- safe: options.safe === true
1956
- });
1957
- if (!exposeValidation.ok) {
1958
- fail(exposeValidation.error.code, exposeValidation.error.message);
1959
- return;
1960
- }
1961
- const plan = exposeValidation.plan;
1962
- const started = await serveObserverLibrary(options.cwd, {
1963
- port,
1964
- safe: options.safe === true,
1965
- expose: plan.exposed,
1966
- edgeAuthed: plan.edgeAuthed,
1967
- ...(plan.publicOrigin ? { publicOrigin: plan.publicOrigin.origin } : {}),
1968
- ...(options.run ? { entryRunId: options.run } : {})
1969
- });
1970
- if (!started.ok) {
1971
- fail(started.error.code, started.error.message);
1972
- return;
1973
- }
1974
- const server = started.server;
1975
- let tunnel;
1976
- let publicUrl;
1977
- const warnings = [];
1978
- if (plan.exposed) {
1979
- try {
1980
- const exposeResult = await startExposedObserver(server, plan);
1981
- tunnel = exposeResult.tunnel;
1982
- publicUrl = exposeResult.publicUrl;
1983
- warnings.push(...exposeResult.warnings);
1984
- }
1985
- catch (error) {
1986
- await server.close();
1987
- if (error instanceof ServeTunnelError) {
1988
- fail(error.code, error.message);
1989
- }
1990
- else {
1991
- fail("HUMANISH_SERVE_TUNNEL_START_FAILED", `Tunnel startup failed: ${error instanceof Error ? error.message : String(error)}`);
1992
- }
1993
- return;
1994
- }
1995
- }
1996
- if (server.mode === "exposed" && options.safe !== true) {
1997
- warnings.push(`edge-authed exposure grants read access to all ${server.runsListed} local runs, including any not verified share_ready (local_only raw screenshots, blocked bundles); anyone who clears the edge auth can view them; add --safe to restrict to share_ready`);
1998
- }
1999
- if (server.mode === "exposed" && options.safe === true) {
2000
- warnings.push(`edge-authed exposure grants read access to ${server.shareReadyCount ?? 0} share_ready runs; non-share_ready runs are absent even behind the edge`);
2001
- }
2002
- if (server.mode === "share-safe-open") {
2003
- warnings.push(`serving ${server.shareReadyCount ?? 0} share_ready runs to anyone who can reach ${publicUrl ?? server.url}; non-share_ready runs are absent and their URLs 404`);
2004
- }
2005
- warnings.push("live desktop stream URLs are never served here; remote viewers see persisted evidence (screenshots, events, terminal tails) only");
2006
- // Auto-open is suppressed under --expose so the public URL is not shoved into a local opener's
2007
- // argv unasked — the exposure target is a remote device anyway. Explicit --open still honors
2008
- // intent and opens the loopback library.
2009
- const shouldOpen = options.open === false
2010
- ? false
2011
- : options.open === true
2012
- ? true
2013
- : !wantsMachine && process.stdout.isTTY === true && plan.exposed !== true;
2014
- const openResult = shouldOpen ? openTarget(server.url) : { opened: false };
2015
- if (openResult.warning) {
2016
- warnings.push(openResult.warning);
2017
- }
2018
- const result = {
2019
- schema: SERVE_SCHEMA,
2020
- ok: true,
2021
- cwd: options.cwd,
2022
- mode: server.mode,
2023
- safe: options.safe === true,
2024
- host: "127.0.0.1",
2025
- port: server.port,
2026
- url: server.url,
2027
- ...(publicUrl ? { publicUrl } : {}),
2028
- ...(tunnel ? { tunnel: { provider: "ngrok", url: tunnel.url } } : {}),
2029
- ...(plan.oauth ? { oauth: { provider: plan.oauth.provider, allowEmails: plan.oauth.allowEmails, allowDomains: plan.oauth.allowDomains } } : {}),
2030
- runsListed: server.runsListed,
2031
- ...(server.shareReadyCount !== undefined ? { shareReadyCount: server.shareReadyCount } : {}),
2032
- ...(server.entryRunId ? { entryRunId: server.entryRunId } : {}),
2033
- opened: openResult.opened,
2034
- ...(openResult.command ? { openCommand: openResult.command } : {}),
2035
- warnings
2036
- };
2037
- writeResult(command, io, result, formatServeHuman);
2038
- io.setExitCode(0);
2039
- await serveObserveUntilSignal(io, {
2040
- url: server.url,
2041
- close: async () => {
2042
- if (tunnel) {
2043
- await tunnel.close();
2044
- }
2045
- await server.close();
2046
- }
2047
- }, { json: wantsMachine });
2048
- });
2049
- }
2050
- function formatServeHuman(result) {
2051
- if (!result.ok) {
2052
- return [
2053
- "humanish serve failed",
2054
- ...(result.error ? [`error: ${result.error.code} ${result.error.message}`] : []),
2055
- ...result.warnings.map((warning) => `warning: ${warning}`)
2056
- ].join("\n") + "\n";
2057
- }
2058
- const modeSuffix = result.safe ? " (share_ready only)" : "";
2059
- const lines = [
2060
- "humanish serve",
2061
- `mode: ${result.mode}${modeSuffix}`,
2062
- `library: ${result.url ?? ""}`,
2063
- `runs: ${result.runsListed}`
2064
- ];
2065
- if (result.publicUrl) {
2066
- lines.push(`public: ${result.publicUrl}`);
2067
- }
2068
- if (result.tunnel) {
2069
- lines.push(`tunnel: ${result.tunnel.provider} ${result.tunnel.url}`);
2070
- }
2071
- if (result.oauth) {
2072
- const rules = [...result.oauth.allowEmails, ...result.oauth.allowDomains];
2073
- lines.push(`edge auth: ${result.oauth.provider} oauth${rules.length > 0 ? ` (allow: ${rules.join(", ")})` : " (no allow rule — any Google account)"}`);
2074
- }
2075
- if (result.entryRunId) {
2076
- lines.push(`entry: ${result.entryRunId}`);
2077
- }
2078
- lines.push(`opened: ${result.opened === true ? "yes" : "no"}`);
2079
- for (const warning of result.warnings) {
2080
- lines.push(`warning: ${warning}`);
2081
- }
2082
- return lines.join("\n") + "\n";
2083
- }
2084
- function registerFeedbackCommands(parent, io) {
2085
- const feedback = parent
2086
- .command("feedback")
2087
- .description("Create public-safe feedback drafts without GitHub API mutation.")
2088
- .summary("Create public-safe feedback drafts, no GitHub API.");
2089
- feedback
2090
- .command("list")
2091
- .description("List recorded feedback candidates and any saved draft.")
2092
- .addHelpText("after", "\nWith no candidates, feedback draft and feedback issue can generate a run-summary follow-up. Public drafting still requires share_ready verification.\n")
2093
- .option("--run <id>", "Run id or latest pointer.", "latest")
2094
- .option("--cwd <path>", "Target project directory.", ".")
2095
- .option("--json", JSON_OPTION_DESCRIPTION)
2096
- .action(async (options, command) => {
2097
- const result = await listFeedback(options.cwd, options.run);
2098
- writeResult(command, io, result, formatFeedbackHuman);
2099
- io.setExitCode(result.ok ? 0 : 2);
2100
- });
2101
- feedback
2102
- .command("draft")
2103
- .description("Generate a public-safe feedback draft from verified evidence.")
2104
- .option("--run <id>", "Run id or latest pointer.", "latest")
2105
- .option("--cwd <path>", "Target project directory.", ".")
2106
- .option("--analysis <id>", "Independent analysis version; use with --finding.")
2107
- .option("--finding <id>", "Finding within --analysis, separate from participant candidates.")
2108
- .option("--candidate <id>", "Which finding to draft (ids from `feedback list`); default: the first.")
2109
- .option("--json", JSON_OPTION_DESCRIPTION)
2110
- .action(async (options, command) => {
2111
- const result = await draftFeedback(options.cwd, options.run, candidateOption(options));
2112
- writeResult(command, io, result, formatFeedbackHuman);
2113
- io.setExitCode(result.ok ? 0 : 2);
2114
- });
2115
- feedback
2116
- .command("verify")
2117
- .description("Verify the feedback draft for public issue eligibility.")
2118
- .option("--run <id>", "Run id or latest pointer.", "latest")
2119
- .option("--cwd <path>", "Target project directory.", ".")
2120
- .option("--analysis <id>", "Independent analysis version; use with --finding.")
2121
- .option("--finding <id>", "Finding within --analysis, separate from participant candidates.")
2122
- .option("--candidate <id>", "Which finding to verify (ids from `feedback list`); default: the first.")
2123
- .option("--json", JSON_OPTION_DESCRIPTION)
2124
- .action(async (options, command) => {
2125
- const result = await verifyFeedback(options.cwd, options.run, candidateOption(options));
2126
- writeResult(command, io, result, formatFeedbackHuman);
2127
- io.setExitCode(result.ok ? 0 : 2);
2128
- });
2129
- feedback
2130
- .command("issue")
2131
- .description("Print Markdown for a public GitHub issue. Does not mutate GitHub.")
2132
- .option("--run <id>", "Run id or latest pointer.", "latest")
2133
- .option("--cwd <path>", "Target project directory.", ".")
2134
- .requiredOption("--repo <owner/repo>", "Repository slug used in rendered filing instructions.")
2135
- .option("--format <format>", "Output format.", "markdown")
2136
- .option("--analysis <id>", "Independent analysis version; use with --finding.")
2137
- .option("--finding <id>", "Finding within --analysis, separate from participant candidates.")
2138
- .option("--candidate <id>", "Which finding to file (ids from `feedback list`); default: the first.")
2139
- .option("--json", JSON_OPTION_DESCRIPTION)
2140
- .action(async (options, command) => {
2141
- const result = await renderIssueMarkdown(options.cwd, options.run, options.repo, candidateOption(options));
2142
- if (wantsJson(command)) {
2143
- io.writeOut(`${JSON.stringify(result, null, 2)}\n`);
2144
- }
2145
- else if (options.format !== "markdown") {
2146
- io.writeErr("Only --format markdown is supported.\n");
2147
- io.setExitCode(2);
2148
- return;
2149
- }
2150
- else if (result.ok && result.issueMarkdown) {
2151
- io.writeOut(result.issueMarkdown);
2152
- }
2153
- else {
2154
- io.writeErr(formatFeedbackHuman(result));
2155
- }
2156
- io.setExitCode(result.ok ? 0 : 2);
2157
- });
2158
- feedback
2159
- .command("issue-url")
2160
- .description("Print a prefilled public issue URL. Does not mutate GitHub.")
2161
- .option("--run <id>", "Run id or latest pointer.", "latest")
2162
- .option("--cwd <path>", "Target project directory.", ".")
2163
- .requiredOption("--repo <owner/repo>", "Repository slug used in the generated URL.")
2164
- .option("--analysis <id>", "Independent analysis version; use with --finding.")
2165
- .option("--finding <id>", "Finding within --analysis, separate from participant candidates.")
2166
- .option("--candidate <id>", "Which finding to link (ids from `feedback list`); default: the first.")
2167
- .option("--json", JSON_OPTION_DESCRIPTION)
2168
- .action(async (options, command) => {
2169
- const result = await renderIssueUrl(options.cwd, options.run, options.repo, candidateOption(options));
2170
- if (wantsJson(command)) {
2171
- io.writeOut(`${JSON.stringify(result, null, 2)}\n`);
2172
- }
2173
- else if (result.ok && result.issueUrl) {
2174
- io.writeOut(`${result.issueUrl}\n`);
2175
- }
2176
- else {
2177
- io.writeErr(formatFeedbackHuman(result));
2178
- }
2179
- io.setExitCode(result.ok ? 0 : 2);
2180
- });
2181
- }
2182
- function registerLabCommands(parent, io) {
2183
- const lab = parent
2184
- .command("lab")
2185
- .description("List, inspect, and run Humanish lab manifests.")
2186
- .summary("List, inspect, and run Humanish lab manifests.");
2187
- lab
2188
- .command("list")
2189
- .description("List committed and ignored Humanish lab manifests.")
2190
- .option("--cwd <path>", "Target project directory.", ".")
2191
- .option("--json", JSON_OPTION_DESCRIPTION)
2192
- .action(async (options, command) => {
2193
- const result = await listLabManifests(options.cwd);
2194
- writeResult(command, io, result, formatLabListHuman);
2195
- io.setExitCode(0);
2196
- });
2197
- lab
2198
- .command("inspect")
2199
- .argument("<lab>", "Lab id or .yaml path.")
2200
- .description("Inspect a Humanish lab manifest without running it.")
2201
- .option("--cwd <path>", "Target project directory.", ".")
2202
- .option("--json", JSON_OPTION_DESCRIPTION)
2203
- .action(async (labName, options, command) => {
2204
- const result = await inspectLabManifest(options.cwd, labName);
2205
- writeResult(command, io, result, formatLabInspectHuman);
2206
- io.setExitCode(result.ok ? 0 : 2);
2207
- });
2208
- lab
2209
- .command("preflight")
2210
- .argument("<lab>", "Lab id or .yaml path.")
2211
- .description("Check lab metadata or explicitly probe reachability. Metadata mode does not verify setup; use doctor --lab <lab> first.")
2212
- .option("--cwd <path>", "Target project directory.", ".")
2213
- .addOption(new Option("--reachability <mode>", "Reachability mode.").choices(["metadata", "public-preview", "sandbox-loopback", "prepared-host"]).default("metadata"))
2214
- .option("--timeout-ms <ms>", "Target reachability timeout.", String(30_000))
2215
- .option("--env-file <path>", "Load a local env file for this preflight without persisting values.")
2216
- .option("--json", JSON_OPTION_DESCRIPTION)
2217
- .action(async (labName, options, command) => {
2218
- if (!await applyEnvFileOption({
2219
- command,
2220
- cwd: options.cwd,
2221
- envFile: options.envFile,
2222
- io
2223
- })) {
2224
- return;
2225
- }
2226
- const timeoutMs = parsePositiveInteger(options.timeoutMs);
2227
- if (timeoutMs === null) {
2228
- const result = {
2229
- schema: "humanish.lab-preflight-result.v1",
2230
- ok: false,
2231
- cwd: resolve(options.cwd),
2232
- lab: labName,
2233
- reachability: options.reachability,
2234
- checks: [{ name: "timeout", ok: false, message: "--timeout-ms must be a positive integer." }],
2235
- targets: [],
2236
- sandbox: { created: false },
2237
- spend: { e2bDesktop: false, model: false },
2238
- warnings: [],
2239
- error: {
2240
- code: "HUMANISH_LAB_PREFLIGHT_INVALID_OPTION",
2241
- message: "--timeout-ms must be a positive integer."
2242
- }
2243
- };
2244
- writeResult(command, io, result, formatLabPreflightHuman);
2245
- io.setExitCode(2);
2246
- return;
2247
- }
2248
- const result = await runLabPreflight({
2249
- cwd: options.cwd,
2250
- lab: labName,
2251
- reachability: options.reachability,
2252
- timeoutMs
2253
- });
2254
- writeResult(command, io, result, formatLabPreflightHuman);
2255
- io.setExitCode(result.ok ? 0 : 2);
2256
- });
2257
- lab
2258
- .command("cleanup")
2259
- .argument("[lab]", "Provider-backed lab to clean up.", "oss")
2260
- .description("Sweep stale provider resources from a crashed prior process, by provider metadata, without printing provider ids. humanish never enumerates an account by default: set HUMANISH_OSS_META_ALLOW_PROVIDER_LIST=1 to opt in for this maintainer-only sweep.")
2261
- .option("--json", JSON_OPTION_DESCRIPTION)
2262
- .action(async (labName, _options, command) => {
2263
- if (labName !== "oss") {
2264
- const result = {
2265
- schema: "humanish.oss-meta-lab-cleanup-result.v1",
2266
- ok: false,
2267
- lab: labName,
2268
- cleanup: { killed: 0, skipped: 0, errors: [`Unsupported cleanup lab '${labName}'.`] }
2269
- };
2270
- writeResult(command, io, result, formatOssMetaLabCleanupHuman);
2271
- io.setExitCode(2);
2272
- return;
2273
- }
2274
- const cleanup = await cleanupStaleOssMetaLabSandboxes();
2275
- const result = {
2276
- schema: "humanish.oss-meta-lab-cleanup-result.v1",
2277
- ok: cleanup.errors.length === 0,
2278
- lab: "oss",
2279
- cleanup
2280
- };
2281
- writeResult(command, io, result, formatOssMetaLabCleanupHuman);
2282
- io.setExitCode(result.ok ? 0 : 2);
2283
- });
2284
- lab
2285
- .command("run")
2286
- .argument("<lab>", "Lab id or .yaml path.")
2287
- .description("Run a Humanish lab manifest. Same as `humanish run <lab>`, grouped under `lab`.")
2288
- .option("--env-file <path>", "Load a local env file for this lab without persisting values.")
2289
- .option("--dry-run", "Render contract evidence without live provider spend. The bundled OSS lab defaults to this mode.")
2290
- .option("--codex-app-server", "Meta only: use Codex app-server client mode for headed desktop actor surfaces.")
2291
- .option("--open", "Open the observer in the default browser.")
2292
- .option("--no-open", "Render without opening a browser.")
2293
- .option("--detach", "Render/open once and exit without attached watch server.")
2294
- .option("--port <port>", "Local observer server port when following.", "0")
2295
- .option("--sims <count>", "Override synthetic sims or headed desktop lanes.")
2296
- .option("--count <count>", "CUA/meta only: override headed desktop lane count.")
2297
- .option("--rerun-failed-from <run>", "CUA fan-out only: create a new run for failed lanes from a prior run.")
2298
- .option("--lanes <lane-ids>", "CUA rerun only: comma-separated lane ids to rerun from the source run.")
2299
- .option("--limit <count>", "Smoke labs only: override repo limit.")
2300
- .option("--run-id <id>", "Explicit lab run id.")
2301
- .option("--cwd <path>", "Target project directory.", ".")
2302
- .option("--repo <owner/repo>", "Smoke/meta only: GitHub repo slug. Repeatable.", collectRepeated, [])
2303
- .option("--repos <owner/repo,...>", "Smoke/meta only: comma-separated GitHub repo slugs.")
2304
- .option("--redact-repos", "Meta only: redact repo labels in durable lab artifacts.")
2305
- .option("--no-redact-repos", "Meta only: persist repo labels in durable lab artifacts. Use only for public-safe runs.")
2306
- .option("--keep", "Smoke labs only: keep disposable clone sandbox for debugging.")
2307
- .option("--scorer <path>", "Terminal/computer-use/shared-world labs only: repo-relative adopter scorer module (.mjs). Overrides review.scorer.ref. Executable code — review it as code.")
2308
- .option("--json", JSON_OPTION_DESCRIPTION)
2309
- .addHelpText("after", [
2310
- "",
2311
- "Examples:",
2312
- " humanish lab run first-run",
2313
- " humanish lab run fanout-demo --rerun-failed-from latest --lanes lane-02,lane-04",
2314
- " humanish lab run oss --dry-run --json --no-open",
2315
- " humanish lab run my-terminal-lab --scorer scorers/product.mjs",
2316
- " humanish lab run .humanish/labs/private-dogfood.yaml --env-file .humanish/local/provider.env",
2317
- "",
2318
- "Human watch path:",
2319
- " humanish watch first-run",
2320
- " humanish watch --lab .humanish/labs/local.yaml",
2321
- "",
2322
- "OSS safety:",
2323
- " Live OSS meta-lab manifests fail closed pending credential isolation."
2324
- ].join("\n"))
2325
- .action(async (labName, options, command) => {
2326
- if (!await applyEnvFileOption({
2327
- command,
2328
- cwd: options.cwd,
2329
- envFile: options.envFile,
2330
- io
2331
- })) {
2332
- return;
2333
- }
2334
- await runLabCommand({
2335
- command,
2336
- io,
2337
- lab: labName,
2338
- mode: "run",
2339
- options
2340
- });
2341
- });
2342
- lab
2343
- .command("oss", { hidden: true })
2344
- .description("Alias: run the bundled OSS meta-lab dry-run contract.")
2345
- .option("--env-file <path>", "Load a local env file for this lab without persisting values.")
2346
- .option("--repos <owner/repo,...>", "Comma-separated GitHub repo slugs.")
2347
- .option("--repo <owner/repo>", "GitHub repo slug. Repeatable.", collectRepeated, [])
2348
- .option("--count <count>", "Number of contract lanes to assign.", String(DEFAULT_OSS_REPOS.length))
2349
- .option("--sims <count>", "Alias for --count.")
2350
- .option("--run-id <id>", "Explicit lab run id.")
2351
- .option("--cwd <path>", "Host directory for ignored .humanish lab report.", ".")
2352
- .option("--dry-run", "Render the Observer-of-Observers contract without provider spend or live E2B launch (default).")
2353
- .option("--open", "Open the observer in the default browser.")
2354
- .option("--no-open", "Render without opening a browser.")
2355
- .option("--detach", "Render/open once and exit without attached watch server.")
2356
- .option("--redact-repos", "Redact repo labels in durable lab artifacts.")
2357
- .option("--no-redact-repos", "Persist repo labels in durable lab artifacts. Defaults to redacted when a GitHub token is present.")
2358
- .option("--port <port>", "Local observer server port when following.", "0")
2359
- .option("--smoke", "Run the disposable local clone smoke harness instead of headed meta-sims.")
2360
- .option("--limit <count>", "Smoke mode only: number of selected repos to trial.", String(DEFAULT_OSS_REPOS.length))
2361
- .option("--keep", "Smoke mode only: keep disposable clone sandbox for debugging.")
2362
- .option("--json", JSON_OPTION_DESCRIPTION)
2363
- .addHelpText("after", [
2364
- "",
2365
- "Preferred paths:",
2366
- " humanish watch oss",
2367
- " humanish lab run oss --dry-run",
2368
- "",
2369
- "Repo selection:",
2370
- " humanish watch --lab .humanish/labs/local-oss.yaml",
2371
- " humanish lab run oss --repos CorentinTh/it-tools,drawdb-io/drawdb,maciekt07/TodoApp,lissy93/dashy",
2372
- " humanish lab run oss --repo CorentinTh/it-tools --repo drawdb-io/drawdb --count 4",
2373
- "",
2374
- "Agent/CI path:",
2375
- " humanish lab run oss --dry-run --json --no-open",
2376
- "",
2377
- "Disposable clone smoke:",
2378
- " humanish lab run oss-smoke --limit 1 --keep",
2379
- " humanish lab oss-smoke --limit 1 --keep",
2380
- "",
2381
- "Shape:",
2382
- " The top-level Observer shows contract-only lanes for the selected repo labels.",
2383
- " No repo clone, provider sandbox, credential forwarding, or Codex actor runs.",
2384
- "",
2385
- "Safety:",
2386
- " Only GitHub owner/repo slugs are accepted. Live OSS meta-lab execution",
2387
- " fails closed pending credential isolation. Repo labels are redacted by",
2388
- " default when overridden; use --no-redact-repos only for public-safe repos."
2389
- ].join("\n"))
2390
- .action(async (options, command) => {
2391
- if (!await applyEnvFileOption({
2392
- command,
2393
- cwd: options.cwd,
2394
- envFile: options.envFile,
2395
- io
2396
- })) {
2397
- return;
2398
- }
2399
- if (options.smoke) {
2400
- await runOssSmokeAction({ command, io, options });
2401
- return;
2402
- }
2403
- const countInput = options.sims ?? options.count;
2404
- const count = parsePositiveInteger(countInput);
2405
- const dryRun = options.dryRun ?? true;
2406
- const port = parseObserverPort(options.port);
2407
- if (port === null) {
2408
- const result = {
2409
- schema: "humanish.oss-meta-lab-result.v1",
2410
- ok: false,
2411
- assignments: [],
2412
- cwd: options.cwd,
2413
- dryRun,
2414
- error: {
2415
- code: "HUMANISH_META_RUN_FAILED",
2416
- message: "--port must be an integer between 0 and 65535."
2417
- },
2418
- liveRequested: !dryRun,
2419
- repos: [...options.repo, ...(options.repos ? [options.repos] : [])],
2420
- sandboxes: [],
2421
- warnings: []
2422
- };
2423
- writeResult(command, io, result, formatOssMetaLabHuman);
2424
- io.setExitCode(2);
2425
- return;
2426
- }
2427
- const wantsMachine = wantsJson(command);
2428
- const shouldOpen = options.open === false ? false : options.open === true ? true : !wantsMachine && process.stdout.isTTY === true;
2429
- const wantsFollow = !wantsMachine && options.detach !== true && !dryRun;
2430
- const repoOverrideRequested = options.repo.length > 0 || options.repos !== undefined;
2431
- const redactRepoNames = options.redactRepos ?? (repoOverrideRequested ? true : undefined);
2432
- let server = null;
2433
- let liveRefresh = null;
2434
- let result;
2435
- try {
2436
- result = await runOssMetaLab({
2437
- ...(wantsFollow ? { completionTimeoutMs: 0 } : {}),
2438
- ...(options.codexAppServer === undefined ? {} : { codexAppServer: options.codexAppServer }),
2439
- cwd: options.cwd,
2440
- ...(wantsFollow
2441
- ? {
2442
- onObserverReady: async (observer) => {
2443
- if (!server) {
2444
- server = await serveObserver(observer, { open: shouldOpen, port });
2445
- }
2446
- }
2447
- }
2448
- : {}),
2449
- open: wantsFollow ? false : shouldOpen,
2450
- ...(redactRepoNames === undefined ? {} : { redactRepoNames }),
2451
- repos: [...options.repo, ...(options.repos ? [options.repos] : [])],
2452
- ...(count === null ? { count: Number.NaN } : { count }),
2453
- dryRun,
2454
- ...(options.runId === undefined ? {} : { runId: options.runId })
2455
- });
2456
- }
2457
- catch (error) {
2458
- const earlyServer = server;
2459
- await earlyServer?.close().catch((cleanupError) => {
2460
- io.writeErr(`watch cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}\n`);
2461
- });
2462
- server = null;
2463
- throw error;
2464
- }
2465
- let output = result;
2466
- if (server && shouldServeOssMetaLabObserver(result, { wantsFollow: true })) {
2467
- liveRefresh = startOssMetaLabLiveRefresh(result);
2468
- output = withOssMetaLabServer(result, server);
2469
- }
2470
- else if (shouldServeOssMetaLabObserver(result, { wantsFollow })) {
2471
- server = await serveObserver(result.observer, { open: shouldOpen, port });
2472
- liveRefresh = startOssMetaLabLiveRefresh(result);
2473
- output = withOssMetaLabServer(result, server);
2474
- }
2475
- else {
2476
- const earlyServer = server;
2477
- await earlyServer?.close().catch((error) => {
2478
- io.writeErr(`watch cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`);
2479
- });
2480
- server = null;
2481
- }
2482
- const exitCode = exitCodeForOssMetaLab(output);
2483
- writeResult(command, io, output, formatOssMetaLabHuman);
2484
- io.setExitCode(exitCode);
2485
- if (shouldForceExitAfterOssMetaLab(output, { detach: options.detach === true, wantsMachine })) {
2486
- // The E2B SDK keeps local handles open after stream URL creation. Detach should
2487
- // return the user's shell, and JSON mode should exit after printing the result.
2488
- setTimeout(() => process.exit(exitCode), 50);
2489
- }
2490
- if (server && output.observer?.ok) {
2491
- await followObserver(io, output.observer, server, output.liveRequested
2492
- ? {
2493
- onStop: async () => {
2494
- const cleanup = liveRefresh
2495
- ? await liveRefresh.cleanup()
2496
- : await cleanupOssMetaLabSandboxes(output);
2497
- return [
2498
- `E2B sandbox cleanup killed ${cleanup.killed}, skipped ${cleanup.skipped}.`,
2499
- ...cleanup.errors.map((error) => `E2B sandbox cleanup error: ${error}`)
2500
- ];
2501
- }
2502
- }
2503
- : {});
2504
- }
2505
- });
2506
- lab
2507
- .command("oss-smoke", { hidden: true })
2508
- .description("Clone lightweight public OSS repos, try Humanish setup/proof, then discard clones.")
2509
- .option("--repos <owner/repo,...>", "Comma-separated public GitHub repo slugs.")
2510
- .option("--repo <owner/repo>", "Public GitHub repo slug. Repeatable.", collectRepeated, [])
2511
- .option("--limit <count>", "Number of selected repos to trial.", String(DEFAULT_OSS_REPOS.length))
2512
- .option("--run-id <id>", "Explicit lab run id.")
2513
- .option("--cwd <path>", "Host directory for ignored .humanish lab report.", ".")
2514
- .option("--keep", "Keep disposable clone sandbox for debugging.")
2515
- .option("--json", JSON_OPTION_DESCRIPTION)
2516
- .addHelpText("after", [
2517
- "",
2518
- "Examples:",
2519
- " humanish lab oss-smoke",
2520
- " humanish lab oss-smoke --repos CorentinTh/it-tools,drawdb-io/drawdb",
2521
- " humanish lab oss-smoke --limit 1 --keep --json",
2522
- "",
2523
- "Safety:",
2524
- " Only public GitHub owner/repo slugs are accepted. Clones live under ignored .humanish/",
2525
- " runtime state and are removed by default."
2526
- ].join("\n"))
2527
- .action(async (options, command) => {
2528
- await runOssSmokeAction({ command, io, options });
2529
- });
2530
- }
2531
- async function runOssSmokeAction(args) {
2532
- const limit = parsePositiveInteger(args.options.limit);
2533
- const labOptions = {
2534
- cwd: args.options.cwd,
2535
- limit: limit ?? Number.NaN,
2536
- repos: [...args.options.repo, ...(args.options.repos ? [args.options.repos] : [])],
2537
- ...(args.options.keep === undefined ? {} : { keep: args.options.keep }),
2538
- ...(args.options.runId === undefined ? {} : { runId: args.options.runId })
2539
- };
2540
- const result = await runOssLab(labOptions);
2541
- writeResult(args.command, args.io, result, formatOssLabHuman);
2542
- args.io.setExitCode(result.ok ? 0 : 2);
2543
- }
2544
- /**
2545
- * Resolve `review.scorer.ref` (or the `--scorer` override) to a loaded adopter scorer, fail-closed
2546
- * (typed error) PRE-SPEND. Precedence: CLI `--scorer` overrides the manifest; `source` records which
2547
- * won. No scorer declared → `{ ok: true }` with no scorer. A declared scorer on an unsupported
2548
- * backend, or a bad/unreadable/broken ref, → `{ ok: false }` so the caller aborts with exit 2.
2549
- */
2550
- async function maybeLoadAdapterScorer(args) {
2551
- const ref = args.flag ?? args.config.review?.scorer?.ref;
2552
- if (ref === undefined)
2553
- return { ok: true };
2554
- const source = args.flag !== undefined ? "cli-flag" : "manifest";
2555
- const loaded = await loadAdapterScorer({ cwd: args.cwd, ref, backend: args.backend, source });
2556
- if (!loaded.ok)
2557
- return { ok: false, error: loaded.error };
2558
- return { ok: true, scorer: { hooks: loaded.hooks, provenance: loaded.provenance } };
2559
- }
2560
- /** Terminal route hooks bag from a loaded scorer (deriveArtifacts is browser-only, dropped here). */
2561
- function terminalScorerHooks(scorer) {
2562
- const { hooks } = scorer;
2563
- return {
2564
- ...(hooks.score ? { score: hooks.score } : {}),
2565
- ...(hooks.deriveFeedback ? { deriveFeedback: hooks.deriveFeedback } : {})
2566
- };
2567
- }
2568
- /** Browser route hooks bag from a loaded scorer (score + deriveFeedback + deriveArtifacts). */
2569
- function browserScorerHooks(scorer) {
2570
- const { hooks } = scorer;
2571
- return {
2572
- ...(hooks.score ? { score: hooks.score } : {}),
2573
- ...(hooks.deriveFeedback ? { deriveFeedback: hooks.deriveFeedback } : {}),
2574
- ...(hooks.deriveArtifacts ? { deriveArtifacts: hooks.deriveArtifacts } : {})
2575
- };
2576
- }
2577
- async function runLabCommand(args) {
2578
- const resolved = await resolveLabManifest(args.options.cwd, args.lab);
2579
- if (!resolved.ok) {
2580
- writeResult(args.command, args.io, resolved, formatLabResolveFailureHuman);
2581
- args.io.setExitCode(2);
2582
- return;
2583
- }
2584
- // Surface forward-declared-field + .yml warnings on run/watch too, not only on inspect.
2585
- // Otherwise a setting that does nothing is silently swallowed on the path users actually run.
2586
- for (const warning of resolved.warnings) {
2587
- args.io.writeErr(`warning: ${warning}\n`);
2588
- }
2589
- const config = resolved.config;
2590
- // The run's identity (#455): which manifest, where it lives, and whether it is committed, a
2591
- // local overlay, or an explicit path. Resolved once here — the only place that knows all three —
2592
- // and carried into the run's status record and bundle so the filesystem can answer "which lab
2593
- // produced this run" without the old `persona.source = "lab:<id>"` string convention.
2594
- const lab = { id: config.id, path: resolved.path, origin: resolved.origin };
2595
- // Named here, once, for every backend: a synthetic or terminal result carries no labId, so the
2596
- // starter lab `first-run` went unnamed in telemetry while the computer-use ones were named.
2597
- noteStudyFacts(args.command, deriveStudyFacts({ labId: config.id }));
2598
- const backend = selectLabBackend(config);
2599
- if (backend !== "cua" && labRerunFlagsRequested(args.options)) {
2600
- writeUnsupportedRerunFlagsResult(args, backend);
2601
- return;
2602
- }
2603
- // Exposure serves a live desktop, which only the computer-use backend produces. Refuse it on any
2604
- // other backend rather than silently ignoring it.
2605
- if (backend !== "cua" && watchExposeRequested(args.options)) {
2606
- const result = {
2607
- schema: "humanish.run-result.v1",
2608
- ok: false,
2609
- cwd: resolve(args.options.cwd),
2610
- warnings: [],
2611
- error: {
2612
- code: "HUMANISH_WATCH_OPTION_CONFLICT",
2613
- message: `--expose/--tunnel/--oauth stream a live desktop and apply only to computer-use labs; this lab resolved to ${backend}.`
2614
- }
2615
- };
2616
- writeResult(args.command, args.io, result, formatRunHuman);
2617
- args.io.setExitCode(2);
2618
- return;
2619
- }
2620
- // #316: resolve + load a config-declared/CLI-flagged adopter scorer FAIL-CLOSED, before any spend.
2621
- // A declared gate that cannot load (bad ref, not found, load failure, no hooks, unsupported backend)
2622
- // aborts with exit 2 rather than green-passing.
2623
- const scorerLoad = await maybeLoadAdapterScorer({
2624
- cwd: args.options.cwd,
2625
- config,
2626
- backend,
2627
- flag: args.options.scorer
2628
- });
2629
- if (!scorerLoad.ok) {
2630
- const result = {
2631
- schema: "humanish.run-result.v1",
2632
- ok: false,
2633
- cwd: resolve(args.options.cwd),
2634
- warnings: [],
2635
- error: scorerLoad.error
2636
- };
2637
- writeResult(args.command, args.io, result, formatRunHuman);
2638
- args.io.setExitCode(2);
2639
- return;
2640
- }
2641
- const scorer = scorerLoad.scorer;
2642
- if (scorer) {
2643
- // Cross-repo guardrail: `humanish lab run` now import()s host JS named in the manifest. Surface it
2644
- // visibly so the invoker (who may not be the manifest author) knows executable code just ran.
2645
- args.io.writeErr(`warning: review scorer ${scorer.provenance.ref} (${scorer.provenance.source}) is executable host code loaded and run in-process — review it as code, not config.\n`);
2646
- }
2647
- const analysisBudget = automaticAnalysisBudget(config.review?.analysis, backend);
2648
- if (analysisBudget && resolveLabDryRun(config, args.options.dryRun, true) === false) {
2649
- args.io.writeErr(`${formatAutomaticAnalysisBudget(analysisBudget)}\n`);
2650
- }
2651
- switch (backend) {
2652
- case "synthetic":
2653
- await runSyntheticBackend({ ...args, config, labProvenance: lab });
2654
- return;
2655
- case "meta":
2656
- await runMetaBackend({ ...args, config, labProvenance: lab });
2657
- return;
2658
- case "smoke":
2659
- await runSmokeBackend({ ...args, config, labProvenance: lab });
2660
- return;
2661
- case "cua":
2662
- await runCuaBackend({ ...args, config, labProvenance: lab, ...(scorer ? { scorer } : {}) });
2663
- return;
2664
- case "scripted":
2665
- await runScriptedBackend({ ...args, config, labProvenance: lab });
2666
- return;
2667
- case "terminal":
2668
- await runTerminalBackend({ ...args, config, labProvenance: lab, ...(scorer ? { scorer } : {}) });
2669
- return;
2670
- case "shared-world":
2671
- await runSharedWorldBackend({ ...args, config, labProvenance: lab, ...(scorer ? { scorer } : {}) });
2672
- return;
2673
- case "concurrent-shared-world":
2674
- await runConcurrentSharedWorldBackend({ ...args, config, labProvenance: lab, ...(scorer ? { scorer } : {}) });
2675
- return;
2676
- default:
2677
- // Compile-time exhaustiveness: a future backend must be handled here, not silently no-op.
2678
- throw new Error(`Unhandled lab backend: ${String(backend)}`);
2679
- }
2680
- }
2681
- function labRerunFlagsRequested(options) {
2682
- return options.rerunFailedFrom !== undefined || options.lanes !== undefined;
2683
- }
2684
- function writeUnsupportedRerunFlagsResult(args, backend) {
2685
- const result = {
2686
- schema: "humanish.run-result.v1",
2687
- ok: false,
2688
- cwd: resolve(args.options.cwd),
2689
- warnings: [],
2690
- error: {
2691
- code: "HUMANISH_UNSUPPORTED_RERUN_FLAGS",
2692
- message: `--rerun-failed-from/--lanes apply only to CUA fan-out labs; this lab resolved to ${backend}.`
2693
- }
2694
- };
2695
- writeResult(args.command, args.io, result, formatRunHuman);
2696
- args.io.setExitCode(2);
2697
- }
2698
- async function runSyntheticBackend(args) {
2699
- const simCount = parseLabCount(args.options.sims, args.config.actors[0]?.count ?? 4);
2700
- if (simCount === null) {
2701
- const result = {
2702
- schema: "humanish.run-result.v1",
2703
- ok: false,
2704
- cwd: args.options.cwd,
2705
- warnings: [],
2706
- error: {
2707
- code: "HUMANISH_INVALID_SIM_COUNT",
2708
- message: "--sims must be a positive integer."
2709
- }
2710
- };
2711
- writeResult(args.command, args.io, result, formatRunHuman);
2712
- args.io.setExitCode(2);
2713
- return;
2714
- }
2715
- const outcome = await runLab(args.config, {
2716
- cwd: args.options.cwd,
2717
- ...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
2718
- count: simCount,
2719
- ...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
2720
- ...(args.options.runId === undefined ? {} : { runId: args.options.runId })
2721
- });
2722
- if (outcome.backend !== "synthetic") {
2723
- throw new Error(`Expected synthetic backend, got ${outcome.backend}.`);
2724
- }
2725
- const runResult = outcome.result;
2726
- if (args.mode === "run") {
2727
- // `run` and `watch` used to disagree about whether a bundle has observer/index.html: watch
2728
- // rendered it to serve it, run did not, and the first `export` of a run bundle failed (#597,
2729
- // found by the 0.72.0 dogfood participant). Render it here too, so the two commands write the
2730
- // same bundle; a render failure is a warning on the result, never a failed run.
2731
- await renderObserverForRun(args.options.cwd, runResult);
2732
- writeResult(args.command, args.io, runResult, formatRunHuman);
2733
- args.io.setExitCode(runResult.ok ? 0 : 2);
2734
- return;
2735
- }
2736
- if (!runResult.ok || !runResult.runId) {
2737
- writeResult(args.command, args.io, runResult, formatRunHuman);
2738
- args.io.setExitCode(2);
2739
- return;
2740
- }
2741
- const openOverride = args.options.open ?? args.config.defaults?.open;
2742
- await renderAndMaybeFollowObserver({
2743
- command: args.command,
2744
- cwd: args.options.cwd,
2745
- io: args.io,
2746
- port: args.options.port ?? "0",
2747
- runInput: runResult.runId,
2748
- ...(args.options.detach === undefined ? {} : { detach: args.options.detach }),
2749
- ...(openOverride === undefined ? {} : { open: openOverride })
2750
- });
2751
- }
2752
- /**
2753
- * Default browser-open policy for a lab backend run. Mirrors the observe/watch gate:
2754
- * an explicit --open/--no-open wins; --json (machine mode) never auto-opens; otherwise a
2755
- * lab-config `defaults.open` wins, and the final fallback opens only for an interactive
2756
- * `watch` on a real TTY. Extracted so all lab backends share one gate (and one test).
2757
- */
2758
- export function resolveBackendShouldOpen(args) {
2759
- if (args.optionOpen === false)
2760
- return false;
2761
- if (args.wantsMachine)
2762
- return args.optionOpen === true;
2763
- return args.optionOpen ?? args.defaultsOpen ?? (process.stdout.isTTY === true && args.mode === "watch");
2764
- }
2765
- async function runCuaBackend(args) {
2766
- const wantsMachine = wantsJson(args.command);
2767
- const shouldOpen = resolveBackendShouldOpen({
2768
- optionOpen: args.options.open,
2769
- defaultsOpen: args.config.defaults?.open,
2770
- mode: args.mode,
2771
- wantsMachine
2772
- });
2773
- const laneIds = parseLaneIds(args.options.lanes);
2774
- if (laneIds.length > 0 && !args.options.rerunFailedFrom) {
2775
- args.io.writeErr("error: --lanes requires --rerun-failed-from.\n");
2776
- args.io.setExitCode(2);
2777
- return;
2778
- }
2779
- const count = parseLabCount(args.options.count ?? args.options.sims, args.config.actors[0]?.count ?? 1);
2780
- if (count === null) {
2781
- args.io.writeErr("error: --count/--sims must be a positive integer.\n");
2782
- args.io.setExitCode(2);
2783
- return;
2784
- }
2785
- const dryRun = resolveLabDryRun(args.config, args.options.dryRun, true) ?? true;
2786
- const port = parseObserverPort(args.options.port ?? "0");
2787
- const wantsFollow = args.mode === "watch" && !wantsMachine && args.options.detach !== true && dryRun !== true;
2788
- const failCua = (code, message) => {
2789
- const result = {
2790
- schema: CUA_ACTOR_LAB_SCHEMA,
2791
- ok: false,
2792
- cwd: args.options.cwd,
2793
- labId: args.config.id,
2794
- actor: args.config.actors[0]?.type ?? "",
2795
- appUrl: "",
2796
- dryRun,
2797
- runId: args.options.runId ?? "not-created",
2798
- warnings: [],
2799
- error: { code, message }
2800
- };
2801
- writeResult(args.command, args.io, result, formatCuaLabHuman);
2802
- args.io.setExitCode(2);
2803
- };
2804
- if (port === null) {
2805
- failCua("HUMANISH_WATCH_OPTION_CONFLICT", "--port must be an integer between 0 and 65535.");
2806
- return;
2807
- }
2808
- // Validate exposure up front (fail-closed matrix), before any run/spend. A live CUA watch is the
2809
- // one surface that serves runtime E2B stream URLs, so it MUST sit behind edge auth.
2810
- const exposeValidation = validateExposure("watch", exposureRequestFromOptions(args.options), {
2811
- dryRun,
2812
- detach: args.options.detach === true,
2813
- json: wantsMachine
2814
- });
2815
- if (!exposeValidation.ok) {
2816
- failCua(exposeValidation.error.code, exposeValidation.error.message);
2817
- return;
2818
- }
2819
- const plan = exposeValidation.plan;
2820
- const exposeRequested = plan.exposed;
2821
- let server = null;
2822
- let attachedObserver = null;
2823
- let tunnel;
2824
- let exposeWarnings = [];
2825
- let exposePublicTarget;
2826
- let outcome;
2827
- try {
2828
- outcome = await runLab(args.config, {
2829
- automaticAnalysis: cliAutomaticAnalysisHooks(args.io),
2830
- cwd: args.options.cwd,
2831
- ...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
2832
- // Watch mode opens the served Observer below (or prints the phone target under --expose)
2833
- // instead of the static render — preserved byte-for-byte from the pre-0.18 open policy so a
2834
- // non-follow watch (dry-run/--json) does not double-open. Run mode keeps the static open.
2835
- open: args.mode === "watch" ? false : shouldOpen,
2836
- count,
2837
- dryRun,
2838
- ...(wantsFollow
2839
- ? {
2840
- // Fires INSIDE runLab, before the actor loop and before sandbox creation, so a
2841
- // tunnel-auth failure aborts before any spend and leaves no orphaned sandbox.
2842
- onObserverReady: async (observer) => {
2843
- attachedObserver = observer;
2844
- if (!server) {
2845
- server = await serveObserver(observer, {
2846
- open: shouldOpen && !exposeRequested,
2847
- port,
2848
- exposed: exposeRequested
2849
- });
2850
- }
2851
- if (exposeRequested) {
2852
- const activeServer = server;
2853
- const exposeResult = await startExposedObserver(activeServer, plan);
2854
- if (exposeResult.tunnel) {
2855
- tunnel = exposeResult.tunnel;
2856
- }
2857
- exposeWarnings = exposeResult.warnings;
2858
- const phoneTarget = exposeResult.publicUrl ?? activeServer.url;
2859
- exposePublicTarget = phoneTarget;
2860
- args.io.writeOut(`watch: exposed live desktop at ${phoneTarget} (edge-authed; open it on your phone)\n`);
2861
- for (const warning of exposeResult.warnings) {
2862
- args.io.writeErr(`warning: ${warning}\n`);
2863
- }
2864
- }
2865
- }
2866
- }
2867
- : {}),
2868
- ...(args.options.runId === undefined ? {} : { runId: args.options.runId }),
2869
- ...(args.scorer ? { cuaHooks: browserScorerHooks(args.scorer), scorerProvenance: args.scorer.provenance } : {}),
2870
- ...(args.options.rerunFailedFrom === undefined
2871
- ? {}
2872
- : {
2873
- rerun: {
2874
- sourceRunId: args.options.rerunFailedFrom,
2875
- ...(laneIds.length === 0 ? {} : { laneIds })
2876
- }
2877
- })
2878
- });
2879
- }
2880
- catch (error) {
2881
- // Tear down the loopback server and any tunnel started inside onObserverReady before rethrowing
2882
- // (or surfacing a structured tunnel-startup failure). The sandbox is created AFTER
2883
- // onObserverReady returns, so a tunnel failure here cannot orphan one.
2884
- const earlyServer = server;
2885
- await earlyServer?.close().catch((cleanupError) => {
2886
- args.io.writeErr(`watch cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}\n`);
2887
- });
2888
- server = null;
2889
- if (tunnel) {
2890
- await tunnel.close().catch(() => undefined);
2891
- tunnel = undefined;
2892
- }
2893
- if (error instanceof ServeTunnelError) {
2894
- failCua(error.code, error.message);
2895
- return;
2896
- }
2897
- throw error;
2898
- }
2899
- if (outcome.backend !== "cua") {
2900
- throw new Error(`Expected cua backend, got ${outcome.backend}.`);
2901
- }
2902
- const result = outcome.result;
2903
- // Serving is NOT gated on result.ok: a timed_out/failed run still comes up so the operator can
2904
- // inspect its evidence live (and, under --expose, from a phone). budget_reached now makes a
2905
- // productive open-ended watch result.ok true.
2906
- let output = result;
2907
- if (server && attachedObserver) {
2908
- const activeServer = server;
2909
- const attachedResult = result.observer?.ok ? result.observer : attachedObserver;
2910
- output = {
2911
- ...result,
2912
- observer: withObserverServer(attachedResult, activeServer),
2913
- warnings: [
2914
- ...result.warnings,
2915
- "Live CUA server is polling observer-data.json with no-store caching.",
2916
- ...(exposeRequested
2917
- ? [
2918
- `Exposed live desktop stream URLs to an edge-authenticated remote viewer${tunnel ? ` via ${tunnel.url.replace(/\/$/, "")}` : ""}.`,
2919
- `this live run's raw, unverified evidence (screenshots, events) is viewable by anyone who clears the edge auth at ${exposePublicTarget ?? activeServer.url}; only the run being watched is served, not your other runs`
2920
- ]
2921
- : []),
2922
- ...exposeWarnings,
2923
- ...(activeServer.warning ? [activeServer.warning] : [])
2924
- ]
2925
- };
2926
- }
2927
- writeResult(args.command, args.io, output, formatCuaLabHuman);
2928
- args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
2929
- if (server && (result.observer?.ok || attachedObserver)) {
2930
- const activeServer = server;
2931
- const followResult = output.observer?.ok ? output.observer : withObserverServer(attachedObserver, activeServer);
2932
- await followObserver(args.io, followResult, activeServer, {
2933
- onStop: async () => {
2934
- if (tunnel) {
2935
- await tunnel.close();
2936
- return ["closed ngrok tunnel"];
2937
- }
2938
- return [];
2939
- }
2940
- });
2941
- }
2942
- else if (args.mode === "watch" && result.ok && !wantsMachine) {
2943
- // Non-follow / dry-run watch keeps today's fallback: render the finished bundle and follow it.
2944
- await renderAndMaybeFollowObserver({
2945
- command: args.command,
2946
- cwd: args.options.cwd,
2947
- io: args.io,
2948
- port: args.options.port ?? "0",
2949
- runInput: result.runId,
2950
- ...(args.options.detach === undefined ? {} : { detach: args.options.detach }),
2951
- ...(shouldOpen === undefined ? {} : { open: shouldOpen })
2952
- });
2953
- }
2954
- }
2955
- // Mirror of runCuaBackend: open semantics from defaults.open/--no-open/watch-mode, writeResult
2956
- // with the scripted human formatter, exit code result.ok ? 0 : 2, watch-mode Observer follow.
2957
- async function runScriptedBackend(args) {
2958
- const wantsMachine = wantsJson(args.command);
2959
- const shouldOpen = resolveBackendShouldOpen({
2960
- optionOpen: args.options.open,
2961
- defaultsOpen: args.config.defaults?.open,
2962
- mode: args.mode,
2963
- wantsMachine
2964
- });
2965
- const outcome = await runLab(args.config, {
2966
- automaticAnalysis: cliAutomaticAnalysisHooks(args.io),
2967
- cwd: args.options.cwd,
2968
- ...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
2969
- // Watch mode opens the served Observer below instead of the static render.
2970
- open: args.mode === "watch" ? false : shouldOpen,
2971
- ...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
2972
- ...(args.options.runId === undefined ? {} : { runId: args.options.runId })
2973
- });
2974
- if (outcome.backend !== "scripted") {
2975
- throw new Error(`Expected scripted backend, got ${outcome.backend}.`);
2976
- }
2977
- const result = outcome.result;
2978
- writeResult(args.command, args.io, result, formatScriptedLabHuman);
2979
- args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
2980
- // Watch mode serves the freshly rendered Observer (and opens it unless told not to).
2981
- if (args.mode === "watch" && result.ok && !wantsMachine) {
2982
- await renderAndMaybeFollowObserver({
2983
- command: args.command,
2984
- cwd: args.options.cwd,
2985
- io: args.io,
2986
- port: args.options.port ?? "0",
2987
- runInput: result.runId,
2988
- ...(args.options.detach === undefined ? {} : { detach: args.options.detach }),
2989
- ...(shouldOpen === undefined ? {} : { open: shouldOpen })
2990
- });
2991
- }
2992
- }
2993
- // Mirror of runCuaBackend/runScriptedBackend: open semantics from defaults.open/--no-open/watch,
2994
- // writeResult with the terminal human formatter, exit code result.ok ? 0 : 2, watch-mode follow.
2995
- async function runTerminalBackend(args) {
2996
- const wantsMachine = wantsJson(args.command);
2997
- const shouldOpen = resolveBackendShouldOpen({
2998
- optionOpen: args.options.open,
2999
- defaultsOpen: args.config.defaults?.open,
3000
- mode: args.mode,
3001
- wantsMachine
3002
- });
3003
- const outcome = await runLab(args.config, {
3004
- automaticAnalysis: cliAutomaticAnalysisHooks(args.io),
3005
- cwd: args.options.cwd,
3006
- ...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
3007
- open: args.mode === "watch" ? false : shouldOpen,
3008
- ...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
3009
- ...(args.options.runId === undefined ? {} : { runId: args.options.runId }),
3010
- ...(args.scorer ? { terminalHooks: terminalScorerHooks(args.scorer), scorerProvenance: args.scorer.provenance } : {})
3011
- });
3012
- if (outcome.backend !== "terminal") {
3013
- throw new Error(`Expected terminal backend, got ${outcome.backend}.`);
3014
- }
3015
- const result = outcome.result;
3016
- writeResult(args.command, args.io, result, formatTerminalLabHuman);
3017
- args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
3018
- if (args.mode === "watch" && result.ok && !wantsMachine) {
3019
- await renderAndMaybeFollowObserver({
3020
- command: args.command,
3021
- cwd: args.options.cwd,
3022
- io: args.io,
3023
- port: args.options.port ?? "0",
3024
- runInput: result.runId,
3025
- ...(args.options.detach === undefined ? {} : { detach: args.options.detach }),
3026
- ...(shouldOpen === undefined ? {} : { open: shouldOpen })
3027
- });
3028
- }
3029
- }
3030
- async function runSharedWorldBackend(args) {
3031
- const wantsMachine = wantsJson(args.command);
3032
- const shouldOpen = resolveBackendShouldOpen({
3033
- optionOpen: args.options.open,
3034
- defaultsOpen: args.config.defaults?.open,
3035
- mode: args.mode,
3036
- wantsMachine
3037
- });
3038
- const outcome = await runLab(args.config, {
3039
- automaticAnalysis: cliAutomaticAnalysisHooks(args.io),
3040
- cwd: args.options.cwd,
3041
- ...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
3042
- open: args.mode === "watch" ? false : shouldOpen,
3043
- ...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
3044
- ...(args.options.runId === undefined ? {} : { runId: args.options.runId }),
3045
- ...(args.scorer ? { sharedWorldHooks: browserScorerHooks(args.scorer), scorerProvenance: args.scorer.provenance } : {})
3046
- });
3047
- if (outcome.backend !== "shared-world") {
3048
- throw new Error(`Expected shared-world backend, got ${outcome.backend}.`);
3049
- }
3050
- const result = outcome.result;
3051
- writeResult(args.command, args.io, result, formatSharedWorldLabHuman);
3052
- args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
3053
- if (args.mode === "watch" && result.ok && !wantsMachine) {
3054
- await renderAndMaybeFollowObserver({
3055
- command: args.command,
3056
- cwd: args.options.cwd,
3057
- io: args.io,
3058
- port: args.options.port ?? "0",
3059
- runInput: result.runId,
3060
- ...(args.options.detach === undefined ? {} : { detach: args.options.detach }),
3061
- ...(shouldOpen === undefined ? {} : { open: shouldOpen })
3062
- });
3063
- }
3064
- }
3065
- function formatSharedWorldLabHuman(result) {
3066
- return [
3067
- `humanish lab shared-world ${result.ok ? (result.dryRun ? "dry-run" : "live") : "failed"}`,
3068
- ...(result.error ? [`${result.error.code}: ${result.error.message}`] : []),
3069
- `run: ${result.runId}`,
3070
- `lab: ${result.labId}`,
3071
- `actor: ${result.actor}`,
3072
- `topology: ${result.topology} (${result.roleCount} role${result.roleCount === 1 ? "" : "s"})`,
3073
- `sequence: ${result.sequence.join(" -> ") || "(none)"}`,
3074
- ...(result.subject?.commit ? [`plane: ${result.subject.repo}@${result.subject.commit.slice(0, 12)}`] : []),
3075
- ...result.roles.map((role) => `role ${role.id} (${role.persona}): ${role.status}${role.session ? ` (${role.session.completionReason})` : ""}${role.skippedReason ? ` · ${role.skippedReason}` : ""}`),
3076
- ...(result.sandbox ? [`sandbox: ${result.sandbox.sandboxId} killed=${result.sandbox.killed ? "yes" : "no"}`] : []),
3077
- ...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
3078
- ...(result.observer?.opened === undefined ? [] : [`opened: ${result.observer.opened ? "yes" : "no"}`]),
3079
- ...result.warnings.map((warning) => `warning: ${warning}`)
3080
- ].join("\n") + "\n";
3081
- }
3082
- async function runConcurrentSharedWorldBackend(args) {
3083
- const wantsMachine = wantsJson(args.command);
3084
- const dryRun = resolveLabDryRun(args.config, args.options.dryRun, true) ?? true;
3085
- const shouldOpen = resolveBackendShouldOpen({
3086
- optionOpen: args.options.open,
3087
- defaultsOpen: args.config.defaults?.open,
3088
- mode: args.mode,
3089
- wantsMachine
3090
- });
3091
- const wantsFollow = args.mode === "watch" && !wantsMachine && args.options.detach !== true && dryRun !== true;
3092
- const port = parseObserverPort(args.options.port ?? "0");
3093
- if (wantsFollow && port === null) {
3094
- const result = {
3095
- schema: "humanish.concurrent-shared-world-lab-result.v1",
3096
- ok: false,
3097
- cwd: args.options.cwd,
3098
- labId: args.config.id,
3099
- actor: args.config.actors[0]?.type ?? "",
3100
- topology: "shared-world",
3101
- topologyMode: "concurrent",
3102
- roleCount: args.config.actors[0]?.lanes?.length ?? 0,
3103
- concurrency: args.config.execution?.concurrency ?? 1,
3104
- dryRun,
3105
- runId: args.options.runId ?? "not-created",
3106
- roles: [],
3107
- warnings: [],
3108
- error: {
3109
- code: "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_FAILED",
3110
- message: "--port must be an integer between 0 and 65535."
3111
- }
3112
- };
3113
- writeResult(args.command, args.io, result, formatConcurrentSharedWorldLabHuman);
3114
- args.io.setExitCode(2);
3115
- return;
3116
- }
3117
- let server = null;
3118
- let attachedObserver = null;
3119
- let outcome;
3120
- try {
3121
- outcome = await runLab(args.config, {
3122
- automaticAnalysis: cliAutomaticAnalysisHooks(args.io),
3123
- cwd: args.options.cwd,
3124
- ...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
3125
- open: wantsFollow ? false : shouldOpen,
3126
- dryRun,
3127
- ...(wantsFollow
3128
- ? {
3129
- onObserverReady: async (observer) => {
3130
- attachedObserver = observer;
3131
- if (!server) {
3132
- server = await serveObserver(observer, { open: shouldOpen, port: port ?? 0 });
3133
- }
3134
- }
3135
- }
3136
- : {}),
3137
- ...(args.options.runId === undefined ? {} : { runId: args.options.runId }),
3138
- ...(args.scorer ? { sharedWorldHooks: browserScorerHooks(args.scorer), scorerProvenance: args.scorer.provenance } : {})
3139
- });
3140
- }
3141
- catch (error) {
3142
- const earlyServer = server;
3143
- await earlyServer?.close().catch((cleanupError) => {
3144
- args.io.writeErr(`watch cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}\n`);
3145
- });
3146
- server = null;
3147
- throw error;
3148
- }
3149
- if (outcome.backend !== "concurrent-shared-world") {
3150
- throw new Error(`Expected concurrent-shared-world backend, got ${outcome.backend}.`);
3151
- }
3152
- const result = outcome.result;
3153
- let output = result;
3154
- if (server && attachedObserver) {
3155
- const activeServer = server;
3156
- output = {
3157
- ...result,
3158
- observer: result.observer?.ok
3159
- ? withObserverServer(result.observer, activeServer)
3160
- : withObserverServer(attachedObserver, activeServer),
3161
- warnings: [
3162
- ...result.warnings,
3163
- "Live concurrent shared-world server is polling observer-data.json with no-store caching.",
3164
- ...(activeServer.warning ? [activeServer.warning] : [])
3165
- ]
3166
- };
3167
- }
3168
- writeResult(args.command, args.io, output, formatConcurrentSharedWorldLabHuman);
3169
- args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
3170
- if (server && output.observer?.ok) {
3171
- await followObserver(args.io, output.observer, server);
3172
- }
3173
- else if (args.mode === "watch" && result.ok && !wantsMachine) {
3174
- await renderAndMaybeFollowObserver({
3175
- command: args.command,
3176
- cwd: args.options.cwd,
3177
- io: args.io,
3178
- port: args.options.port ?? "0",
3179
- runInput: result.runId,
3180
- ...(args.options.detach === undefined ? {} : { detach: args.options.detach }),
3181
- ...(shouldOpen === undefined ? {} : { open: shouldOpen })
3182
- });
3183
- }
3184
- }
3185
- function formatConcurrentSharedWorldLabHuman(result) {
3186
- return [
3187
- `humanish lab concurrent-shared-world ${result.ok ? (result.dryRun ? "dry-run" : "live") : "failed"}`,
3188
- ...(result.error ? [`${result.error.code}: ${result.error.message}`] : []),
3189
- `run: ${result.runId}`,
3190
- `lab: ${result.labId}`,
3191
- `actor: ${result.actor}`,
3192
- `topology: ${result.topology}/${result.topologyMode} (${result.roleCount} persona${result.roleCount === 1 ? "" : "s"}, concurrency ${result.concurrency})`,
3193
- ...(result.host ? [`host: ${result.host}`] : []),
3194
- ...(result.subject?.commit ? [`plane: ${result.subject.repo}@${result.subject.commit.slice(0, 12)}`] : []),
3195
- ...(result.overlapProven === undefined ? [] : [`overlap: ${result.overlapProven ? "proven" : "not observed"} (this run only; no scale or adoption claim)`]),
3196
- ...result.roles.map((role) => `persona ${role.id} (${role.persona}): ${role.status}${role.session ? ` (${role.session.completionReason})` : ""} ${role.ok ? "ok" : "not-ok"}`),
3197
- ...(result.subjectSandbox ? [`subject sandbox: ${result.subjectSandbox.sandboxId} killed=${result.subjectSandbox.killed ? "yes" : "no"}`] : []),
3198
- ...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
3199
- ...(result.observer?.opened === undefined ? [] : [`opened: ${result.observer.opened ? "yes" : "no"}`]),
3200
- ...result.warnings.map((warning) => `warning: ${warning}`)
3201
- ].join("\n") + "\n";
3202
- }
3203
- function formatTerminalLabHuman(result) {
3204
- return [
3205
- `humanish lab terminal ${result.ok ? (result.dryRun ? "dry-run" : "live") : "failed"}`,
3206
- ...(result.error ? [`${result.error.code}: ${result.error.message}`] : []),
3207
- `run: ${result.runId}`,
3208
- `lab: ${result.labId}`,
3209
- `actor: ${result.actor}`,
3210
- `product: ${result.product}`,
3211
- ...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
3212
- ...(result.observer?.opened === undefined ? [] : [`opened: ${result.observer.opened ? "yes" : "no"}`]),
3213
- ...result.warnings.map((warning) => `warning: ${warning}`)
3214
- ].join("\n") + "\n";
3215
- }
3216
- function formatScriptedLabHuman(result) {
3217
- return [
3218
- `humanish lab scripted ${result.ok ? (result.dryRun ? "dry-run" : "live") : "failed"}`,
3219
- ...(result.error ? [`${result.error.code}: ${result.error.message}`] : []),
3220
- `run: ${result.runId}`,
3221
- `lab: ${result.labId}`,
3222
- `actor: ${result.actor}`,
3223
- `subject: ${result.appUrl}`,
3224
- ...(result.scenario
3225
- ? [`scenario: ${result.scenario.id} @ ${result.scenario.sourceDigest.slice(0, 12)} (${result.scenario.source}, ${result.scenario.steps} step${result.scenario.steps === 1 ? "" : "s"})`]
3226
- : []),
3227
- ...result.sessions.map((session) => `session ${session.surface}: ${session.status} (${session.completionReason}) · ${session.reason} [${session.screenshots} screenshot${session.screenshots === 1 ? "" : "s"}]`),
3228
- ...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
3229
- ...(result.observer?.opened === undefined ? [] : [`opened: ${result.observer.opened ? "yes" : "no"}`]),
3230
- ...result.warnings.map((warning) => `warning: ${warning}`)
3231
- ].join("\n") + "\n";
3232
- }
3233
- export function formatCuaLabHuman(result) {
3234
- return [
3235
- `humanish lab cua ${result.ok ? (result.dryRun ? "dry-run" : "live") : "failed"}`,
3236
- ...(result.error ? [`${result.error.code}: ${result.error.message}`] : []),
3237
- `run: ${result.runId}`,
3238
- `lab: ${result.labId}`,
3239
- `actor: ${result.actor}`,
3240
- `subject: ${result.appUrl}`,
3241
- ...(result.subject?.source === "clone"
3242
- ? [`repo: ${result.subject.repo}${result.subject.commit ? `@${result.subject.commit.slice(0, 12)}` : ""}${result.subject.envNames && result.subject.envNames.length > 0 ? ` env=[${result.subject.envNames.join(", ")}]` : ""}`]
3243
- : []),
3244
- ...(result.rerun
3245
- ? [`rerun: ${result.rerun.selectedLaneIds.join(", ")} from ${result.rerun.sourceRunId}`]
3246
- : []),
3247
- ...(result.diagnostics ? [`diagnostic: ${formatCuaDiagnostics(result.diagnostics)}`] : []),
3248
- ...((result.lanes?.length ?? 0) > 1
3249
- ? result.lanes.map(lane => `lane ${lane.id}: ${lane.status}${lane.session ? ` (${lane.session.completionReason})` : ""}${lane.diagnostics ? ` · ${formatCuaDiagnostics(lane.diagnostics)}` : ""}${lane.session ? ` · ${lane.session.reason}` : ""}`)
3250
- : []),
3251
- ...(result.session && (result.lanes?.length ?? 0) <= 1
3252
- ? [`session: ${result.session.status} (${result.session.completionReason})${result.session.stopCause ? ` · ${formatCuaStopCause(result.session.stopCause)}` : ""} · ${result.session.reason}`,
3253
- `screenshots: ${result.session.screenshots}`]
3254
- : []),
3255
- ...(result.sandbox
3256
- ? [`sandbox: ${result.sandbox.sandboxId} stream=${result.sandbox.streamUrlPresent ? "connected" : "missing"} killed=${result.sandbox.killed ? "yes" : "no"}`]
3257
- : []),
3258
- ...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
3259
- ...(result.observer?.opened === undefined ? [] : [`opened: ${result.observer.opened ? "yes" : "no"}`]),
3260
- ...result.warnings.map((warning) => `warning: ${warning}`)
3261
- ].join("\n") + "\n";
3262
- }
3263
- async function runSmokeBackend(args) {
3264
- const fanout = args.config.subject.clone?.fanout ?? args.config.subject.repos?.length ?? DEFAULT_OSS_REPOS.length;
3265
- const limit = parseLabCount(args.options.limit ?? args.options.sims, fanout);
3266
- if (limit === null) {
3267
- const result = {
3268
- schema: "humanish.oss-lab-result.v1",
3269
- ok: false,
3270
- cleanup: { kept: Boolean(args.options.keep), sandboxRemoved: false },
3271
- completedAt: new Date().toISOString(),
3272
- cwd: args.options.cwd,
3273
- error: {
3274
- code: "HUMANISH_INVALID_OSS_LIMIT",
3275
- message: "--limit must be a positive integer."
3276
- },
3277
- repos: [],
3278
- runId: args.options.runId ?? "not-created",
3279
- sandboxPath: ".humanish/tmp/oss-lab/not-created",
3280
- startedAt: new Date().toISOString(),
3281
- warnings: []
3282
- };
3283
- writeResult(args.command, args.io, result, formatOssLabHuman);
3284
- args.io.setExitCode(2);
3285
- return;
3286
- }
3287
- const repos = labReposOverride(args.options);
3288
- const outcome = await runLab(args.config, {
3289
- cwd: args.options.cwd,
3290
- ...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
3291
- count: limit,
3292
- ...(repos === undefined ? {} : { repos }),
3293
- ...(args.options.keep === undefined ? {} : { keep: args.options.keep }),
3294
- ...(args.options.runId === undefined ? {} : { runId: args.options.runId })
3295
- });
3296
- if (outcome.backend !== "smoke") {
3297
- throw new Error(`Expected smoke backend, got ${outcome.backend}.`);
3298
- }
3299
- const result = outcome.result;
3300
- writeResult(args.command, args.io, result, formatOssLabHuman);
3301
- args.io.setExitCode(result.ok ? 0 : 2);
3302
- }
3303
- async function runMetaBackend(args) {
3304
- const metaCountDefault = args.config.subject.clone?.fanout ?? args.config.subject.repos?.length ?? DEFAULT_OSS_REPOS.length;
3305
- const count = parseLabCount(args.options.count ?? args.options.sims, metaCountDefault);
3306
- const repos = labReposOverride(args.options);
3307
- const port = parseObserverPort(args.options.port ?? "0");
3308
- if (port === null) {
3309
- const result = {
3310
- schema: "humanish.oss-meta-lab-result.v1",
3311
- ok: false,
3312
- assignments: [],
3313
- cwd: args.options.cwd,
3314
- dryRun: args.options.dryRun === true,
3315
- error: {
3316
- code: "HUMANISH_META_RUN_FAILED",
3317
- message: "--port must be an integer between 0 and 65535."
3318
- },
3319
- liveRequested: args.options.dryRun !== true,
3320
- repos: repos ?? args.config.subject.repos ?? [],
3321
- sandboxes: [],
3322
- warnings: []
3323
- };
3324
- writeResult(args.command, args.io, result, formatOssMetaLabHuman);
3325
- args.io.setExitCode(2);
3326
- return;
3327
- }
3328
- const wantsMachine = wantsJson(args.command);
3329
- const dryRun = resolveLabDryRun(args.config, args.options.dryRun, undefined);
3330
- const shouldOpen = resolveBackendShouldOpen({
3331
- optionOpen: args.options.open,
3332
- defaultsOpen: args.config.defaults?.open,
3333
- mode: args.mode,
3334
- wantsMachine
3335
- });
3336
- const wantsFollow = args.mode === "watch" && !wantsMachine && args.options.detach !== true && dryRun !== true;
3337
- const codexAppServer = args.options.codexAppServer ?? args.config.execution?.desktop?.codexAppServer;
3338
- const repoOverrideRequested = (args.options.repo?.length ?? 0) > 0 || args.options.repos !== undefined;
3339
- const defaultRedactRepos = repoOverrideRequested ? true : args.config.policies?.redactRepos;
3340
- const redactRepoNames = args.options.redactRepos ?? defaultRedactRepos;
3341
- let server = null;
3342
- let liveRefresh = null;
3343
- let result;
3344
- try {
3345
- const outcome = await runLab(args.config, {
3346
- cwd: args.options.cwd,
3347
- ...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
3348
- ...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
3349
- ...(wantsFollow ? { completionTimeoutMs: 0 } : {}),
3350
- ...(codexAppServer === undefined ? {} : { codexAppServer }),
3351
- ...(wantsFollow
3352
- ? {
3353
- onObserverReady: async (observer) => {
3354
- if (!server) {
3355
- server = await serveObserver(observer, { open: shouldOpen, port });
3356
- }
3357
- }
3358
- }
3359
- : {}),
3360
- open: wantsFollow ? false : shouldOpen,
3361
- ...(dryRun === undefined ? {} : { dryRun }),
3362
- ...(redactRepoNames === undefined ? {} : { redactRepos: redactRepoNames }),
3363
- ...(repos === undefined ? {} : { repos }),
3364
- count: count === null ? Number.NaN : count,
3365
- ...(args.options.runId === undefined ? {} : { runId: args.options.runId })
3366
- });
3367
- if (outcome.backend !== "meta") {
3368
- throw new Error(`Expected meta backend, got ${outcome.backend}.`);
3369
- }
3370
- result = outcome.result;
3371
- }
3372
- catch (error) {
3373
- const earlyServer = server;
3374
- await earlyServer?.close().catch((cleanupError) => {
3375
- args.io.writeErr(`watch cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}\n`);
3376
- });
3377
- server = null;
3378
- throw error;
3379
- }
3380
- let output = result;
3381
- if (server && shouldServeOssMetaLabObserver(result, { wantsFollow: true })) {
3382
- liveRefresh = startOssMetaLabLiveRefresh(result);
3383
- output = withOssMetaLabServer(result, server);
3384
- }
3385
- else if (shouldServeOssMetaLabObserver(result, { wantsFollow })) {
3386
- server = await serveObserver(result.observer, { open: shouldOpen, port });
3387
- liveRefresh = startOssMetaLabLiveRefresh(result);
3388
- output = withOssMetaLabServer(result, server);
3389
- }
3390
- else {
3391
- const earlyServer = server;
3392
- await earlyServer?.close().catch((error) => {
3393
- args.io.writeErr(`watch cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`);
3394
- });
3395
- server = null;
3396
- }
3397
- const exitCode = exitCodeForOssMetaLab(output);
3398
- writeResult(args.command, args.io, output, formatOssMetaLabHuman);
3399
- args.io.setExitCode(exitCode);
3400
- if (shouldForceExitAfterOssMetaLab(output, { detach: args.options.detach === true, wantsMachine })) {
3401
- setTimeout(() => process.exit(exitCode), 50);
3402
- }
3403
- if (server && output.observer?.ok) {
3404
- await followObserver(args.io, output.observer, server, output.liveRequested
3405
- ? {
3406
- onStop: async () => {
3407
- const cleanup = liveRefresh
3408
- ? await liveRefresh.cleanup()
3409
- : await cleanupOssMetaLabSandboxes(output);
3410
- return [
3411
- `E2B sandbox cleanup killed ${cleanup.killed}, skipped ${cleanup.skipped}.`,
3412
- ...cleanup.errors.map((error) => `E2B sandbox cleanup error: ${error}`)
3413
- ];
3414
- }
3415
- }
3416
- : {});
3417
- }
3418
- }
3419
- async function renderAndMaybeFollowObserver(args) {
3420
- const port = parseObserverPort(args.port);
3421
- if (port === null) {
3422
- const result = {
3423
- schema: "humanish.run-result.v1",
3424
- ok: false,
3425
- cwd: args.cwd,
3426
- warnings: [],
3427
- error: {
3428
- code: "HUMANISH_INVALID_PORT",
3429
- message: "--port must be an integer between 0 and 65535."
3430
- }
3431
- };
3432
- writeResult(args.command, args.io, result, formatRunHuman);
3433
- args.io.setExitCode(2);
3434
- return;
3435
- }
3436
- const wantsMachine = wantsJson(args.command);
3437
- const shouldOpen = args.open === false ? false : args.open === true ? true : !wantsMachine && process.stdout.isTTY === true;
3438
- const wantsFollow = !wantsMachine && args.detach !== true;
3439
- const rendered = await renderObserver(args.cwd, args.runInput, { open: wantsFollow ? false : shouldOpen });
3440
- let server = null;
3441
- let result = rendered;
3442
- if (rendered.ok && wantsFollow) {
3443
- server = await serveObserver(rendered, { open: shouldOpen, port });
3444
- result = withObserverServer(rendered, server);
3445
- }
3446
- writeResult(args.command, args.io, result, formatObserverHuman);
3447
- args.io.setExitCode(result.ok ? 0 : 2);
3448
- if (result.ok && server) {
3449
- await followObserver(args.io, result, server);
3450
- }
3451
- }
3452
- async function applyEnvFileOption(args) {
3453
- const env = args.env ?? process.env;
3454
- if (args.envFile) {
3455
- const stagedEnv = { ...env };
3456
- const result = await loadEnvFile(args.cwd, args.envFile, stagedEnv);
3457
- if (!result.ok) {
3458
- writeResult(args.command, args.io, result, formatEnvFileHuman);
3459
- args.io.setExitCode(2);
3460
- return false;
3461
- }
3462
- for (const name of result.loaded)
3463
- env[name] = stagedEnv[name];
3464
- }
3465
- // Provider-key discovery (#436): fill still-missing keys from the documented project
3466
- // overlay, the owning vendors' native stores, and the humanish user store — fill-only
3467
- // (an explicit --env-file or process env always wins), each fill announced by name and
3468
- // source on stderr, never by value. HUMANISH_STRICT_KEYS=1 restores env-only behavior.
3469
- try {
3470
- const discovered = await keyDiscoveryFn({
3471
- cwd: args.cwd,
3472
- env,
3473
- announce: (line) => args.io.writeErr(`${line}\n`)
3474
- });
3475
- args.onDiscovered?.(discovered.map(fill => fill.name));
3476
- }
3477
- catch {
3478
- // Discovery must never break a command; a rung that fails to read is a miss, not an error.
3479
- }
3480
- return true;
3481
- }
3482
- function labReposOverride(options) {
3483
- const override = [
3484
- ...(options.repo ?? []),
3485
- ...(options.repos ? [options.repos] : [])
3486
- ];
3487
- return override.length > 0 ? override : undefined;
3488
- }
3489
- function parseLabCount(value, fallback) {
3490
- return value === undefined ? fallback : parsePositiveInteger(value);
3491
- }
3492
- function parseLaneIds(value) {
3493
- if (value === undefined) {
3494
- return [];
3495
- }
3496
- const seen = new Set();
3497
- const laneIds = [];
3498
- for (const raw of value.split(",")) {
3499
- const laneId = raw.trim();
3500
- if (!laneId || seen.has(laneId))
3501
- continue;
3502
- seen.add(laneId);
3503
- laneIds.push(laneId);
3504
- }
3505
- return laneIds;
3506
- }
3507
- function withObserverServer(rendered, server) {
3508
- return {
3509
- ...rendered,
3510
- observerUrl: server.url,
3511
- serverUrl: server.url,
3512
- opened: server.opened,
3513
- ...(server.openCommand ? { openCommand: server.openCommand } : {}),
3514
- warnings: [
3515
- ...rendered.warnings,
3516
- "Live observer server is polling observer-data.json with no-store caching.",
3517
- ...(server.warning ? [server.warning] : [])
3518
- ]
3519
- };
3520
- }
3521
- function withOssMetaLabServer(result, server) {
3522
- return {
3523
- ...result,
3524
- observer: {
3525
- ...result.observer,
3526
- observerUrl: server.url,
3527
- serverUrl: server.url,
3528
- opened: server.opened,
3529
- ...(server.openCommand ? { openCommand: server.openCommand } : {}),
3530
- warnings: [
3531
- ...result.observer.warnings,
3532
- "Live OSS meta-lab server is polling observer-data.json with no-store caching.",
3533
- ...(server.warning ? [server.warning] : [])
3534
- ]
3535
- },
3536
- warnings: [
3537
- ...result.warnings,
3538
- "Live OSS meta-lab server is polling observer-data.json with no-store caching.",
3539
- ...(server.warning ? [server.warning] : [])
3540
- ]
3541
- };
3542
- }
3543
- // Exported so tests/program.test.ts can drive the command-boundary catch-all's
3544
- // post-write guard (invocationEnvelopeAlreadyWritten above) through the same
3545
- // funnel every real command uses, without duplicating its stdout-vs-formatHuman
3546
- // branching. Not re-exported from src/index.ts; this stays an internal seam.
3547
- export function writeResult(command, io, result, formatHuman) {
3548
- const output = automaticAnalysisEnvelope(result);
3549
- if (wantsJson(command)) {
3550
- io.writeOut(`${JSON.stringify(output, null, 2)}\n`);
3551
- }
3552
- else {
3553
- io.writeOut(formatHuman(output));
3554
- if (output !== null && typeof output === "object" && "automaticAnalysis" in output) {
3555
- const analysis = output.automaticAnalysis;
3556
- if (analysis)
3557
- io.writeOut(`analysis: ${analysis.state}${analysis.reason ? ` (${analysis.reason})` : ""}\n`);
3558
- }
3559
- }
3560
- markInvocationEnvelopeWritten(command);
3561
- // Every backend's result passes through here, so this is where a study's facts get read for
3562
- // telemetry — not in each backend, which is how they went unreported for two releases.
3563
- noteStudyFacts(command, deriveStudyFacts(result));
3564
- }
3565
- const studyFactsByCommand = new WeakMap();
3566
- function noteStudyFacts(command, facts) {
3567
- if (Object.keys(facts).length === 0)
3568
- return;
3569
- studyFactsByCommand.set(command, { ...studyFactsByCommand.get(command), ...facts });
3570
- }
3571
- /** Exported for tests: the facts writeResult read off a command's result document. */
3572
- export function studyFactsFor(command) {
3573
- return { ...studyFactsByCommand.get(command) };
3574
- }
3575
- function formatDoctorHuman(result) {
3576
- return [
3577
- `humanish doctor ${result.ok ? "ok" : "needs setup"}`,
3578
- `cwd: ${result.cwd}`,
3579
- // "missing" is a VERDICT, and a row that never ran has none. A participant reading doctor on a
3580
- // fresh desktop got `- missing package.json: package.json is present and safe to read`, which
3581
- // contradicts itself in eleven words (labs/tui-self-study.yaml).
3582
- ...result.checks.map((check) => `- ${check.ok ? "ok" : check.checked === false ? "not checked" : "missing"} ${check.name}: ${check.message}`)
3583
- ].join("\n") + "\n";
3584
- }
3585
- function formatRunHuman(result) {
3586
- if (!result.ok) {
3587
- return `${result.error?.code}: ${result.error?.message}\n`;
3588
- }
3589
- return [
3590
- `humanish run ${result.mode}`,
3591
- `run: ${result.runId}`,
3592
- ...(result.simCount === undefined ? [] : [`sims: ${result.simCount}`]),
3593
- `bundle: ${result.bundlePath}`,
3594
- `review: ${result.reviewPath}`,
3595
- ...result.warnings.map((warning) => `warning: ${warning}`)
3596
- ].join("\n") + "\n";
3597
- }
3598
- function formatVerifyHuman(result) {
3599
- return [
3600
- `humanish verify ${result.ok ? "passed" : "failed"}`,
3601
- `run: ${result.run}`,
3602
- `share-safety: ${result.shareSafety.status}`,
3603
- ...result.shareSafety.reasons.map((reason) => `share-safety reason: ${reason.code}: ${reason.message}`),
3604
- ...result.checks.map((check) => `- ${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}`),
3605
- ...result.warnings.map((warning) => `warning: ${warning}`)
3606
- ].join("\n") + "\n";
3607
- }
3608
- function formatCleanupHuman(result) {
3609
- if (!result.ok && result.error) {
3610
- return `${result.error.code}: ${result.error.message}\n`;
3611
- }
3612
- return [
3613
- `humanish cleanup ${result.ok ? "passed" : "failed"}`,
3614
- `run: ${result.runId ?? result.run}`,
3615
- `resources: killed ${result.summary.killed}, already-clean ${result.summary.alreadyClean}, skipped ${result.summary.skipped}, failed ${result.summary.failed}`,
3616
- ...(result.cleanupPath ? [`cleanup: ${result.cleanupPath}`] : []),
3617
- ...result.warnings.map((warning) => `warning: ${warning}`)
3618
- ].join("\n") + "\n";
3619
- }
3620
- function formatRunsHuman(result) {
3621
- if (!result.ok) {
3622
- return `${result.error?.code}: ${result.error?.message}\n`;
3623
- }
3624
- if (result.runs.length === 0) {
3625
- return `No Humanish runs found in ${result.cwd}\n`;
3626
- }
3627
- return [
3628
- `latest: ${result.latest ?? "none"}`,
3629
- ...result.runs.map((run) => `- ${run.runId} ${run.mode ?? "unknown"} ${run.createdAt ?? "unknown"} ${run.path}`)
3630
- ].join("\n") + "\n";
3631
- }
3632
- function formatObserverHuman(result) {
3633
- if (!result.ok) {
3634
- return `${result.error?.code}: ${result.error?.message}\n`;
3635
- }
3636
- return [
3637
- "humanish observer rendered",
3638
- `run: ${result.run}`,
3639
- `observer: ${result.observerPath}`,
3640
- ...(result.observerUrl ? [`url: ${result.observerUrl}`] : []),
3641
- ...(result.opened === undefined ? [] : [`opened: ${result.opened ? "yes" : "no"}`]),
3642
- `bundle: ${result.bundlePath}`,
3643
- ...result.warnings.map((warning) => `warning: ${warning}`)
3644
- ].join("\n") + "\n";
3645
- }
3646
- function formatCodexAppServerUiHuman(result) {
3647
- if (!result.ok) {
3648
- return `${result.error?.code}: ${result.error?.message}\n`;
3649
- }
3650
- return [
3651
- "humanish codex app-server",
3652
- `url: ${result.url ?? "not-started"}`,
3653
- `status: ${result.status ?? "unknown"}`,
3654
- `state: ${result.stateFile ?? "none"}`,
3655
- `reason: ${result.reason}`
3656
- ].join("\n") + "\n";
3657
- }
3658
- function formatEnvFileHuman(result) {
3659
- if (!result.ok) {
3660
- return `${result.error?.code}: ${result.error?.message}\n`;
3661
- }
3662
- return [
3663
- "humanish env-file loaded",
3664
- `env-file: ${result.envFile}`,
3665
- `loaded: ${result.loaded.length ? result.loaded.join(", ") : "none"}`,
3666
- `skipped-existing: ${result.skipped.length ? result.skipped.join(", ") : "none"}`
3667
- ].join("\n") + "\n";
3668
- }
3669
- function formatLabListHuman(result) {
3670
- if (result.labs.length === 0) {
3671
- return [
3672
- `No Humanish labs found in ${result.cwd}`,
3673
- "Create one under humanish/labs/*.yaml, .humanish/labs/*.yaml, or pass a .yaml path.",
3674
- ...result.warnings.map((warning) => `warning: ${warning}`)
3675
- ].join("\n") + "\n";
3676
- }
3677
- return [
3678
- "humanish labs",
3679
- ...result.labs.map((lab) => `- ${lab.id} ${lab.source} ${lab.origin} ${lab.path}${lab.title ? ` (${lab.title})` : ""}`),
3680
- ...result.warnings.map((warning) => `warning: ${warning}`)
3681
- ].join("\n") + "\n";
3682
- }
3683
- function formatLabInspectHuman(result) {
3684
- if (!result.ok || !result.config) {
3685
- return `${result.error?.code}: ${result.error?.message}\n`;
3686
- }
3687
- const config = result.config;
3688
- return [
3689
- "humanish lab",
3690
- `id: ${config.id}`,
3691
- `subject: ${config.subject.source}`,
3692
- ...(config.execution?.target ? [`execution: ${config.execution.target}`] : []),
3693
- `actors: ${config.actors.map((actor) => actor.type).join(", ")}`,
3694
- ...(config.title ? [`title: ${config.title}`] : []),
3695
- ...(config.description ? [`description: ${config.description}`] : []),
3696
- ...(result.path ? [`path: ${result.path}`] : []),
3697
- ...(result.origin ? [`origin: ${result.origin}`] : []),
3698
- ...(config.subject.repos?.length ? [`repos: ${config.subject.repos.join(", ")}`] : []),
3699
- ...(result.personas ?? []).map(persona => `persona ${persona.id}: ${persona.brief ? `authored context (before runtime additions)\n${persona.brief.text}` : "unresolved; id only"}`),
3700
- ...result.warnings.map((warning) => `warning: ${warning}`)
3701
- ].join("\n") + "\n";
3702
- }
3703
- function formatLabPreflightHuman(result) {
3704
- const checkedTargets = result.targets.filter((target) => target.checked);
3705
- const reachableTargets = checkedTargets.filter((target) => target.reachable === true);
3706
- const blockedTargets = result.targets.filter((target) => target.status === "blocked");
3707
- return [
3708
- `humanish lab preflight ${result.ok ? "passed" : "failed"}`,
3709
- `lab: ${result.labId ?? result.lab}`,
3710
- ...(result.backend ? [`backend: ${result.backend}`] : []),
3711
- `reachability: ${result.reachability}`,
3712
- `targets: ${checkedTargets.length ? `${reachableTargets.length}/${checkedTargets.length} reachable` : `${result.targets.length} declared, not checked`}`,
3713
- ...(blockedTargets.length ? [`blocked-targets: ${blockedTargets.length}`] : []),
3714
- `spend: ${result.spend.e2bDesktop ? "one e2b desktop, no model calls" : "none"}`,
3715
- ...(result.analysis ? [formatAutomaticAnalysisBudget(result.analysis)] : []),
3716
- ...(result.sandbox.created
3717
- ? [`sandbox: created=yes killed=${result.sandbox.killed === true ? "yes" : "no"}`]
3718
- : []),
3719
- ...result.checks.map((check) => `- ${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}`),
3720
- ...(result.error ? [`error: ${result.error.code}: ${result.error.message}`] : []),
3721
- ...result.warnings.map((warning) => `warning: ${warning}`)
3722
- ].join("\n") + "\n";
3723
- }
3724
- function formatLabResolveFailureHuman(result) {
3725
- return [
3726
- `${result.error.code}: ${result.error.message}`,
3727
- ...result.warnings.map((warning) => `warning: ${warning}`)
3728
- ].join("\n") + "\n";
3729
- }
3730
- async function readCodexAppServerPrompt(options) {
3731
- if (options.prompt !== undefined && options.prompt.trim()) {
3732
- return options.prompt;
3733
- }
3734
- if (options.promptFile !== undefined && options.promptFile.trim()) {
3735
- const promptPath = resolve(options.cwd, options.promptFile);
3736
- const text = await readFile(promptPath, "utf8");
3737
- return text.trim() || null;
3738
- }
3739
- return null;
3740
- }
3741
- function codexAppServerUiError(cwd, code, message) {
3742
- return {
3743
- schema: "humanish.codex-app-server-ui-result.v1",
3744
- ok: false,
3745
- cwd: resolve(cwd),
3746
- reason: message,
3747
- error: { code, message }
3748
- };
3749
- }
3750
- function codexAppServerUiResultFromState(state) {
3751
- return {
3752
- schema: "humanish.codex-app-server-ui-result.v1",
3753
- ok: state.status === "passed",
3754
- cwd: state.cwd,
3755
- reason: state.reason,
3756
- stateFile: state.stateFile,
3757
- status: state.status,
3758
- ...(state.url === undefined ? {} : { url: state.url })
3759
- };
3760
- }
3761
- function parsePositiveInteger(value) {
3762
- if (!/^\d+$/.test(value)) {
3763
- return null;
3764
- }
3765
- const parsed = Number.parseInt(value, 10);
3766
- return Number.isSafeInteger(parsed) && parsed >= 1 ? parsed : null;
3767
- }
3768
- function parseTimeoutMs(value) {
3769
- if (!/^\d+$/.test(value)) {
3770
- return null;
3771
- }
3772
- const parsed = Number.parseInt(value, 10);
3773
- return parsed >= 1 && parsed <= 3_600_000 ? parsed : null;
3774
- }
3775
- function parseObserverPort(value) {
3776
- if (!/^\d+$/.test(value)) {
3777
- return null;
3778
- }
3779
- const parsed = Number.parseInt(value, 10);
3780
- return parsed >= 0 && parsed <= 65535 ? parsed : null;
3781
- }
3782
- export async function followObserver(io, result, server, options = {}) {
3783
- io.writeOut(`watching: ${result.serverUrl ?? result.observerUrl ?? result.observerPath}\n`);
3784
- io.writeOut("watching: press Ctrl-C to stop\n");
3785
- await new Promise((resolve) => {
3786
- const signalTarget = options.signalTarget ?? process;
3787
- const signals = options.signals ?? ["SIGINT", "SIGTERM", "SIGHUP"];
3788
- const handlers = new Map();
3789
- let stopping = false;
3790
- const stop = (signal) => {
3791
- if (stopping) {
3792
- return;
3793
- }
3794
- stopping = true;
3795
- for (const [registeredSignal, handler] of handlers.entries()) {
3796
- signalTarget.removeListener(registeredSignal, handler);
3797
- }
3798
- io.setExitCode(exitCodeForSignal(signal));
3799
- (async () => {
3800
- try {
3801
- await server.close();
3802
- }
3803
- catch (error) {
3804
- io.writeErr(`watch cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`);
3805
- }
3806
- if (options.onStop) {
3807
- try {
3808
- const messages = await options.onStop();
3809
- for (const message of messages) {
3810
- io.writeOut(`watch cleanup: ${message}\n`);
3811
- }
3812
- }
3813
- catch (error) {
3814
- io.writeErr(`watch cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`);
3815
- }
3816
- }
3817
- io.writeOut("watch stopped\n");
3818
- resolve();
3819
- })();
3820
- };
3821
- for (const signal of signals) {
3822
- const handler = () => stop(signal);
3823
- handlers.set(signal, handler);
3824
- signalTarget.once(signal, handler);
3825
- }
3826
- });
3827
- }
3828
- function exitCodeForSignal(signal) {
3829
- switch (signal) {
3830
- case "SIGINT":
3831
- return 130;
3832
- case "SIGTERM":
3833
- return 143;
3834
- case "SIGHUP":
3835
- return 129;
3836
- }
3837
- }
3838
- /** `--candidate` as the module option, absent when not given (exactOptionalPropertyTypes). */
3839
- function candidateOption(options) {
3840
- return { ...(options.candidate === undefined ? {} : { candidate: options.candidate }),
3841
- ...(options.analysis === undefined ? {} : { analysis: options.analysis }),
3842
- ...(options.finding === undefined ? {} : { finding: options.finding }) };
3843
- }
3844
- function formatFeedbackHuman(result) {
3845
- if (!result.ok) {
3846
- return `${result.error?.code}: ${result.error?.message}\n`;
3847
- }
3848
- const candidates = result.candidates ?? [];
3849
- const noCandidates = result.candidates !== undefined && candidates.length === 0;
3850
- return [
3851
- noCandidates && result.draft === undefined ? "humanish feedback: no recorded candidates" : "humanish feedback ready",
3852
- `run: ${result.run}`,
3853
- ...(result.draftPath ? [`draft: ${result.draftPath}`] : []),
3854
- ...(result.issuePath ? [`issue: ${result.issuePath}`] : []),
3855
- ...(result.draft?.source_candidate_id ? [`candidate: ${result.draft.source_candidate_id}`] : []),
3856
- ...(noCandidates ? [
3857
- "candidates: none recorded",
3858
- "With no candidates, feedback draft and feedback issue can generate a run-summary follow-up after share_ready verification.",
3859
- ...(result.draft ? [`summary: ${result.draft.summary}`] : [])
3860
- ] : []),
3861
- // Every finding the run produced, so the second and third are one flag away (#609).
3862
- ...(candidates.length > 1 || (candidates.length === 1 && result.draft === undefined)
3863
- ? [
3864
- `candidates (${candidates.length}; choose one with --candidate <id>):`,
3865
- ...candidates.map((item) => `- ${item.id} [${item.failure_owner}] ${item.persona_id}: ${item.summary}`)
3866
- ]
3867
- : [])
3868
- ].join("\n") + "\n";
3869
- }
3870
- function formatOssLabHuman(result) {
3871
- if (!result.ok && result.error) {
3872
- return `${result.error.code}: ${result.error.message}\n`;
3873
- }
3874
- return [
3875
- `humanish lab oss-smoke ${result.ok ? "passed" : "failed"}`,
3876
- `run: ${result.runId}`,
3877
- ...(result.reportMarkdownPath ? [`report: ${result.reportMarkdownPath}`] : []),
3878
- `sandbox: ${result.cleanup.kept ? result.sandboxPath : "removed"}`,
3879
- ...result.repos.map((repo) => {
3880
- const passed = repo.steps.filter((step) => step.ok).length;
3881
- return `- ${repo.ok ? "ok" : "fail"} ${repo.repo}: ${passed}/${repo.steps.length} steps, ${repo.changedFiles.length} changed files in disposable clone`;
3882
- }),
3883
- ...result.warnings.map((warning) => `warning: ${warning}`)
3884
- ].join("\n") + "\n";
3885
- }
3886
- function formatOssMetaLabHuman(result) {
3887
- return [
3888
- `humanish lab oss ${result.ok ? (result.dryRun ? "dry-run" : "watch") : "failed"}`,
3889
- ...(result.error ? [`${result.error.code}: ${result.error.message}`] : []),
3890
- `run: ${result.runId ?? "not-created"}`,
3891
- `repos: ${result.repos.join(", ")}`,
3892
- ...(result.count === undefined ? [] : [`desktops: ${result.count}`]),
3893
- ...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
3894
- ...(result.observer?.observerUrl ? [`url: ${result.observer.observerUrl}`] : []),
3895
- ...(result.observer?.opened === undefined ? [] : [`opened: ${result.observer.opened ? "yes" : "no"}`]),
3896
- ...(result.observer?.bundlePath ? [`bundle: ${result.observer.bundlePath}`] : []),
3897
- ...result.assignments.map((assignment) => `- ${String(assignment.index).padStart(2, "0")} ${assignment.repo}: top-level desktop lane -> nested Humanish Observer`),
3898
- ...result.sandboxes.map((sandbox) => {
3899
- const sandboxLabel = sandbox.sandboxId ? ` sandbox=${sandbox.sandboxId}` : "";
3900
- const bootstrapLabel = sandbox.bootstrapStatus ? ` bootstrap=${sandbox.bootstrapStatus}` : "";
3901
- const completionLabel = sandbox.completionStatus ? ` completion=${sandbox.completionStatus}` : "";
3902
- const screenshotLabel = sandbox.screenshotPresent ? " screenshot=yes" : "";
3903
- return `sandbox ${sandbox.streamId}: ${sandbox.repo} stream=${sandbox.urlPresent ? "connected" : "missing"}${bootstrapLabel}${completionLabel}${screenshotLabel}${sandboxLabel}`;
3904
- }),
3905
- ...result.warnings.map((warning) => `warning: ${warning}`)
3906
- ].join("\n") + "\n";
3907
- }
3908
- function formatOssMetaLabCleanupHuman(result) {
3909
- return [
3910
- `humanish lab cleanup ${result.lab} ${result.ok ? "passed" : "failed"}`,
3911
- ...(result.cleanup.matched === undefined ? [] : [`matched: ${result.cleanup.matched}`]),
3912
- `killed: ${result.cleanup.killed}`,
3913
- `skipped: ${result.cleanup.skipped}`,
3914
- ...(result.cleanup.remaining === undefined ? [] : [`remaining: ${result.cleanup.remaining}`]),
3915
- ...result.cleanup.errors.map((error) => `error: ${error}`)
3916
- ].join("\n") + "\n";
3917
- }
3918
- function collectRepeated(value, previous) {
3919
- return [...previous, value];
3920
- }
3921
- // True when any tunnel-edge exposure flag is present (not --safe, which is an orthogonal filter).
3922
- // Used to refuse exposure on the non-lab watch path and on non-CUA backends, where there is no live
3923
- // desktop to stream.
3924
- function watchExposeRequested(o) {
3925
- return o.expose === true
3926
- || o.tunnel !== undefined
3927
- || o.tunnelDomain !== undefined
3928
- || o.oauth !== undefined
3929
- || (o.allowEmail?.length ?? 0) > 0
3930
- || (o.allowDomain?.length ?? 0) > 0
3931
- || o.publicUrl !== undefined;
3932
- }
3933
- // Map LabCommandOptions onto the shared exposure validator input.
3934
- function exposureRequestFromOptions(options) {
3935
- return {
3936
- expose: options.expose === true,
3937
- ...(options.tunnel === undefined ? {} : { tunnel: options.tunnel }),
3938
- ...(options.tunnelDomain === undefined ? {} : { tunnelDomain: options.tunnelDomain }),
3939
- ...(options.oauth === undefined ? {} : { oauth: options.oauth }),
3940
- allowEmails: options.allowEmail ?? [],
3941
- allowDomains: options.allowDomain ?? [],
3942
- ...(options.publicUrl === undefined ? {} : { publicUrl: options.publicUrl }),
3943
- safe: options.safe === true
3944
- };
3945
- }
3946
- function formatInitHuman(result) {
3947
- const title = result.ok
3948
- ? `humanish init ${result.mode}`
3949
- : `humanish init ${result.mode} blocked`;
3950
- const lines = [title, `cwd: ${result.cwd}`];
3951
- if (result.ok && result.mode === "applied") {
3952
- // A successful setup should leave its next action on the first terminal screen.
3953
- // Dry-runs and refusals retain their full plan; JSON always retains every change.
3954
- const counts = new Map();
3955
- for (const change of result.changes) {
3956
- counts.set(change.action, (counts.get(change.action) ?? 0) + 1);
3957
- }
3958
- const labels = {
3959
- create: "created", mkdir: "directories prepared", update: "updated", skip: "preserved"
3960
- };
3961
- lines.push("", `changes: ${[...counts].map(([action, count]) => `${count} ${labels[action]}`).join(", ")}`, "Use humanish init --dry-run --json to inspect all files.");
3962
- }
3963
- else {
3964
- lines.push("", "changes:", ...result.changes.map(formatInitChange));
3965
- }
3966
- if (result.warnings.length > 0) {
3967
- lines.push("", "warnings:", ...result.warnings.map((warning) => `- ${warning}`));
3968
- }
3969
- if (result.error) {
3970
- lines.push("", `${result.error.code}: ${result.error.message}`);
3971
- }
3972
- if (result.mode === "needs-confirmation") {
3973
- lines.push("", "Run with --dry-run --json to inspect or --yes to apply.");
3974
- }
3975
- else if (result.ok && result.nextSteps !== undefined) {
3976
- // Twenty files and no next step is where the funnel died (#505). Increasingly the reader here
3977
- // is a coding agent doing setup for someone, and an agent does what stdout tells it to.
3978
- lines.push(...result.nextSteps);
3979
- }
3980
- return `${lines.join("\n")}\n`;
3981
- }
3982
- function formatInitChange(change) {
3983
- return `- ${change.action.padEnd(6)} ${change.path} (${change.target}: ${change.reason})`;
3984
- }
3985
- export function shouldForceExitAfterOssMetaLab(output, options) {
3986
- return output.liveRequested === true
3987
- && (options.detach || options.wantsMachine)
3988
- && output.sandboxes.some((sandbox) => sandbox.urlPresent);
3989
- }
3990
- export function shouldServeOssMetaLabObserver(output, options) {
3991
- return options.wantsFollow
3992
- && output.observer?.ok === true;
3993
- }
3994
- export function exitCodeForOssMetaLab(output) {
3995
- return output.ok ? 0 : 2;
3996
- }
3997
- function wantsJson(command) {
3998
- let current = command;
3999
- while (current) {
4000
- if (current.opts().json === true) {
4001
- return true;
4002
- }
4003
- current = current.parent ?? null;
4004
- }
4005
- return false;
4006
- }
4007
- /** Listeners exist only while post-run analysis is active; actor signal behavior is unchanged. */
4008
- export function cliAutomaticAnalysisHooks(io) {
4009
- const controller = new AbortController();
4010
- return {
4011
- deps: { signal: controller.signal },
4012
- onStart: () => {
4013
- const cancel = () => { controller.abort(); };
4014
- const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
4015
- io.writeErr("Participants finished; preparing analysis…\n");
4016
- for (const signal of signals)
4017
- process.on(signal, cancel);
4018
- return () => { for (const signal of signals)
4019
- process.off(signal, cancel); };
4020
- }
4021
- };
4022
- }
4023
- /** Preserve the run's own result while making requested post-processing failures machine-visible. */
4024
- export function automaticAnalysisEnvelope(result) {
4025
- if (result === null || typeof result !== "object" || !("automaticAnalysis" in result) || !("ok" in result))
4026
- return result;
4027
- const run = result;
4028
- return { ...run, runOk: run.ok, ok: run.ok && automaticAnalysisSucceeded(run) };
4029
- }
4030
- //# sourceMappingURL=program.js.map