humanish 0.104.3 → 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.
- package/AGENTS.md +89 -94
- package/ARCHITECTURE.md +146 -0
- package/CHANGELOG.md +502 -0
- package/CONTEXT.md +51 -0
- package/CONTRIBUTING.md +111 -9
- package/README.md +213 -124
- package/SECURITY.md +1 -1
- package/TELEMETRY.md +112 -0
- package/dist/actors/affordance.d.ts +56 -0
- package/dist/actors/affordance.js +155 -0
- package/dist/actors/agent-session.js +42 -0
- package/dist/actors/codex/app-server-actor-trace.d.ts +11 -0
- package/dist/actors/codex/app-server-actor-trace.js +211 -0
- package/dist/actors/codex/app-server-client.d.ts +39 -0
- package/dist/actors/codex/app-server-client.js +116 -0
- package/dist/actors/codex/app-server-trace.d.ts +154 -0
- package/dist/actors/codex/app-server-trace.js +424 -0
- package/dist/actors/codex/app-server-ui.d.ts +40 -0
- package/dist/actors/codex/app-server-ui.js +328 -0
- package/dist/actors/codex/app-server.d.ts +44 -0
- package/dist/actors/codex/app-server.js +268 -0
- package/dist/actors/codex/qualified-versions.d.ts +45 -0
- package/dist/actors/codex/qualified-versions.js +87 -0
- package/dist/actors/codex/restricted-executable.d.ts +17 -0
- package/dist/actors/codex/restricted-executable.js +147 -0
- package/dist/actors/codex/restricted-launch.d.ts +116 -0
- package/dist/actors/codex/restricted-launch.js +222 -0
- package/dist/actors/codex/restricted-participant-policy.d.ts +46 -0
- package/dist/actors/codex/restricted-participant-policy.js +82 -0
- package/dist/actors/codex/restricted-participant.d.ts +20 -0
- package/dist/actors/codex/restricted-participant.js +432 -0
- package/dist/actors/codex/restricted-policy.d.ts +70 -0
- package/dist/actors/codex/restricted-policy.js +326 -0
- package/dist/actors/codex/restricted-session.d.ts +65 -0
- package/dist/actors/codex/restricted-session.js +432 -0
- package/dist/actors/codex/restricted-transport.d.ts +73 -0
- package/dist/actors/codex/restricted-transport.js +335 -0
- package/dist/actors/codex/restricted-turn.d.ts +62 -0
- package/dist/actors/codex/restricted-turn.js +284 -0
- package/dist/actors/computer-use/actor.d.ts +77 -0
- package/dist/actors/computer-use/actor.js +78 -0
- package/dist/actors/computer-use/admission-limit.d.ts +12 -0
- package/dist/actors/computer-use/admission-limit.js +19 -0
- package/dist/actors/computer-use/executor-error.js +48 -0
- package/dist/actors/computer-use/loop/actions.d.ts +26 -0
- package/dist/actors/computer-use/loop/actions.js +245 -0
- package/dist/actors/computer-use/loop/backstop.d.ts +42 -0
- package/dist/actors/computer-use/loop/backstop.js +202 -0
- package/dist/actors/computer-use/loop/debrief.d.ts +19 -0
- package/dist/actors/computer-use/loop/debrief.js +164 -0
- package/dist/actors/computer-use/loop/ending.d.ts +86 -0
- package/dist/actors/computer-use/loop/ending.js +316 -0
- package/dist/actors/computer-use/loop/observation.d.ts +55 -0
- package/dist/actors/computer-use/loop/observation.js +246 -0
- package/dist/actors/computer-use/loop/output-limit.d.ts +11 -0
- package/dist/actors/computer-use/loop/output-limit.js +21 -0
- package/dist/actors/computer-use/loop/provider-call.d.ts +19 -0
- package/dist/actors/computer-use/loop/provider-call.js +208 -0
- package/dist/actors/computer-use/loop/race.d.ts +34 -0
- package/dist/actors/computer-use/loop/race.js +79 -0
- package/dist/actors/computer-use/loop/session.d.ts +77 -0
- package/dist/actors/computer-use/loop/session.js +135 -0
- package/dist/actors/computer-use/loop/spend.d.ts +30 -0
- package/dist/actors/computer-use/loop/spend.js +85 -0
- package/dist/actors/computer-use/loop/trace.d.ts +35 -0
- package/dist/actors/computer-use/loop/trace.js +140 -0
- package/dist/actors/computer-use/loop/types.d.ts +404 -0
- package/dist/actors/computer-use/loop/types.js +1 -0
- package/dist/actors/computer-use/loop/usage.d.ts +102 -0
- package/dist/actors/computer-use/loop/usage.js +355 -0
- package/dist/actors/computer-use/loop.d.ts +11 -0
- package/dist/actors/computer-use/loop.js +248 -0
- package/dist/actors/computer-use/openai-provider.d.ts +107 -0
- package/dist/actors/computer-use/openai-provider.js +472 -0
- package/dist/actors/computer-use/openai-requests.d.ts +35 -0
- package/dist/actors/computer-use/openai-requests.js +74 -0
- package/dist/actors/computer-use/openai-wire.d.ts +67 -0
- package/dist/actors/computer-use/openai-wire.js +365 -0
- package/dist/actors/computer-use/provider-error.d.ts +27 -0
- package/dist/actors/computer-use/provider-error.js +86 -0
- package/dist/actors/computer-use/speech.js +6 -0
- package/dist/actors/contract.d.ts +315 -0
- package/dist/actors/contract.js +201 -0
- package/dist/actors/goal-source.js +38 -0
- package/dist/actors/local-agent/claude-session.d.ts +43 -0
- package/dist/actors/local-agent/claude-session.js +370 -0
- package/dist/actors/local-agent/cli.d.ts +115 -0
- package/dist/actors/local-agent/cli.js +413 -0
- package/dist/actors/local-agent/readiness.d.ts +31 -0
- package/dist/actors/local-agent/readiness.js +45 -0
- package/dist/actors/openai-endpoint.d.ts +2 -0
- package/dist/actors/openai-endpoint.js +2 -0
- package/dist/actors/output-token-limit.js +4 -0
- package/dist/actors/reasoning-effort.js +33 -0
- package/dist/actors/registry.d.ts +59 -0
- package/dist/actors/registry.js +89 -0
- package/dist/actors/scripted-browser/actor.d.ts +49 -0
- package/dist/actors/scripted-browser/actor.js +428 -0
- package/dist/actors/scripted-browser/browser-command.d.ts +1 -0
- package/dist/actors/scripted-browser/browser-command.js +48 -0
- package/dist/actors/scripted-browser/journey.d.ts +9 -0
- package/dist/actors/scripted-browser/journey.js +171 -0
- package/dist/actors/scripted-browser/steps.d.ts +60 -0
- package/dist/actors/scripted-browser/steps.js +384 -0
- package/dist/actors/scripted-browser/types.d.ts +135 -0
- package/dist/actors/scripted-browser/types.js +26 -0
- package/dist/actors/stop-cause.d.ts +9 -0
- package/dist/actors/stop-cause.js +42 -0
- package/dist/actors/stop-conditions.d.ts +40 -0
- package/dist/actors/stop-conditions.js +65 -0
- package/dist/analysis/automatic-completion.d.ts +38 -0
- package/dist/analysis/automatic-completion.js +90 -0
- package/dist/analysis/automatic-config.d.ts +41 -0
- package/dist/analysis/automatic-config.js +135 -0
- package/dist/analysis/automatic.d.ts +17 -0
- package/dist/analysis/automatic.js +279 -0
- package/dist/analysis/codex-config.d.ts +8 -0
- package/dist/analysis/codex-config.js +69 -0
- package/dist/analysis/evidence-sources.d.ts +23 -0
- package/dist/analysis/evidence-sources.js +251 -0
- package/dist/analysis/evidence.d.ts +18 -0
- package/dist/analysis/evidence.js +482 -0
- package/dist/analysis/execute.d.ts +52 -0
- package/dist/analysis/execute.js +504 -0
- package/dist/analysis/image-tokens.d.ts +1 -0
- package/dist/analysis/image-tokens.js +41 -0
- package/dist/analysis/job.d.ts +97 -0
- package/dist/analysis/job.js +311 -0
- package/dist/analysis/load.d.ts +3 -0
- package/dist/analysis/load.js +11 -0
- package/dist/analysis/provider.d.ts +57 -0
- package/dist/analysis/provider.js +227 -0
- package/dist/analysis/restricted-codex.d.ts +19 -0
- package/dist/analysis/restricted-codex.js +23 -0
- package/dist/analysis/service.d.ts +72 -0
- package/dist/analysis/service.js +399 -0
- package/dist/analysis/sharing.d.ts +11 -0
- package/dist/analysis/sharing.js +44 -0
- package/dist/analysis/store-executions.d.ts +27 -0
- package/dist/analysis/store-executions.js +224 -0
- package/dist/analysis/store.d.ts +31 -0
- package/dist/analysis/store.js +315 -0
- package/dist/analysis/types.d.ts +217 -0
- package/dist/analysis/types.js +12 -0
- package/dist/analysis/validation.d.ts +225 -0
- package/dist/analysis/validation.js +562 -0
- package/dist/browser-control/client.d.ts +18 -0
- package/dist/browser-control/client.js +239 -0
- package/dist/browser-control/dispatcher.d.ts +19 -0
- package/dist/browser-control/dispatcher.js +221 -0
- package/dist/browser-control/protocol.d.ts +492 -0
- package/dist/browser-control/protocol.js +360 -0
- package/dist/browser-control/recording-transfer.d.ts +3 -0
- package/dist/browser-control/recording-transfer.js +48 -0
- package/dist/browser-control/transport.d.ts +27 -0
- package/dist/browser-control/transport.js +189 -0
- package/dist/cli/commands/analysis-signals.d.ts +7 -0
- package/dist/cli/commands/analysis-signals.js +25 -0
- package/dist/cli/commands/analyze.d.ts +3 -0
- package/dist/cli/commands/analyze.js +212 -0
- package/dist/cli/commands/codex.d.ts +3 -0
- package/dist/cli/commands/codex.js +145 -0
- package/dist/cli/commands/comms.d.ts +3 -0
- package/dist/cli/commands/comms.js +225 -0
- package/dist/cli/commands/feedback.d.ts +3 -0
- package/dist/cli/commands/feedback.js +138 -0
- package/dist/cli/commands/lab-format.d.ts +8 -0
- package/dist/cli/commands/lab-format.js +105 -0
- package/dist/cli/commands/lab-route-computer-use.d.ts +20 -0
- package/dist/cli/commands/lab-route-computer-use.js +251 -0
- package/dist/cli/commands/lab-route-open.d.ts +27 -0
- package/dist/cli/commands/lab-route-open.js +37 -0
- package/dist/cli/commands/lab-route-preview.d.ts +20 -0
- package/dist/cli/commands/lab-route-preview.js +74 -0
- package/dist/cli/commands/lab-route-run.d.ts +17 -0
- package/dist/cli/commands/lab-route-run.js +31 -0
- package/dist/cli/commands/lab-route-scripted.d.ts +19 -0
- package/dist/cli/commands/lab-route-scripted.js +50 -0
- package/dist/cli/commands/lab-route-shared-world.d.ts +20 -0
- package/dist/cli/commands/lab-route-shared-world.js +135 -0
- package/dist/cli/commands/lab-route-terminal.d.ts +19 -0
- package/dist/cli/commands/lab-route-terminal.js +49 -0
- package/dist/cli/commands/lab-run.d.ts +9 -0
- package/dist/cli/commands/lab-run.js +166 -0
- package/dist/cli/commands/lab-scorer.d.ts +30 -0
- package/dist/cli/commands/lab-scorer.js +17 -0
- package/dist/cli/commands/lab.d.ts +3 -0
- package/dist/cli/commands/lab.js +193 -0
- package/dist/cli/commands/observe.d.ts +4 -0
- package/dist/cli/commands/observe.js +345 -0
- package/dist/cli/commands/runs.d.ts +11 -0
- package/dist/cli/commands/runs.js +314 -0
- package/dist/cli/commands/setup.d.ts +11 -0
- package/dist/cli/commands/setup.js +325 -0
- package/dist/cli/commands/tui.d.ts +32 -0
- package/dist/cli/commands/tui.js +305 -0
- package/dist/cli/commands/watch.d.ts +3 -0
- package/dist/cli/commands/watch.js +245 -0
- package/dist/cli/doctor.d.ts +39 -0
- package/dist/cli/doctor.js +263 -0
- package/dist/cli/first-run-path.d.ts +67 -0
- package/dist/cli/first-run-path.js +150 -0
- package/dist/cli/io.d.ts +85 -0
- package/dist/cli/io.js +232 -0
- package/dist/cli/observer-follow.d.ts +56 -0
- package/dist/cli/observer-follow.js +160 -0
- package/dist/cli/orientation.d.ts +30 -0
- package/dist/cli/orientation.js +114 -0
- package/dist/cli/program.d.ts +15 -0
- package/dist/cli/program.js +338 -0
- package/dist/cli/secret-prompt.js +39 -0
- package/dist/cli/telemetry.js +351 -0
- package/dist/cli.js +2 -4
- package/dist/comms/agentmail.d.ts +29 -0
- package/dist/comms/agentmail.js +568 -0
- package/dist/comms/capture-surface.d.ts +54 -0
- package/dist/comms/capture-surface.js +468 -0
- package/dist/comms/catch-host.d.ts +52 -0
- package/dist/comms/catch-host.js +192 -0
- package/dist/comms/connections.d.ts +20 -0
- package/dist/comms/connections.js +116 -0
- package/dist/comms/email-catch.d.ts +49 -0
- package/dist/comms/email-catch.js +236 -0
- package/dist/comms/extract.d.ts +7 -0
- package/dist/comms/extract.js +65 -0
- package/dist/comms/fake-inbox.d.ts +39 -0
- package/dist/comms/fake-inbox.js +136 -0
- package/dist/comms/images.d.ts +7 -0
- package/dist/comms/images.js +53 -0
- package/dist/comms/lease-store.d.ts +91 -0
- package/dist/comms/lease-store.js +600 -0
- package/dist/comms/providers.d.ts +30 -0
- package/dist/comms/providers.js +24 -0
- package/dist/comms/receiving-common.d.ts +17 -0
- package/dist/comms/receiving-common.js +82 -0
- package/dist/comms/receiving-evidence.d.ts +4 -0
- package/dist/comms/receiving-evidence.js +59 -0
- package/dist/comms/receiving-recovery.d.ts +22 -0
- package/dist/comms/receiving-recovery.js +100 -0
- package/dist/comms/receiving-render.d.ts +16 -0
- package/dist/comms/receiving-render.js +409 -0
- package/dist/comms/receiving-runtime.d.ts +41 -0
- package/dist/comms/receiving-runtime.js +66 -0
- package/dist/comms/receiving-surface.d.ts +8 -0
- package/dist/comms/receiving-surface.js +156 -0
- package/dist/comms/receiving-types.d.ts +122 -0
- package/dist/comms/receiving-types.js +3 -0
- package/dist/comms/receiving.d.ts +33 -0
- package/dist/comms/receiving.js +508 -0
- package/dist/comms/sandbox-catch-script.d.ts +16 -0
- package/dist/comms/sandbox-catch-script.js +331 -0
- package/dist/comms/sandbox-catch.d.ts +214 -0
- package/dist/comms/sandbox-catch.js +451 -0
- package/dist/comms/setup.d.ts +65 -0
- package/dist/comms/setup.js +259 -0
- package/dist/comms/thread-evidence.d.ts +28 -0
- package/dist/comms/thread-evidence.js +30 -0
- package/dist/comms/types.d.ts +76 -0
- package/dist/comms/types.js +8 -0
- package/dist/deprecated.d.ts +90 -0
- package/dist/deprecated.js +106 -0
- package/dist/evidence/browser-hygiene.js +42 -0
- package/dist/evidence/desktop-recorder.d.ts +39 -0
- package/dist/evidence/desktop-recorder.js +340 -0
- package/dist/evidence/desktop-recording-artifact.d.ts +5 -0
- package/dist/evidence/desktop-recording-artifact.js +53 -0
- package/dist/evidence/desktop-recording-types.d.ts +27 -0
- package/dist/evidence/desktop-recording-types.js +16 -0
- package/dist/evidence/encoded-text.d.ts +27 -0
- package/dist/evidence/encoded-text.js +243 -0
- package/dist/evidence/frame-signature.d.ts +17 -0
- package/dist/evidence/frame-signature.js +111 -0
- package/dist/evidence/image.d.ts +24 -0
- package/dist/evidence/image.js +170 -0
- package/dist/evidence/plain-text.d.ts +8 -0
- package/dist/evidence/plain-text.js +16 -0
- package/dist/evidence/redaction.d.ts +110 -0
- package/dist/evidence/redaction.js +329 -0
- package/dist/feedback/draft.d.ts +58 -0
- package/dist/feedback/draft.js +309 -0
- package/dist/feedback/export-bundle.d.ts +6 -0
- package/dist/feedback/export-bundle.js +585 -0
- package/dist/feedback/export.d.ts +50 -0
- package/dist/feedback/export.js +392 -0
- package/dist/feedback/feedback.d.ts +37 -0
- package/dist/feedback/feedback.js +282 -0
- package/dist/feedback/proof.d.ts +8 -0
- package/dist/feedback/proof.js +56 -0
- package/dist/guest/bootstrap.d.ts +55 -0
- package/dist/guest/bootstrap.js +348 -0
- package/dist/guest/browser-tools.d.ts +8 -0
- package/dist/guest/browser-tools.js +72 -0
- package/dist/guest/chromium-text.d.ts +62 -0
- package/dist/guest/chromium-text.js +386 -0
- package/dist/guest/desktop-executor.d.ts +57 -0
- package/dist/guest/desktop-executor.js +256 -0
- package/dist/guest/desktop-keys.d.ts +8 -0
- package/dist/guest/desktop-keys.js +89 -0
- package/dist/guest/desktop-media.d.ts +70 -0
- package/dist/guest/desktop-media.js +385 -0
- package/dist/guest/desktop-native.d.ts +14 -0
- package/dist/guest/desktop-native.js +156 -0
- package/dist/guest/media-config.d.ts +15 -0
- package/dist/guest/media-config.js +16 -0
- package/dist/guest/runtime-desktop.d.ts +45 -0
- package/dist/guest/runtime-desktop.js +420 -0
- package/dist/guest/runtime-processes.d.ts +28 -0
- package/dist/guest/runtime-processes.js +94 -0
- package/dist/guest/runtime.d.ts +31 -0
- package/dist/guest/runtime.js +124 -0
- package/dist/guest-media-worker.js +184 -48
- package/dist/guest-runtime-main.js +23 -10
- package/dist/guest-runtime-revision.js +1 -1
- package/dist/index.d.ts +32 -83
- package/dist/index.js +21 -42
- package/dist/keys/env-file.d.ts +15 -0
- package/dist/keys/env-file.js +109 -0
- package/dist/keys/key-resolution.d.ts +58 -0
- package/dist/keys/key-resolution.js +389 -0
- package/dist/lab/adapter-extension.d.ts +75 -0
- package/dist/lab/adapter-extension.js +253 -0
- package/dist/lab/adapter-scorer-loader.d.ts +43 -0
- package/dist/lab/adapter-scorer-loader.js +130 -0
- package/dist/lab/bag-overrides.d.ts +18 -0
- package/dist/lab/bag-overrides.js +121 -0
- package/dist/lab/composition-rules.d.ts +5 -0
- package/dist/lab/composition-rules.js +283 -0
- package/dist/lab/config.d.ts +7 -0
- package/dist/lab/config.js +208 -0
- package/dist/lab/device-presets.d.ts +97 -0
- package/dist/lab/device-presets.js +87 -0
- package/dist/lab/discover.d.ts +65 -0
- package/dist/lab/discover.js +422 -0
- package/dist/lab/doctor.d.ts +39 -0
- package/dist/lab/doctor.js +233 -0
- package/dist/lab/init-paths.d.ts +5 -0
- package/dist/lab/init-paths.js +89 -0
- package/dist/lab/init-plan.d.ts +20 -0
- package/dist/lab/init-plan.js +163 -0
- package/dist/lab/init-templates.d.ts +28 -0
- package/dist/lab/init-templates.js +416 -0
- package/dist/lab/init.d.ts +39 -0
- package/dist/lab/init.js +287 -0
- package/dist/lab/keys.d.ts +2 -0
- package/dist/lab/keys.js +264 -0
- package/dist/lab/parse/actors.d.ts +14 -0
- package/dist/lab/parse/actors.js +453 -0
- package/dist/lab/parse/comms.d.ts +12 -0
- package/dist/lab/parse/comms.js +246 -0
- package/dist/lab/parse/execution.d.ts +19 -0
- package/dist/lab/parse/execution.js +356 -0
- package/dist/lab/parse/subject-state.d.ts +30 -0
- package/dist/lab/parse/subject-state.js +179 -0
- package/dist/lab/parse/subject.d.ts +9 -0
- package/dist/lab/parse/subject.js +420 -0
- package/dist/lab/parse/values.d.ts +17 -0
- package/dist/lab/parse/values.js +65 -0
- package/dist/lab/participant-assignment.d.ts +12 -0
- package/dist/lab/participant-assignment.js +19 -0
- package/dist/lab/persona-resolve.d.ts +39 -0
- package/dist/lab/persona-resolve.js +118 -0
- package/dist/lab/persona.d.ts +39 -0
- package/dist/lab/persona.js +192 -0
- package/dist/lab/plan-base.d.ts +24 -0
- package/dist/lab/plan-base.js +113 -0
- package/dist/lab/plan-participants.d.ts +81 -0
- package/dist/lab/plan-participants.js +92 -0
- package/dist/lab/plan-types.d.ts +269 -0
- package/dist/lab/plan-types.js +5 -0
- package/dist/lab/plan.d.ts +27 -0
- package/dist/lab/plan.js +156 -0
- package/dist/lab/preflight-probes.d.ts +3 -0
- package/dist/lab/preflight-probes.js +312 -0
- package/dist/lab/preflight-result.d.ts +7 -0
- package/dist/lab/preflight-result.js +43 -0
- package/dist/lab/preflight.d.ts +95 -0
- package/dist/lab/preflight.js +117 -0
- package/dist/lab/route-inputs.d.ts +17 -0
- package/dist/lab/route-inputs.js +95 -0
- package/dist/lab/routing.d.ts +81 -0
- package/dist/lab/routing.js +214 -0
- package/dist/lab/run-lab-events.d.ts +69 -0
- package/dist/lab/run-lab-events.js +95 -0
- package/dist/lab/run-lab-options.d.ts +82 -0
- package/dist/lab/run-lab-options.js +399 -0
- package/dist/lab/summary.d.ts +60 -0
- package/dist/lab/summary.js +168 -0
- package/dist/lab/tasks.d.ts +91 -0
- package/dist/lab/tasks.js +139 -0
- package/dist/lab/types.d.ts +813 -0
- package/dist/lab/types.js +6 -0
- package/dist/lab/validation.d.ts +67 -0
- package/dist/lab/validation.js +387 -0
- package/dist/lab/warnings.d.ts +2 -0
- package/dist/lab/warnings.js +338 -0
- package/dist/observer/artifact.d.ts +23 -0
- package/dist/observer/artifact.js +235 -0
- package/dist/observer/data.d.ts +115 -0
- package/dist/observer/data.js +258 -0
- package/dist/observer/exposure.d.ts +64 -0
- package/dist/observer/exposure.js +143 -0
- package/dist/observer/http.js +50 -0
- package/dist/observer/library.js +190 -0
- package/dist/observer/listen.js +85 -0
- package/dist/observer/live.d.ts +9 -0
- package/dist/observer/live.js +25 -0
- package/dist/observer/pinned-files.d.ts +30 -0
- package/dist/observer/pinned-files.js +160 -0
- package/dist/observer/render.d.ts +69 -0
- package/dist/observer/render.js +350 -0
- package/dist/observer/run-inventory.d.ts +35 -0
- package/dist/observer/run-inventory.js +95 -0
- package/dist/observer/run-routes.d.ts +37 -0
- package/dist/observer/run-routes.js +373 -0
- package/dist/observer/serve.d.ts +97 -0
- package/dist/observer/serve.js +305 -0
- package/dist/observer/static.js +369 -0
- package/dist/observer/tunnel.js +112 -0
- package/dist/observer-app.html +9 -20
- package/dist/routes/computer-use/bundle-parts.d.ts +50 -0
- package/dist/routes/computer-use/bundle-parts.js +177 -0
- package/dist/routes/computer-use/bundle.d.ts +56 -0
- package/dist/routes/computer-use/bundle.js +184 -0
- package/dist/routes/computer-use/diagnostics.d.ts +38 -0
- package/dist/routes/computer-use/diagnostics.js +94 -0
- package/dist/routes/computer-use/e2b-desktop/comms.d.ts +65 -0
- package/dist/routes/computer-use/e2b-desktop/comms.js +225 -0
- package/dist/routes/computer-use/e2b-desktop/desktop.d.ts +3 -0
- package/dist/routes/computer-use/e2b-desktop/desktop.js +91 -0
- package/dist/routes/computer-use/e2b-desktop/fidelity.d.ts +65 -0
- package/dist/routes/computer-use/e2b-desktop/fidelity.js +156 -0
- package/dist/routes/computer-use/e2b-desktop/prepare.d.ts +18 -0
- package/dist/routes/computer-use/e2b-desktop/prepare.js +198 -0
- package/dist/routes/computer-use/e2b-desktop/start.d.ts +14 -0
- package/dist/routes/computer-use/e2b-desktop/start.js +193 -0
- package/dist/routes/computer-use/e2b-desktop/state.d.ts +65 -0
- package/dist/routes/computer-use/e2b-desktop/state.js +69 -0
- package/dist/routes/computer-use/e2b-desktop/teardown.d.ts +6 -0
- package/dist/routes/computer-use/e2b-desktop/teardown.js +152 -0
- package/dist/routes/computer-use/external-comms.d.ts +20 -0
- package/dist/routes/computer-use/external-comms.js +55 -0
- package/dist/routes/computer-use/fanout-bundle.d.ts +11 -0
- package/dist/routes/computer-use/fanout-bundle.js +274 -0
- package/dist/routes/computer-use/fanout-lanes.d.ts +15 -0
- package/dist/routes/computer-use/fanout-lanes.js +302 -0
- package/dist/routes/computer-use/in-process-desktop.d.ts +17 -0
- package/dist/routes/computer-use/in-process-desktop.js +40 -0
- package/dist/routes/computer-use/lanes.d.ts +35 -0
- package/dist/routes/computer-use/lanes.js +321 -0
- package/dist/routes/computer-use/legacy-lane-spec.d.ts +62 -0
- package/dist/routes/computer-use/legacy-lane-spec.js +39 -0
- package/dist/routes/computer-use/live-flush.d.ts +42 -0
- package/dist/routes/computer-use/live-flush.js +154 -0
- package/dist/routes/computer-use/local-tree-pack.d.ts +27 -0
- package/dist/routes/computer-use/local-tree-pack.js +47 -0
- package/dist/routes/computer-use/local-vm.d.ts +20 -0
- package/dist/routes/computer-use/local-vm.js +218 -0
- package/dist/routes/computer-use/participant-desktop.d.ts +63 -0
- package/dist/routes/computer-use/participant-desktop.js +33 -0
- package/dist/routes/computer-use/participant-facts.d.ts +6 -0
- package/dist/routes/computer-use/participant-facts.js +22 -0
- package/dist/routes/computer-use/participant-model.d.ts +30 -0
- package/dist/routes/computer-use/participant-model.js +222 -0
- package/dist/routes/computer-use/participant-prompt.d.ts +53 -0
- package/dist/routes/computer-use/participant-prompt.js +87 -0
- package/dist/routes/computer-use/participant-runs.d.ts +67 -0
- package/dist/routes/computer-use/participant-runs.js +255 -0
- package/dist/routes/computer-use/plan.d.ts +56 -0
- package/dist/routes/computer-use/plan.js +308 -0
- package/dist/routes/computer-use/preflight.d.ts +26 -0
- package/dist/routes/computer-use/preflight.js +85 -0
- package/dist/routes/computer-use/rerun-selection.d.ts +19 -0
- package/dist/routes/computer-use/rerun-selection.js +121 -0
- package/dist/routes/computer-use/result.d.ts +7 -0
- package/dist/routes/computer-use/result.js +292 -0
- package/dist/routes/computer-use/route.d.ts +33 -0
- package/dist/routes/computer-use/route.js +145 -0
- package/dist/routes/computer-use/run-lanes.d.ts +59 -0
- package/dist/routes/computer-use/run-lanes.js +135 -0
- package/dist/routes/computer-use/self-report.d.ts +21 -0
- package/dist/routes/computer-use/self-report.js +231 -0
- package/dist/routes/computer-use/setup.d.ts +136 -0
- package/dist/routes/computer-use/setup.js +371 -0
- package/dist/routes/computer-use/single-bundle.d.ts +97 -0
- package/dist/routes/computer-use/single-bundle.js +393 -0
- package/dist/routes/computer-use/subject-projection.d.ts +51 -0
- package/dist/routes/computer-use/subject-projection.js +159 -0
- package/dist/routes/computer-use/types.d.ts +539 -0
- package/dist/routes/computer-use/types.js +37 -0
- package/dist/routes/preview.d.ts +13 -0
- package/dist/routes/preview.js +36 -0
- package/dist/routes/scripted/bundle.d.ts +47 -0
- package/dist/routes/scripted/bundle.js +195 -0
- package/dist/routes/scripted/plan.d.ts +30 -0
- package/dist/routes/scripted/plan.js +95 -0
- package/dist/routes/scripted/result.d.ts +48 -0
- package/dist/routes/scripted/result.js +176 -0
- package/dist/routes/scripted/route.d.ts +28 -0
- package/dist/routes/scripted/route.js +218 -0
- package/dist/routes/scripted/scenario.d.ts +21 -0
- package/dist/routes/scripted/scenario.js +102 -0
- package/dist/routes/scripted/session-result.d.ts +8 -0
- package/dist/routes/scripted/session-result.js +60 -0
- package/dist/routes/scripted/setup.d.ts +45 -0
- package/dist/routes/scripted/setup.js +126 -0
- package/dist/routes/scripted/subject.d.ts +49 -0
- package/dist/routes/scripted/subject.js +156 -0
- package/dist/routes/scripted/surface-records.d.ts +26 -0
- package/dist/routes/scripted/surface-records.js +88 -0
- package/dist/routes/scripted/surface-sessions.d.ts +13 -0
- package/dist/routes/scripted/surface-sessions.js +40 -0
- package/dist/routes/scripted/types.d.ts +88 -0
- package/dist/routes/scripted/types.js +3 -0
- package/dist/routes/shared-world/bundle.d.ts +20 -0
- package/dist/routes/shared-world/bundle.js +446 -0
- package/dist/routes/shared-world/checkpoints.d.ts +25 -0
- package/dist/routes/shared-world/checkpoints.js +46 -0
- package/dist/routes/shared-world/comms.d.ts +68 -0
- package/dist/routes/shared-world/comms.js +176 -0
- package/dist/routes/shared-world/external-public.d.ts +21 -0
- package/dist/routes/shared-world/external-public.js +130 -0
- package/dist/routes/shared-world/handoff.d.ts +62 -0
- package/dist/routes/shared-world/handoff.js +348 -0
- package/dist/routes/shared-world/lobby-code.d.ts +25 -0
- package/dist/routes/shared-world/lobby-code.js +152 -0
- package/dist/routes/shared-world/plan.d.ts +27 -0
- package/dist/routes/shared-world/plan.js +174 -0
- package/dist/routes/shared-world/provenance.d.ts +27 -0
- package/dist/routes/shared-world/provenance.js +65 -0
- package/dist/routes/shared-world/provisioned.d.ts +57 -0
- package/dist/routes/shared-world/provisioned.js +505 -0
- package/dist/routes/shared-world/result.d.ts +18 -0
- package/dist/routes/shared-world/result.js +327 -0
- package/dist/routes/shared-world/route.d.ts +21 -0
- package/dist/routes/shared-world/route.js +277 -0
- package/dist/routes/shared-world/seat-records.d.ts +20 -0
- package/dist/routes/shared-world/seat-records.js +213 -0
- package/dist/routes/shared-world/seats.d.ts +38 -0
- package/dist/routes/shared-world/seats.js +208 -0
- package/dist/routes/shared-world/setup.d.ts +48 -0
- package/dist/routes/shared-world/setup.js +200 -0
- package/dist/routes/shared-world/types.d.ts +354 -0
- package/dist/routes/shared-world/types.js +29 -0
- package/dist/routes/terminal/adapter.d.ts +37 -0
- package/dist/routes/terminal/adapter.js +201 -0
- package/dist/routes/terminal/bundle.d.ts +79 -0
- package/dist/routes/terminal/bundle.js +305 -0
- package/dist/routes/terminal/credentials.d.ts +34 -0
- package/dist/routes/terminal/credentials.js +102 -0
- package/dist/routes/terminal/dry-run.d.ts +24 -0
- package/dist/routes/terminal/dry-run.js +157 -0
- package/dist/routes/terminal/encoding.js +79 -0
- package/dist/routes/terminal/ledger.d.ts +49 -0
- package/dist/routes/terminal/ledger.js +170 -0
- package/dist/routes/terminal/lifetime.d.ts +10 -0
- package/dist/routes/terminal/lifetime.js +22 -0
- package/dist/routes/terminal/live-finish.d.ts +31 -0
- package/dist/routes/terminal/live-finish.js +215 -0
- package/dist/routes/terminal/live-sandbox.d.ts +69 -0
- package/dist/routes/terminal/live-sandbox.js +452 -0
- package/dist/routes/terminal/persona.d.ts +20 -0
- package/dist/routes/terminal/persona.js +34 -0
- package/dist/routes/terminal/plan.d.ts +21 -0
- package/dist/routes/terminal/plan.js +115 -0
- package/dist/routes/terminal/recorder.d.ts +23 -0
- package/dist/routes/terminal/recorder.js +77 -0
- package/dist/routes/terminal/result.d.ts +47 -0
- package/dist/routes/terminal/result.js +114 -0
- package/dist/routes/terminal/route.d.ts +23 -0
- package/dist/routes/terminal/route.js +170 -0
- package/dist/routes/terminal/runtime-auth.d.ts +12 -0
- package/dist/routes/terminal/runtime-auth.js +23 -0
- package/dist/routes/terminal/runtime.d.ts +14 -0
- package/dist/routes/terminal/runtime.js +47 -0
- package/dist/routes/terminal/sandbox.d.ts +35 -0
- package/dist/routes/terminal/sandbox.js +126 -0
- package/dist/routes/terminal/session.d.ts +25 -0
- package/dist/routes/terminal/session.js +199 -0
- package/dist/routes/terminal/token-usage.d.ts +15 -0
- package/dist/routes/terminal/token-usage.js +93 -0
- package/dist/routes/terminal/trace.d.ts +40 -0
- package/dist/routes/terminal/trace.js +172 -0
- package/dist/routes/terminal/types.d.ts +342 -0
- package/dist/routes/terminal/types.js +31 -0
- package/dist/run/bundle-shape.d.ts +7 -0
- package/dist/run/bundle-shape.js +369 -0
- package/dist/run/bundle.d.ts +547 -0
- package/dist/run/bundle.js +44 -0
- package/dist/run/concurrency.js +24 -0
- package/dist/run/contained-output.d.ts +50 -0
- package/dist/run/contained-output.js +351 -0
- package/dist/run/cost-summary.d.ts +28 -0
- package/dist/run/cost-summary.js +213 -0
- package/dist/run/costs.js +140 -0
- package/dist/run/detail.d.ts +62 -0
- package/dist/run/detail.js +139 -0
- package/dist/run/dry-run-inputs.d.ts +9 -0
- package/dist/run/dry-run-inputs.js +90 -0
- package/dist/run/dry-run.d.ts +6 -0
- package/dist/run/dry-run.js +362 -0
- package/dist/run/feedback-shape.d.ts +7 -0
- package/dist/run/feedback-shape.js +89 -0
- package/dist/run/git-state.d.ts +35 -0
- package/dist/run/git-state.js +316 -0
- package/dist/run/git-workspace.d.ts +32 -0
- package/dist/run/git-workspace.js +450 -0
- package/dist/run/judge.d.ts +210 -0
- package/dist/run/judge.js +240 -0
- package/dist/run/locate.d.ts +22 -0
- package/dist/run/locate.js +122 -0
- package/dist/run/outcomes.d.ts +31 -0
- package/dist/run/outcomes.js +210 -0
- package/dist/run/participant-records.d.ts +28 -0
- package/dist/run/participant-records.js +63 -0
- package/dist/run/participant.d.ts +29 -0
- package/dist/run/participant.js +12 -0
- package/dist/run/paths.d.ts +79 -0
- package/dist/run/paths.js +356 -0
- package/dist/run/preflight-receipts.d.ts +46 -0
- package/dist/run/preflight-receipts.js +173 -0
- package/dist/run/pricing.d.ts +124 -0
- package/dist/run/pricing.js +298 -0
- package/dist/run/project.d.ts +8 -0
- package/dist/run/project.js +103 -0
- package/dist/run/projection.d.ts +184 -0
- package/dist/run/projection.js +356 -0
- package/dist/run/reclaim.d.ts +41 -0
- package/dist/run/reclaim.js +183 -0
- package/dist/run/results.d.ts +88 -0
- package/dist/run/results.js +3 -0
- package/dist/run/run-index.d.ts +107 -0
- package/dist/run/run-index.js +243 -0
- package/dist/run/run.d.ts +103 -0
- package/dist/run/run.js +199 -0
- package/dist/run/sandbox-receipts.d.ts +31 -0
- package/dist/run/sandbox-receipts.js +49 -0
- package/dist/run/shared-world-evidence.d.ts +220 -0
- package/dist/run/shared-world-evidence.js +49 -0
- package/dist/run/shared-world-shape.d.ts +26 -0
- package/dist/run/shared-world-shape.js +181 -0
- package/dist/run/stats.d.ts +82 -0
- package/dist/run/stats.js +258 -0
- package/dist/run/status.d.ts +111 -0
- package/dist/run/status.js +176 -0
- package/dist/run/stored-runs.d.ts +38 -0
- package/dist/run/stored-runs.js +273 -0
- package/dist/run/stream-shape.d.ts +4 -0
- package/dist/run/stream-shape.js +160 -0
- package/dist/run/streams.d.ts +257 -0
- package/dist/run/streams.js +12 -0
- package/dist/run/study-files.d.ts +35 -0
- package/dist/run/study-files.js +164 -0
- package/dist/run/terminal-contract.d.ts +25 -0
- package/dist/run/terminal-contract.js +47 -0
- package/dist/run/text.d.ts +2 -0
- package/dist/run/text.js +4 -0
- package/dist/run/transient-comms-secrets.js +72 -0
- package/dist/run/type-guards.d.ts +6 -0
- package/dist/run/type-guards.js +18 -0
- package/dist/run-lab.d.ts +110 -0
- package/dist/run-lab.js +148 -0
- package/dist/subject/clone.d.ts +37 -0
- package/dist/subject/clone.js +62 -0
- package/dist/subject/desktop-cli.d.ts +17 -0
- package/dist/subject/desktop-cli.js +51 -0
- package/dist/subject/local-tree-archive.d.ts +82 -0
- package/dist/subject/local-tree-archive.js +431 -0
- package/dist/subject/local-tree.d.ts +28 -0
- package/dist/subject/local-tree.js +58 -0
- package/dist/subject/node-bootstrap.d.ts +12 -0
- package/dist/subject/node-bootstrap.js +66 -0
- package/dist/subject/runtime.d.ts +13 -0
- package/dist/subject/runtime.js +50 -0
- package/dist/subject/serve.d.ts +39 -0
- package/dist/subject/serve.js +249 -0
- package/dist/subject/state.d.ts +23 -0
- package/dist/subject/state.js +58 -0
- package/dist/subject/steps.d.ts +37 -0
- package/dist/subject/steps.js +39 -0
- package/dist/substrates/command-failure.d.ts +23 -0
- package/dist/substrates/command-failure.js +45 -0
- package/dist/substrates/desktop-session.d.ts +41 -0
- package/dist/substrates/desktop-session.js +59 -0
- package/dist/substrates/detached.d.ts +55 -0
- package/dist/substrates/detached.js +139 -0
- package/dist/substrates/e2b/cdp-probe.d.ts +99 -0
- package/dist/substrates/e2b/cdp-probe.js +556 -0
- package/dist/substrates/e2b/desktop-browser.d.ts +81 -0
- package/dist/substrates/e2b/desktop-browser.js +410 -0
- package/dist/substrates/e2b/desktop-cdp.d.ts +77 -0
- package/dist/substrates/e2b/desktop-cdp.js +211 -0
- package/dist/substrates/e2b/desktop-executor.d.ts +92 -0
- package/dist/substrates/e2b/desktop-executor.js +302 -0
- package/dist/substrates/e2b/desktop-geometry.d.ts +83 -0
- package/dist/substrates/e2b/desktop-geometry.js +331 -0
- package/dist/substrates/e2b/desktop-media.d.ts +29 -0
- package/dist/substrates/e2b/desktop-media.js +151 -0
- package/dist/substrates/e2b/desktop-recording.d.ts +18 -0
- package/dist/substrates/e2b/desktop-recording.js +238 -0
- package/dist/substrates/e2b/desktop-resources.d.ts +11 -0
- package/dist/substrates/e2b/desktop-resources.js +35 -0
- package/dist/substrates/e2b/desktop-screenshot-cleanup.d.ts +15 -0
- package/dist/substrates/e2b/desktop-screenshot-cleanup.js +66 -0
- package/dist/substrates/e2b/lifetime.d.ts +12 -0
- package/dist/substrates/e2b/lifetime.js +18 -0
- package/dist/substrates/e2b/sandbox.d.ts +102 -0
- package/dist/substrates/e2b/sandbox.js +202 -0
- package/dist/substrates/e2b/sdk.d.ts +194 -0
- package/dist/substrates/e2b/sdk.js +168 -0
- package/dist/substrates/e2b/shell.d.ts +10 -0
- package/dist/substrates/e2b/shell.js +61 -0
- package/dist/substrates/local/captured-inbox.d.ts +6 -0
- package/dist/substrates/local/captured-inbox.js +87 -0
- package/dist/substrates/local/firecracker-desktop.d.ts +25 -0
- package/dist/substrates/local/firecracker-desktop.js +315 -0
- package/dist/substrates/local/runtime-config.d.ts +6 -0
- package/dist/substrates/local/runtime-config.js +91 -0
- package/dist/substrates/local/runtime-host.d.ts +30 -0
- package/dist/substrates/local/runtime-host.js +143 -0
- package/dist/substrates/local/runtime-release.d.ts +5 -0
- package/dist/substrates/local/runtime-release.js +32 -0
- package/dist/substrates/local/runtime-ssh.d.ts +9 -0
- package/dist/substrates/local/runtime-ssh.js +129 -0
- package/dist/substrates/local/runtime.d.ts +25 -0
- package/dist/substrates/local/runtime.js +190 -0
- package/dist/substrates/shell.d.ts +44 -0
- package/dist/substrates/shell.js +22 -0
- package/dist/tui/actions.d.ts +39 -0
- package/dist/tui/actions.js +203 -0
- package/dist/tui/contract.d.ts +144 -0
- package/dist/tui/contract.js +52 -0
- package/dist/tui/launch.js +172 -0
- package/dist/tui/project.d.ts +10 -0
- package/dist/tui/project.js +17 -0
- package/dist/tui-app.js +125 -125
- package/dist/verify/actor.d.ts +27 -0
- package/dist/verify/actor.js +292 -0
- package/dist/verify/artifacts.d.ts +47 -0
- package/dist/verify/artifacts.js +373 -0
- package/dist/verify/costs.d.ts +30 -0
- package/dist/verify/costs.js +129 -0
- package/dist/verify/liveness.d.ts +7 -0
- package/dist/verify/liveness.js +72 -0
- package/dist/verify/rerun.d.ts +3 -0
- package/dist/verify/rerun.js +46 -0
- package/dist/verify/shared-world-concurrent.d.ts +11 -0
- package/dist/verify/shared-world-concurrent.js +346 -0
- package/dist/verify/shared-world.d.ts +8 -0
- package/dist/verify/shared-world.js +295 -0
- package/dist/verify/subject.d.ts +17 -0
- package/dist/verify/subject.js +123 -0
- package/dist/verify/verify.d.ts +34 -0
- package/dist/verify/verify.js +333 -0
- package/docs/README.md +67 -0
- package/docs/architecture/actor-contract.md +215 -174
- package/docs/architecture/browser-control.md +51 -37
- package/docs/architecture/comms-inbox.md +8 -8
- package/docs/architecture/desktop-recording.md +2 -2
- package/docs/architecture/desktop-sessions.md +33 -21
- package/docs/architecture/external-public-shared-world.md +80 -64
- package/docs/architecture/github-feedback-loop.md +40 -27
- package/docs/architecture/guest-desktop.md +22 -10
- package/docs/architecture/local-browser-runtime.md +30 -21
- package/docs/architecture/observer-review.md +9 -9
- package/docs/architecture/observer.md +53 -57
- package/docs/architecture/participant-media.md +3 -3
- package/docs/architecture/project-layout.md +33 -137
- package/docs/architecture/real-email-receiving.md +7 -6
- package/docs/architecture/restricted-codex-analysis.md +211 -25
- package/docs/architecture/serve.md +89 -87
- package/docs/architecture/state-driven-executor.md +79 -64
- package/docs/architecture/task-protocol-support.md +18 -18
- package/docs/architecture/terminal-product-lane.md +92 -86
- package/docs/contracts/adapter-admission.md +8 -4
- package/docs/contracts/adapter-fixtures.md +6 -6
- package/docs/contracts/core.md +22 -24
- package/docs/contracts/feedback.md +33 -23
- package/docs/contracts/policy.md +28 -31
- package/docs/contracts/run-bundle.md +168 -82
- package/docs/contracts/schemas.md +594 -379
- package/docs/contracts/study-analysis.md +2 -2
- package/docs/contracts/study-costs.md +12 -12
- package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +36 -0
- package/docs/decisions/0002-observer-is-one-self-contained-file.md +30 -0
- package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +35 -0
- package/docs/decisions/0004-participant-desktops-stay-in-the-route.md +31 -0
- package/docs/decisions/README.md +16 -0
- package/docs/goals/current.md +22 -46
- package/docs/principles/actor-fidelity.md +51 -63
- package/docs/principles/engineering.md +81 -0
- package/docs/principles/invariants-and-defaults.md +48 -45
- package/docs/principles/self-driving-harness.md +11 -10
- package/docs/principles/three-roles.md +12 -13
- package/docs/product/automatic-analysis.md +39 -9
- package/docs/product/lobby-trivia-3player-external-public.md +14 -13
- package/docs/product/open-source-install-experience.md +34 -38
- package/docs/ramp/README.md +91 -213
- package/docs/release/open-source-readiness.md +5 -2
- package/docs/release/public-readiness-standard.md +1 -1
- package/examples/README.md +11 -0
- package/examples/participant/README.md +80 -0
- package/examples/participant/app.mjs +55 -0
- package/examples/participant/run.mjs +116 -0
- package/examples/scorer/README.md +29 -0
- package/examples/scorer/run.mjs +63 -0
- package/examples/scorer/scorer.mjs +20 -0
- package/package.json +61 -57
- package/skills/humanish/SKILL.md +112 -83
- package/skills/humanish/agents/openai.yaml +2 -2
- package/dist/actor-contract.d.ts +0 -288
- package/dist/actor-contract.js +0 -340
- package/dist/actor-contract.js.map +0 -1
- package/dist/actor-goal-source.js +0 -32
- package/dist/actor-goal-source.js.map +0 -1
- package/dist/actor-registry.d.ts +0 -77
- package/dist/actor-registry.js +0 -106
- package/dist/actor-registry.js.map +0 -1
- package/dist/actor-stop-cause.d.ts +0 -9
- package/dist/actor-stop-cause.js +0 -42
- package/dist/actor-stop-cause.js.map +0 -1
- package/dist/adapter-extension.d.ts +0 -76
- package/dist/adapter-extension.js +0 -273
- package/dist/adapter-extension.js.map +0 -1
- package/dist/adapter-scorer-loader.d.ts +0 -42
- package/dist/adapter-scorer-loader.js +0 -129
- package/dist/adapter-scorer-loader.js.map +0 -1
- package/dist/affordance.d.ts +0 -53
- package/dist/affordance.js +0 -145
- package/dist/affordance.js.map +0 -1
- package/dist/agent-session.js +0 -43
- package/dist/agent-session.js.map +0 -1
- package/dist/argv.d.ts +0 -1
- package/dist/argv.js +0 -8
- package/dist/argv.js.map +0 -1
- package/dist/artifact-reference.d.ts +0 -22
- package/dist/artifact-reference.js +0 -47
- package/dist/artifact-reference.js.map +0 -1
- package/dist/automatic-analysis-completion.d.ts +0 -25
- package/dist/automatic-analysis-completion.js +0 -56
- package/dist/automatic-analysis-completion.js.map +0 -1
- package/dist/automatic-analysis-config.d.ts +0 -38
- package/dist/automatic-analysis-config.js +0 -68
- package/dist/automatic-analysis-config.js.map +0 -1
- package/dist/automatic-study-analysis.d.ts +0 -17
- package/dist/automatic-study-analysis.js +0 -195
- package/dist/automatic-study-analysis.js.map +0 -1
- package/dist/browser-control-client.d.ts +0 -18
- package/dist/browser-control-client.js +0 -176
- package/dist/browser-control-client.js.map +0 -1
- package/dist/browser-control-dispatcher.d.ts +0 -19
- package/dist/browser-control-dispatcher.js +0 -147
- package/dist/browser-control-dispatcher.js.map +0 -1
- package/dist/browser-control-protocol.d.ts +0 -472
- package/dist/browser-control-protocol.js +0 -185
- package/dist/browser-control-protocol.js.map +0 -1
- package/dist/browser-control-recording-transfer.d.ts +0 -4
- package/dist/browser-control-recording-transfer.js +0 -42
- package/dist/browser-control-recording-transfer.js.map +0 -1
- package/dist/browser-control-transport.d.ts +0 -27
- package/dist/browser-control-transport.js +0 -177
- package/dist/browser-control-transport.js.map +0 -1
- package/dist/browser-evidence-hygiene.js +0 -43
- package/dist/browser-evidence-hygiene.js.map +0 -1
- package/dist/chrome-cdp-probe.d.ts +0 -97
- package/dist/chrome-cdp-probe.js +0 -536
- package/dist/chrome-cdp-probe.js.map +0 -1
- package/dist/claude-agent-sdk.d.ts +0 -94
- package/dist/claude-agent-sdk.js +0 -367
- package/dist/claude-agent-sdk.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/codex-app-server-ui.d.ts +0 -39
- package/dist/codex-app-server-ui.js +0 -357
- package/dist/codex-app-server-ui.js.map +0 -1
- package/dist/codex-app-server.d.ts +0 -144
- package/dist/codex-app-server.js +0 -754
- package/dist/codex-app-server.js.map +0 -1
- package/dist/command-failure.d.ts +0 -43
- package/dist/command-failure.js +0 -73
- package/dist/command-failure.js.map +0 -1
- package/dist/comms-agentmail.d.ts +0 -25
- package/dist/comms-agentmail.js +0 -418
- package/dist/comms-agentmail.js.map +0 -1
- package/dist/comms-catch-host.d.ts +0 -52
- package/dist/comms-catch-host.js +0 -178
- package/dist/comms-catch-host.js.map +0 -1
- package/dist/comms-connections.d.ts +0 -52
- package/dist/comms-connections.js +0 -123
- package/dist/comms-connections.js.map +0 -1
- package/dist/comms-email-catch.d.ts +0 -54
- package/dist/comms-email-catch.js +0 -212
- package/dist/comms-email-catch.js.map +0 -1
- package/dist/comms-evidence.d.ts +0 -27
- package/dist/comms-evidence.js +0 -31
- package/dist/comms-evidence.js.map +0 -1
- package/dist/comms-fake-inbox.d.ts +0 -45
- package/dist/comms-fake-inbox.js +0 -184
- package/dist/comms-fake-inbox.js.map +0 -1
- package/dist/comms-images.d.ts +0 -7
- package/dist/comms-images.js +0 -39
- package/dist/comms-images.js.map +0 -1
- package/dist/comms-inbox.d.ts +0 -66
- package/dist/comms-inbox.js +0 -389
- package/dist/comms-inbox.js.map +0 -1
- package/dist/comms-lease-store.d.ts +0 -91
- package/dist/comms-lease-store.js +0 -495
- package/dist/comms-lease-store.js.map +0 -1
- package/dist/comms-receiving-evidence.d.ts +0 -46
- package/dist/comms-receiving-evidence.js +0 -35
- package/dist/comms-receiving-evidence.js.map +0 -1
- package/dist/comms-receiving-inbox.d.ts +0 -16
- package/dist/comms-receiving-inbox.js +0 -410
- package/dist/comms-receiving-inbox.js.map +0 -1
- package/dist/comms-receiving-runtime.d.ts +0 -25
- package/dist/comms-receiving-runtime.js +0 -45
- package/dist/comms-receiving-runtime.js.map +0 -1
- package/dist/comms-receiving-types.d.ts +0 -98
- package/dist/comms-receiving-types.js +0 -2
- package/dist/comms-receiving-types.js.map +0 -1
- package/dist/comms-receiving.d.ts +0 -58
- package/dist/comms-receiving.js +0 -554
- package/dist/comms-receiving.js.map +0 -1
- package/dist/comms-sandbox-catch.d.ts +0 -229
- package/dist/comms-sandbox-catch.js +0 -737
- package/dist/comms-sandbox-catch.js.map +0 -1
- package/dist/comms-setup.d.ts +0 -40
- package/dist/comms-setup.js +0 -121
- package/dist/comms-setup.js.map +0 -1
- package/dist/comms-types.d.ts +0 -72
- package/dist/comms-types.js +0 -10
- package/dist/comms-types.js.map +0 -1
- package/dist/computer-use-actor.d.ts +0 -76
- package/dist/computer-use-actor.js +0 -68
- package/dist/computer-use-actor.js.map +0 -1
- package/dist/computer-use.d.ts +0 -429
- package/dist/computer-use.js +0 -1749
- package/dist/computer-use.js.map +0 -1
- package/dist/concurrency.js +0 -27
- package/dist/concurrency.js.map +0 -1
- package/dist/concurrent-shared-world-lab.d.ts +0 -186
- package/dist/concurrent-shared-world-lab.js +0 -2179
- package/dist/concurrent-shared-world-lab.js.map +0 -1
- package/dist/core/git-state.d.ts +0 -33
- package/dist/core/git-state.js +0 -277
- package/dist/core/git-state.js.map +0 -1
- package/dist/core/git-workspace.d.ts +0 -31
- package/dist/core/git-workspace.js +0 -447
- package/dist/core/git-workspace.js.map +0 -1
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.js +0 -3
- package/dist/core/index.js.map +0 -1
- package/dist/core/run-primitives.d.ts +0 -66
- package/dist/core/run-primitives.js +0 -120
- package/dist/core/run-primitives.js.map +0 -1
- package/dist/cua-actor-lab.d.ts +0 -855
- package/dist/cua-actor-lab.js +0 -3545
- package/dist/cua-actor-lab.js.map +0 -1
- package/dist/cua-admission-limit.d.ts +0 -12
- package/dist/cua-admission-limit.js +0 -20
- package/dist/cua-admission-limit.js.map +0 -1
- package/dist/cua-desktop-lane.d.ts +0 -35
- package/dist/cua-desktop-lane.js +0 -13
- package/dist/cua-desktop-lane.js.map +0 -1
- package/dist/cua-diagnostics.d.ts +0 -38
- package/dist/cua-diagnostics.js +0 -68
- package/dist/cua-diagnostics.js.map +0 -1
- package/dist/cua-executor-error.js +0 -48
- package/dist/cua-executor-error.js.map +0 -1
- package/dist/cua-provider-error.d.ts +0 -16
- package/dist/cua-provider-error.js +0 -35
- package/dist/cua-provider-error.js.map +0 -1
- package/dist/cua-speech.js +0 -2
- package/dist/cua-speech.js.map +0 -1
- package/dist/desktop-recorder.d.ts +0 -39
- package/dist/desktop-recorder.js +0 -226
- package/dist/desktop-recorder.js.map +0 -1
- package/dist/desktop-recording-artifact.d.ts +0 -5
- package/dist/desktop-recording-artifact.js +0 -54
- package/dist/desktop-recording-artifact.js.map +0 -1
- package/dist/desktop-recording-types.d.ts +0 -24
- package/dist/desktop-recording-types.js +0 -14
- package/dist/desktop-recording-types.js.map +0 -1
- package/dist/desktop-session.d.ts +0 -41
- package/dist/desktop-session.js +0 -47
- package/dist/desktop-session.js.map +0 -1
- package/dist/device-presets.d.ts +0 -67
- package/dist/device-presets.js +0 -55
- package/dist/device-presets.js.map +0 -1
- package/dist/doctor-lab.d.ts +0 -38
- package/dist/doctor-lab.js +0 -126
- package/dist/doctor-lab.js.map +0 -1
- package/dist/e2b-cua-desktop.d.ts +0 -3
- package/dist/e2b-cua-desktop.js +0 -708
- package/dist/e2b-cua-desktop.js.map +0 -1
- package/dist/e2b-cua-provisioning.d.ts +0 -314
- package/dist/e2b-cua-provisioning.js +0 -1219
- package/dist/e2b-cua-provisioning.js.map +0 -1
- package/dist/e2b-desktop-executor.d.ts +0 -101
- package/dist/e2b-desktop-executor.js +0 -297
- package/dist/e2b-desktop-executor.js.map +0 -1
- package/dist/e2b-desktop-launch.d.ts +0 -243
- package/dist/e2b-desktop-launch.js +0 -234
- package/dist/e2b-desktop-launch.js.map +0 -1
- package/dist/e2b-desktop-media.d.ts +0 -14
- package/dist/e2b-desktop-media.js +0 -58
- package/dist/e2b-desktop-media.js.map +0 -1
- package/dist/e2b-desktop-recording.d.ts +0 -16
- package/dist/e2b-desktop-recording.js +0 -162
- package/dist/e2b-desktop-recording.js.map +0 -1
- package/dist/e2b-desktop-resources.d.ts +0 -11
- package/dist/e2b-desktop-resources.js +0 -33
- package/dist/e2b-desktop-resources.js.map +0 -1
- package/dist/e2b-desktop-screenshot-cleanup.d.ts +0 -15
- package/dist/e2b-desktop-screenshot-cleanup.js +0 -67
- package/dist/e2b-desktop-screenshot-cleanup.js.map +0 -1
- package/dist/e2b-desktop-session.d.ts +0 -7
- package/dist/e2b-desktop-session.js +0 -29
- package/dist/e2b-desktop-session.js.map +0 -1
- package/dist/e2b-detached.d.ts +0 -53
- package/dist/e2b-detached.js +0 -130
- package/dist/e2b-detached.js.map +0 -1
- package/dist/e2b-terminal-lab.d.ts +0 -395
- package/dist/e2b-terminal-lab.js +0 -2041
- package/dist/e2b-terminal-lab.js.map +0 -1
- package/dist/env-file.d.ts +0 -14
- package/dist/env-file.js +0 -108
- package/dist/env-file.js.map +0 -1
- package/dist/export-bundle.d.ts +0 -7
- package/dist/export-bundle.js +0 -441
- package/dist/export-bundle.js.map +0 -1
- package/dist/export.d.ts +0 -49
- package/dist/export.js +0 -308
- package/dist/export.js.map +0 -1
- package/dist/feedback-proof.d.ts +0 -8
- package/dist/feedback-proof.js +0 -44
- package/dist/feedback-proof.js.map +0 -1
- package/dist/feedback.d.ts +0 -76
- package/dist/feedback.js +0 -496
- package/dist/feedback.js.map +0 -1
- package/dist/first-run-path.d.ts +0 -55
- package/dist/first-run-path.js +0 -136
- package/dist/first-run-path.js.map +0 -1
- package/dist/frame-signature.d.ts +0 -24
- package/dist/frame-signature.js +0 -128
- package/dist/frame-signature.js.map +0 -1
- package/dist/guest-bootstrap.d.ts +0 -55
- package/dist/guest-bootstrap.js +0 -285
- package/dist/guest-bootstrap.js.map +0 -1
- package/dist/guest-browser-tools.d.ts +0 -8
- package/dist/guest-browser-tools.js +0 -66
- package/dist/guest-browser-tools.js.map +0 -1
- package/dist/guest-chromium-text.d.ts +0 -27
- package/dist/guest-chromium-text.js +0 -281
- package/dist/guest-chromium-text.js.map +0 -1
- package/dist/guest-desktop-executor.d.ts +0 -22
- package/dist/guest-desktop-executor.js +0 -178
- package/dist/guest-desktop-executor.js.map +0 -1
- package/dist/guest-desktop-media.d.ts +0 -33
- package/dist/guest-desktop-media.js +0 -281
- package/dist/guest-desktop-media.js.map +0 -1
- package/dist/guest-desktop-native.d.ts +0 -14
- package/dist/guest-desktop-native.js +0 -131
- package/dist/guest-desktop-native.js.map +0 -1
- package/dist/guest-media-config.d.ts +0 -15
- package/dist/guest-media-config.js +0 -8
- package/dist/guest-media-config.js.map +0 -1
- package/dist/guest-media-worker.js.map +0 -1
- package/dist/guest-runtime-desktop.d.ts +0 -42
- package/dist/guest-runtime-desktop.js +0 -314
- package/dist/guest-runtime-desktop.js.map +0 -1
- package/dist/guest-runtime-main.js.map +0 -1
- package/dist/guest-runtime-revision.js.map +0 -1
- package/dist/guest-runtime.d.ts +0 -31
- package/dist/guest-runtime.js +0 -99
- package/dist/guest-runtime.js.map +0 -1
- package/dist/image-evidence.d.ts +0 -2
- package/dist/image-evidence.js +0 -47
- package/dist/image-evidence.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/init-templates.d.ts +0 -25
- package/dist/init-templates.js +0 -542
- package/dist/init-templates.js.map +0 -1
- package/dist/init.d.ts +0 -38
- package/dist/init.js +0 -532
- package/dist/init.js.map +0 -1
- package/dist/key-resolution.d.ts +0 -67
- package/dist/key-resolution.js +0 -375
- package/dist/key-resolution.js.map +0 -1
- package/dist/lab-app-runner.d.ts +0 -78
- package/dist/lab-app-runner.js +0 -403
- package/dist/lab-app-runner.js.map +0 -1
- package/dist/lab-config.d.ts +0 -968
- package/dist/lab-config.js +0 -2714
- package/dist/lab-config.js.map +0 -1
- package/dist/lab-engine.d.ts +0 -97
- package/dist/lab-engine.js +0 -285
- package/dist/lab-engine.js.map +0 -1
- package/dist/lab-preflight.d.ts +0 -70
- package/dist/lab-preflight.js +0 -388
- package/dist/lab-preflight.js.map +0 -1
- package/dist/lab-summary.d.ts +0 -57
- package/dist/lab-summary.js +0 -126
- package/dist/lab-summary.js.map +0 -1
- package/dist/labs.d.ts +0 -59
- package/dist/labs.js +0 -385
- package/dist/labs.js.map +0 -1
- package/dist/listen.js +0 -84
- package/dist/listen.js.map +0 -1
- package/dist/local-agent-claude-session.d.ts +0 -45
- package/dist/local-agent-claude-session.js +0 -203
- package/dist/local-agent-claude-session.js.map +0 -1
- package/dist/local-agent-cli.d.ts +0 -121
- package/dist/local-agent-cli.js +0 -427
- package/dist/local-agent-cli.js.map +0 -1
- package/dist/local-captured-inbox.d.ts +0 -6
- package/dist/local-captured-inbox.js +0 -65
- package/dist/local-captured-inbox.js.map +0 -1
- package/dist/local-firecracker-desktop.d.ts +0 -23
- package/dist/local-firecracker-desktop.js +0 -191
- package/dist/local-firecracker-desktop.js.map +0 -1
- package/dist/local-firecracker-study.d.ts +0 -9
- package/dist/local-firecracker-study.js +0 -135
- package/dist/local-firecracker-study.js.map +0 -1
- package/dist/local-runtime-config.d.ts +0 -6
- package/dist/local-runtime-config.js +0 -60
- package/dist/local-runtime-config.js.map +0 -1
- package/dist/local-runtime-host.d.ts +0 -31
- package/dist/local-runtime-host.js +0 -128
- package/dist/local-runtime-host.js.map +0 -1
- package/dist/local-runtime-release.d.ts +0 -7
- package/dist/local-runtime-release.js +0 -33
- package/dist/local-runtime-release.js.map +0 -1
- package/dist/local-runtime-ssh.d.ts +0 -27
- package/dist/local-runtime-ssh.js +0 -91
- package/dist/local-runtime-ssh.js.map +0 -1
- package/dist/local-runtime.d.ts +0 -25
- package/dist/local-runtime.js +0 -133
- package/dist/local-runtime.js.map +0 -1
- package/dist/observer-data.d.ts +0 -114
- package/dist/observer-data.js +0 -224
- package/dist/observer-data.js.map +0 -1
- package/dist/observer-library.js +0 -191
- package/dist/observer-library.js.map +0 -1
- package/dist/observer-serve.d.ts +0 -99
- package/dist/observer-serve.js +0 -284
- package/dist/observer-serve.js.map +0 -1
- package/dist/observer-static.js +0 -357
- package/dist/observer-static.js.map +0 -1
- package/dist/observer.d.ts +0 -118
- package/dist/observer.js +0 -947
- package/dist/observer.js.map +0 -1
- package/dist/openai-responses-cu.d.ts +0 -158
- package/dist/openai-responses-cu.js +0 -714
- package/dist/openai-responses-cu.js.map +0 -1
- package/dist/orientation.d.ts +0 -29
- package/dist/orientation.js +0 -95
- package/dist/orientation.js.map +0 -1
- package/dist/oss-lab.d.ts +0 -55
- package/dist/oss-lab.js +0 -344
- package/dist/oss-lab.js.map +0 -1
- package/dist/oss-meta-lab-scoring.d.ts +0 -15
- package/dist/oss-meta-lab-scoring.js +0 -145
- package/dist/oss-meta-lab-scoring.js.map +0 -1
- package/dist/oss-meta-lab.d.ts +0 -287
- package/dist/oss-meta-lab.js +0 -4551
- package/dist/oss-meta-lab.js.map +0 -1
- package/dist/oss-remote-telemetry.d.ts +0 -77
- package/dist/oss-remote-telemetry.js +0 -403
- package/dist/oss-remote-telemetry.js.map +0 -1
- package/dist/output-token-limit.js +0 -5
- package/dist/output-token-limit.js.map +0 -1
- package/dist/participant-assignment.d.ts +0 -12
- package/dist/participant-assignment.js +0 -15
- package/dist/participant-assignment.js.map +0 -1
- package/dist/persona-resolve.d.ts +0 -44
- package/dist/persona-resolve.js +0 -109
- package/dist/persona-resolve.js.map +0 -1
- package/dist/persona.d.ts +0 -35
- package/dist/persona.js +0 -141
- package/dist/persona.js.map +0 -1
- package/dist/pi-agent-core.d.ts +0 -77
- package/dist/pi-agent-core.js +0 -207
- package/dist/pi-agent-core.js.map +0 -1
- package/dist/pricing.d.ts +0 -145
- package/dist/pricing.js +0 -277
- package/dist/pricing.js.map +0 -1
- package/dist/program.d.ts +0 -96
- package/dist/program.js +0 -4029
- package/dist/program.js.map +0 -1
- package/dist/reasoning-effort.js +0 -26
- package/dist/reasoning-effort.js.map +0 -1
- package/dist/reclaim.d.ts +0 -31
- package/dist/reclaim.js +0 -90
- package/dist/reclaim.js.map +0 -1
- package/dist/redaction.d.ts +0 -90
- package/dist/redaction.js +0 -294
- package/dist/redaction.js.map +0 -1
- package/dist/restricted-codex-analysis.d.ts +0 -15
- package/dist/restricted-codex-analysis.js +0 -13
- package/dist/restricted-codex-analysis.js.map +0 -1
- package/dist/restricted-codex-participant-policy.d.ts +0 -44
- package/dist/restricted-codex-participant-policy.js +0 -43
- package/dist/restricted-codex-participant-policy.js.map +0 -1
- package/dist/restricted-codex-participant-run.d.ts +0 -20
- package/dist/restricted-codex-participant-run.js +0 -79
- package/dist/restricted-codex-participant-run.js.map +0 -1
- package/dist/restricted-codex-participant.d.ts +0 -20
- package/dist/restricted-codex-participant.js +0 -250
- package/dist/restricted-codex-participant.js.map +0 -1
- package/dist/restricted-codex-policy.d.ts +0 -64
- package/dist/restricted-codex-policy.js +0 -176
- package/dist/restricted-codex-policy.js.map +0 -1
- package/dist/restricted-codex-session.d.ts +0 -44
- package/dist/restricted-codex-session.js +0 -657
- package/dist/restricted-codex-session.js.map +0 -1
- package/dist/restricted-codex-transport.d.ts +0 -69
- package/dist/restricted-codex-transport.js +0 -296
- package/dist/restricted-codex-transport.js.map +0 -1
- package/dist/run-detail.d.ts +0 -61
- package/dist/run-detail.js +0 -137
- package/dist/run-detail.js.map +0 -1
- package/dist/run-index.d.ts +0 -71
- package/dist/run-index.js +0 -206
- package/dist/run-index.js.map +0 -1
- package/dist/run-narration-secrets.js +0 -68
- package/dist/run-narration-secrets.js.map +0 -1
- package/dist/run-paths.d.ts +0 -56
- package/dist/run-paths.js +0 -355
- package/dist/run-paths.js.map +0 -1
- package/dist/run-projection.d.ts +0 -186
- package/dist/run-projection.js +0 -350
- package/dist/run-projection.js.map +0 -1
- package/dist/run-status.d.ts +0 -139
- package/dist/run-status.js +0 -218
- package/dist/run-status.js.map +0 -1
- package/dist/run.d.ts +0 -1255
- package/dist/run.js +0 -5947
- package/dist/run.js.map +0 -1
- package/dist/sandbox-receipts.d.ts +0 -19
- package/dist/sandbox-receipts.js +0 -51
- package/dist/sandbox-receipts.js.map +0 -1
- package/dist/screenshot-image.d.ts +0 -11
- package/dist/screenshot-image.js +0 -26
- package/dist/screenshot-image.js.map +0 -1
- package/dist/scripted-browser-actor.d.ts +0 -297
- package/dist/scripted-browser-actor.js +0 -1399
- package/dist/scripted-browser-actor.js.map +0 -1
- package/dist/scripted-browser-lab.d.ts +0 -121
- package/dist/scripted-browser-lab.js +0 -852
- package/dist/scripted-browser-lab.js.map +0 -1
- package/dist/secret-prompt.js +0 -36
- package/dist/secret-prompt.js.map +0 -1
- package/dist/selected-output-paths.d.ts +0 -52
- package/dist/selected-output-paths.js +0 -366
- package/dist/selected-output-paths.js.map +0 -1
- package/dist/serve-exposure.d.ts +0 -62
- package/dist/serve-exposure.js +0 -129
- package/dist/serve-exposure.js.map +0 -1
- package/dist/serve-http.js +0 -45
- package/dist/serve-http.js.map +0 -1
- package/dist/serve-tunnel.js +0 -113
- package/dist/serve-tunnel.js.map +0 -1
- package/dist/shared-world-lab.d.ts +0 -269
- package/dist/shared-world-lab.js +0 -1422
- package/dist/shared-world-lab.js.map +0 -1
- package/dist/source-archive.d.ts +0 -82
- package/dist/source-archive.js +0 -408
- package/dist/source-archive.js.map +0 -1
- package/dist/stats.d.ts +0 -82
- package/dist/stats.js +0 -219
- package/dist/stats.js.map +0 -1
- package/dist/stop-conditions.d.ts +0 -39
- package/dist/stop-conditions.js +0 -66
- package/dist/stop-conditions.js.map +0 -1
- package/dist/study-analysis-codex-config.d.ts +0 -11
- package/dist/study-analysis-codex-config.js +0 -34
- package/dist/study-analysis-codex-config.js.map +0 -1
- package/dist/study-analysis-engine.d.ts +0 -47
- package/dist/study-analysis-engine.js +0 -327
- package/dist/study-analysis-engine.js.map +0 -1
- package/dist/study-analysis-evidence.d.ts +0 -27
- package/dist/study-analysis-evidence.js +0 -660
- package/dist/study-analysis-evidence.js.map +0 -1
- package/dist/study-analysis-job.d.ts +0 -97
- package/dist/study-analysis-job.js +0 -214
- package/dist/study-analysis-job.js.map +0 -1
- package/dist/study-analysis-provider.d.ts +0 -40
- package/dist/study-analysis-provider.js +0 -170
- package/dist/study-analysis-provider.js.map +0 -1
- package/dist/study-analysis-service.d.ts +0 -59
- package/dist/study-analysis-service.js +0 -257
- package/dist/study-analysis-service.js.map +0 -1
- package/dist/study-analysis-sharing.d.ts +0 -11
- package/dist/study-analysis-sharing.js +0 -35
- package/dist/study-analysis-sharing.js.map +0 -1
- package/dist/study-analysis-store.d.ts +0 -47
- package/dist/study-analysis-store.js +0 -493
- package/dist/study-analysis-store.js.map +0 -1
- package/dist/study-analysis-validation.d.ts +0 -618
- package/dist/study-analysis-validation.js +0 -384
- package/dist/study-analysis-validation.js.map +0 -1
- package/dist/study-analysis.d.ts +0 -206
- package/dist/study-analysis.js +0 -4
- package/dist/study-analysis.js.map +0 -1
- package/dist/study-costs.js +0 -114
- package/dist/study-costs.js.map +0 -1
- package/dist/subject-runtime.d.ts +0 -23
- package/dist/subject-runtime.js +0 -70
- package/dist/subject-runtime.js.map +0 -1
- package/dist/tasks.d.ts +0 -90
- package/dist/tasks.js +0 -144
- package/dist/tasks.js.map +0 -1
- package/dist/telemetry.js +0 -324
- package/dist/telemetry.js.map +0 -1
- package/dist/terminal-agent-actor.d.ts +0 -41
- package/dist/terminal-agent-actor.js +0 -25
- package/dist/terminal-agent-actor.js.map +0 -1
- package/dist/terminal-encoding.js +0 -80
- package/dist/terminal-encoding.js.map +0 -1
- package/dist/terminal-node-bootstrap.d.ts +0 -4
- package/dist/terminal-node-bootstrap.js +0 -58
- package/dist/terminal-node-bootstrap.js.map +0 -1
- package/dist/terminal-participant-activity.d.ts +0 -6
- package/dist/terminal-participant-activity.js +0 -34
- package/dist/terminal-participant-activity.js.map +0 -1
- package/dist/terminal-runtime-auth.d.ts +0 -13
- package/dist/terminal-runtime-auth.js +0 -24
- package/dist/terminal-runtime-auth.js.map +0 -1
- package/dist/terminal-runtime.d.ts +0 -16
- package/dist/terminal-runtime.js +0 -42
- package/dist/terminal-runtime.js.map +0 -1
- package/dist/terminal-token-usage.d.ts +0 -15
- package/dist/terminal-token-usage.js +0 -94
- package/dist/terminal-token-usage.js.map +0 -1
- package/dist/tui-actions.d.ts +0 -39
- package/dist/tui-actions.js +0 -178
- package/dist/tui-actions.js.map +0 -1
- package/dist/tui-contract.d.ts +0 -141
- package/dist/tui-contract.js +0 -54
- package/dist/tui-contract.js.map +0 -1
- package/dist/tui-launch.js +0 -167
- package/dist/tui-launch.js.map +0 -1
- package/dist/tui-project.d.ts +0 -9
- package/dist/tui-project.js +0 -18
- package/dist/tui-project.js.map +0 -1
- package/docs/architecture/examples/state-driven-local-app/README.md +0 -80
- package/docs/architecture/examples/state-driven-local-app/app.mjs +0 -48
- package/docs/architecture/examples/state-driven-local-app/runner.mjs +0 -105
- package/docs/architecture/local-codex-tui-actor.md +0 -213
- package/docs/architecture/oss-lab-poc.md +0 -250
- package/docs/architecture/runtime-broker-core.md +0 -30
- package/docs/release/0.100.0-local-setup.md +0 -31
- package/docs/release/0.100.1-participant-continuity.md +0 -20
- package/docs/release/0.101.0-codex-ui-tools.md +0 -28
- package/docs/release/0.102.0-participant-media.md +0 -43
- package/docs/release/0.103.0-desktop-recording.md +0 -29
- package/docs/release/0.103.1-analysis-capture-allocation.md +0 -18
- package/docs/release/0.103.2-analysis-ending-context.md +0 -21
- package/docs/release/0.104.0-local-captured-inboxes.md +0 -29
- package/docs/release/0.104.1-recording-startup.md +0 -24
- package/docs/release/0.104.2-recording-timestamps.md +0 -32
- package/docs/release/0.104.3-local-smtp-catch.md +0 -15
- package/docs/release/0.20.0-external-public-shared-world.md +0 -43
- package/docs/release/0.84.0-redacted-evidence.md +0 -42
- package/docs/release/0.84.1-portable-feedback.md +0 -20
- package/docs/release/0.85.0-observer-review.md +0 -56
- package/docs/release/0.85.1-observer-continuity.md +0 -20
- package/docs/release/0.86.0-participant-evidence.md +0 -50
- package/docs/release/0.86.1-task-preflight-saved-recordings.md +0 -40
- package/docs/release/0.87.0-participant-endings-and-phone-review.md +0 -64
- package/docs/release/0.88.0-study-diagnostics.md +0 -43
- package/docs/release/0.88.1-completion-evidence-and-local-app.md +0 -79
- package/docs/release/0.88.2-sequential-study-budgets.md +0 -57
- package/docs/release/0.89.0-study-findings.md +0 -79
- package/docs/release/0.89.1-analysis-finished-notice.md +0 -24
- package/docs/release/0.90.0-automatic-analysis.md +0 -21
- package/docs/release/0.91.0-analysis-quality-and-defaults.md +0 -33
- package/docs/release/0.91.1-study-review-polish.md +0 -28
- package/docs/release/0.92.0-grid-playback.md +0 -22
- package/docs/release/0.93.0-global-playback.md +0 -26
- package/docs/release/0.93.1-observer-review-controls.md +0 -24
- package/docs/release/0.94.0-reliability.md +0 -48
- package/docs/release/0.95.0-connections-setup.md +0 -38
- package/docs/release/0.96.0-real-email-receiving.md +0 -47
- package/docs/release/0.96.1-browser-navigation.md +0 -22
- package/docs/release/0.97.0-codex-account-analysis.md +0 -45
- package/docs/release/0.98.0-local-browser-studies.md +0 -31
- package/docs/release/0.99.0-local-browser-mac.md +0 -34
- package/docs/release/0.99.1-action-recovery.md +0 -19
- /package/dist/{agent-session.d.ts → actors/agent-session.d.ts} +0 -0
- /package/dist/{cua-executor-error.d.ts → actors/computer-use/executor-error.d.ts} +0 -0
- /package/dist/{cua-speech.d.ts → actors/computer-use/speech.d.ts} +0 -0
- /package/dist/{actor-goal-source.d.ts → actors/goal-source.d.ts} +0 -0
- /package/dist/{output-token-limit.d.ts → actors/output-token-limit.d.ts} +0 -0
- /package/dist/{reasoning-effort.d.ts → actors/reasoning-effort.d.ts} +0 -0
- /package/dist/{secret-prompt.d.ts → cli/secret-prompt.d.ts} +0 -0
- /package/dist/{telemetry.d.ts → cli/telemetry.d.ts} +0 -0
- /package/dist/{browser-evidence-hygiene.d.ts → evidence/browser-hygiene.d.ts} +0 -0
- /package/dist/{serve-http.d.ts → observer/http.d.ts} +0 -0
- /package/dist/{observer-library.d.ts → observer/library.d.ts} +0 -0
- /package/dist/{listen.d.ts → observer/listen.d.ts} +0 -0
- /package/dist/{observer-static.d.ts → observer/static.d.ts} +0 -0
- /package/dist/{serve-tunnel.d.ts → observer/tunnel.d.ts} +0 -0
- /package/dist/{terminal-encoding.d.ts → routes/terminal/encoding.d.ts} +0 -0
- /package/dist/{concurrency.d.ts → run/concurrency.d.ts} +0 -0
- /package/dist/{study-costs.d.ts → run/costs.d.ts} +0 -0
- /package/dist/{run-narration-secrets.d.ts → run/transient-comms-secrets.d.ts} +0 -0
- /package/dist/{tui-launch.d.ts → tui/launch.d.ts} +0 -0
package/dist/cua-actor-lab.js
DELETED
|
@@ -1,3545 +0,0 @@
|
|
|
1
|
-
import { e2bDesktopTemplate } from "./e2b-desktop-media.js";
|
|
2
|
-
export { inboxRecipientFor, laneHasInboxRecipient } from "./cua-desktop-lane.js";
|
|
3
|
-
export { CUA_ACTOR_LAB_PROVIDER_METADATA, DEFAULT_MOBILE_USER_AGENT, SANDBOX_CAMERA_PATH, SANDBOX_MEDIA_DIR, SUBJECT_DIR, SYNTHETIC_CAMERA_COMMAND, applyMobileEmulation, buildFillDesktopWindowCommand, captureDesktopBrowserGeometry, commandDigestOf, declaredScreenForRender, desktopBrowserFamily, inspectDesktopScreenGeometry, makeChromeBrowserStateObserver, makeChromeDesktopGeometryObserver, parseXwininfoGeometry, prepareDesktopMedia, provisionCloneSubject, provisionLocalTreeSubject } from "./e2b-cua-provisioning.js";
|
|
4
|
-
import { prepareReceivingRun, receivingPublication } from "./comms-receiving-runtime.js";
|
|
5
|
-
import { laneHasInboxRecipient } from "./cua-desktop-lane.js";
|
|
6
|
-
import { createE2BCuaDesktopLane } from "./e2b-cua-desktop.js";
|
|
7
|
-
import { isLocalBrowserLab, LOCAL_BROWSER_LIFETIME_MS } from "./local-runtime-config.js";
|
|
8
|
-
import { DEFAULT_STATE_STEP_TIMEOUT_MS, commandDigestOf, declaredScreenForRender } from "./e2b-cua-provisioning.js";
|
|
9
|
-
import { receivingEmailValidationReason } from "./lab-config.js";
|
|
10
|
-
import { withTransientCommsSecrets } from "./run-narration-secrets.js";
|
|
11
|
-
// The computer-use lab backend: a subject (an app-url the caller provisioned, or a repo the
|
|
12
|
-
// lab clones AND serves in-sandbox) driven by a REGISTRY-RESOLVED computer-use actor inside a
|
|
13
|
-
// hosted E2B desktop. This is the path that makes `actors[].type` load-bearing — the
|
|
14
|
-
// descriptor returned by the registry runs the session; the lab provisions the desktop and
|
|
15
|
-
// subject, composes the prompt from config, persists the evidence bundle, and tears down.
|
|
16
|
-
//
|
|
17
|
-
// Substrate notes:
|
|
18
|
-
// - The desktop is created via the shared loader in e2b-desktop-launch.ts with kill-on-timeout
|
|
19
|
-
// lifecycle, so a dead host process can never orphan a sandbox past its server-side deadline.
|
|
20
|
-
// - Env placement follows the doctrine (docs/principles/invariants-and-defaults.md): the
|
|
21
|
-
// ACTOR's key never enters the sandbox (the model drives from outside via the provider API);
|
|
22
|
-
// the SUBJECT's declared env NAMES are provisioned in on the clone route — values come from
|
|
23
|
-
// the caller's environment and are never logged or persisted.
|
|
24
|
-
// - The live stream URL is runtime-only (carries an auth key) and is never persisted into run
|
|
25
|
-
// artifacts — only its presence is recorded, mirroring the meta lab's convention.
|
|
26
|
-
// - Evidence redaction is mode-aware (docs/principles/invariants-and-defaults.md, the
|
|
27
|
-
// capture-vs-publish rule): screenshots persist RAW (full fidelity) by default into gitignored
|
|
28
|
-
// .humanish/; `policies.redactScreenshots: true` opts into blur-at-capture for a share-as-is
|
|
29
|
-
// bundle. Length-only typed text and text redaction of reasoning/messages are UNCONDITIONAL;
|
|
30
|
-
// harness errors are redacted at THIS boundary; the bundle's `stream.actor` carries the
|
|
31
|
-
// conformant humanish.actor-trace.v1 projection, whose `redaction.screenshots` records the
|
|
32
|
-
// run's actual mode ("raw" | "blurred" | "n/a") — every label downstream derives from it.
|
|
33
|
-
import { randomBytes } from "node:crypto";
|
|
34
|
-
import { readFile, realpath, rm } from "node:fs/promises";
|
|
35
|
-
import path from "node:path";
|
|
36
|
-
import { completeAutomaticAnalysis, markFinalizedStudyResult } from "./automatic-analysis-completion.js";
|
|
37
|
-
import { resolveAutomaticAnalysis } from "./automatic-analysis-config.js";
|
|
38
|
-
import { describeMissingKeys } from "./key-resolution.js";
|
|
39
|
-
import { desktopMediaValidationReason, taskProtocolValidationReason } from "./lab-config.js";
|
|
40
|
-
import { pathToFileURL } from "node:url";
|
|
41
|
-
import { toErrorMessage } from "./command-failure.js";
|
|
42
|
-
import { cuaLaneDiagnostics, summarizeCuaDiagnostics } from "./cua-diagnostics.js";
|
|
43
|
-
import { feedbackProofCommands } from "./feedback-proof.js";
|
|
44
|
-
import { actorRegistry, isCuaActorDescriptor } from "./actor-registry.js";
|
|
45
|
-
import { actorEnding } from "./actor-stop-cause.js";
|
|
46
|
-
import { adapterScoreFailureMessage, applyBrowserAdapterHooks } from "./adapter-extension.js";
|
|
47
|
-
import { FakeInbox } from "./comms-fake-inbox.js";
|
|
48
|
-
import { recipientInboxUrl } from "./comms-inbox.js";
|
|
49
|
-
import { collectExternalCommsThread, externalCatchHealthy, externalInboxUrl } from "./comms-sandbox-catch.js";
|
|
50
|
-
import { mapWithConcurrency } from "./concurrency.js";
|
|
51
|
-
import { DEFAULT_DEVICE_PRESET, isDevicePresetName, resolveDevicePreset } from "./device-presets.js";
|
|
52
|
-
import {} from "./e2b-desktop-launch.js";
|
|
53
|
-
import {} from "./e2b-desktop-resources.js";
|
|
54
|
-
import {} from "./e2b-detached.js";
|
|
55
|
-
import { assertScreenshotEvidence } from "./image-evidence.js";
|
|
56
|
-
import { MAX_CUA_LANES, cuaLaneValidationReason, isHttpUrl, isLoopbackUrl, outputTokenLimitValidationReason, subjectStateInvalidReason } from "./lab-config.js";
|
|
57
|
-
import { startClaudeSession } from "./local-agent-claude-session.js";
|
|
58
|
-
import { checkHostedCodexCompatibility, createLocalAgentProvider, detectLocalAgents } from "./local-agent-cli.js";
|
|
59
|
-
import { buildObserverData } from "./observer-data.js";
|
|
60
|
-
import { attachObserverRuntimeStreamUrls, renderObserver } from "./observer.js";
|
|
61
|
-
import { DEFAULT_OPENAI_CU_MODEL } from "./openai-responses-cu.js";
|
|
62
|
-
import { participantAssignment } from "./participant-assignment.js";
|
|
63
|
-
import { labPersonaIds, resolveCommittedPersonas } from "./persona-resolve.js";
|
|
64
|
-
import { personaToDirectives, renderPersonaPromptSection } from "./persona.js";
|
|
65
|
-
import { MODEL_RATES, estimateActorCostForExecution, estimateAllocatedDesktopCost, estimateDesktopCost, round6 } from "./pricing.js";
|
|
66
|
-
import { containsSensitive, digestText, redactText } from "./redaction.js";
|
|
67
|
-
import { createRestrictedCodexParticipant, } from "./restricted-codex-participant.js";
|
|
68
|
-
import { prepareRunArtifactPaths, validatePreparedRunArtifactPaths } from "./run-paths.js";
|
|
69
|
-
import { beginRunStatus, withRunStatusScope } from "./run-status.js";
|
|
70
|
-
import { PUBLIC_TARGET_CWD, REVIEW_SCHEMA, RUN_BUNDLE_SCHEMA, aggregateTaskFunnels, buildRunSource, formatParticipantOutcomes, formatStudyTaskFunnel, loadRunBundle, tallyParticipantOutcomes, withCuaReviewProvenance } from "./run.js";
|
|
71
|
-
import { assertPreparedSelectedOutputDirectory, assertSafeOutputPathSegment, prepareContainedOutputDirectory, prepareSelectedOutputDirectory, writeContainedOutputFile, writePreparedRunLatestPointer } from "./selected-output-paths.js";
|
|
72
|
-
import { createLocalTreeArchive } from "./source-archive.js";
|
|
73
|
-
import { renderTaskPrompt } from "./tasks.js";
|
|
74
|
-
export const CUA_ACTOR_LAB_SCHEMA = "humanish.cua-lab-result.v2";
|
|
75
|
-
// The only fan-out topology this slice ships: N lanes = N independent E2B desktop sandboxes,
|
|
76
|
-
// each its own world (clone/serve + subject.state per lane). Shared-world is layer 7 (#164).
|
|
77
|
-
export const CUA_FANOUT_STRATEGY = "per-lane-worlds";
|
|
78
|
-
// Env override that may only LOWER the effective concurrency (never raise concurrent paid
|
|
79
|
-
// desktops — invariant 3). Read names-only into a local; the value never persists.
|
|
80
|
-
const CUA_MAX_CONCURRENCY_ENV = "HUMANISH_CUA_MAX_CONCURRENCY";
|
|
81
|
-
// The DEFAULT session budget, sized so a study can FINISH (docs/principles/three-roles.md: a
|
|
82
|
-
// session ends because the participant is done, not because a timer fired — the time-box is a
|
|
83
|
-
// session-level cap a researcher sets generously; spend protection is the dollar caps' job).
|
|
84
|
-
// The old 300s default ended real signup studies mid-flow: observed studies run 16-40 turns at
|
|
85
|
-
// ~5-6s per turn BEFORE any email wait, so five minutes was the biggest single source of
|
|
86
|
-
// budget_reached endings that read as participant failures.
|
|
87
|
-
//
|
|
88
|
-
// App-url and in-process routes default to 30 minutes. Provisioned routes (clone/local-tree)
|
|
89
|
-
// default to whatever the 1-hour sandbox cap leaves after provisioning, declared state seeding,
|
|
90
|
-
// and the teardown buffer — 20 minutes on a stateless clone — floored at the old five minutes so
|
|
91
|
-
// a state-heavy lab still gets a session at all. An EXPLICIT execution.timeoutMs is never
|
|
92
|
-
// adjusted: when it cannot be provisioned, the plan-time cap refusal shows the arithmetic.
|
|
93
|
-
const DEFAULT_APP_URL_SESSION_TIMEOUT_MS = 30 * 60_000;
|
|
94
|
-
const MIN_DERIVED_SESSION_TIMEOUT_MS = 5 * 60_000;
|
|
95
|
-
function defaultSessionTimeoutMs(config) {
|
|
96
|
-
const provisionedRoute = config.subject.source === "clone" || config.subject.source === "local-tree";
|
|
97
|
-
if (!provisionedRoute)
|
|
98
|
-
return DEFAULT_APP_URL_SESSION_TIMEOUT_MS;
|
|
99
|
-
const stateBudgetMs = (config.subject.state?.seed ?? []).reduce((sum, step) => sum + (step.timeoutMs ?? DEFAULT_STATE_STEP_TIMEOUT_MS), 0);
|
|
100
|
-
const room = MAX_SANDBOX_MS - SUBJECT_PROVISION_BUDGET_MS - stateBudgetMs - SANDBOX_TIMEOUT_BUFFER_MS;
|
|
101
|
-
return Math.max(MIN_DERIVED_SESSION_TIMEOUT_MS, Math.min(DEFAULT_APP_URL_SESSION_TIMEOUT_MS, room));
|
|
102
|
-
}
|
|
103
|
-
// Device/screen size comes from the named-preset registry (device-presets.ts), selectable per run
|
|
104
|
-
// via execution.desktop.device (default `desktop`=1440x950). NOTE: this is run-wide for now; a
|
|
105
|
-
// per-PERSONA device dimension (N personas × devices, as the bespoke sims author) lands with
|
|
106
|
-
// fan-out. On this E2B-desktop route only width/height physically render — isMobile/DSF are
|
|
107
|
-
// honest metadata + a prompt signal, not rendered (device-presets.ts FIDELITY NOTE) — and the
|
|
108
|
-
// rendered WIDTH is floored to MIN_DESKTOP_RENDER_WIDTH (Chrome's ~500px window minimum) so a mobile
|
|
109
|
-
// screen the browser can't shrink to does not overflow + clip (see resolveLaneDevice / #221).
|
|
110
|
-
// Server-side reclamation buffer past the loop's own wall-clock stop.
|
|
111
|
-
const SANDBOX_TIMEOUT_BUFFER_MS = 10 * 60_000;
|
|
112
|
-
// Room the clone route adds to the sandbox deadline for clone/install/build/start/probe.
|
|
113
|
-
const SUBJECT_PROVISION_BUDGET_MS = 30 * 60_000;
|
|
114
|
-
/** E2B refuses a sandbox lifetime over one hour ("400: Timeout cannot be greater than 1 hours").
|
|
115
|
-
* The derived per-lane deadline has to stay under it, and saying so at plan time beats discovering
|
|
116
|
-
* it from a raw provider 400 after a plan has already printed. */
|
|
117
|
-
const MAX_SANDBOX_MS = 60 * 60_000;
|
|
118
|
-
const DEFAULT_MISSION = "You are testing a web application. The browser is already open at the subject URL. Explore it, accomplish what the scenario asks, and stop when done.";
|
|
119
|
-
/**
|
|
120
|
-
* The participant's outcome as ONE fixed first line of its last message (#570, second half). The
|
|
121
|
-
* free-text computer-use provider has no schema to fill; a fixed line is the next best thing, and
|
|
122
|
-
* the loop reads it into the trace's declaredOutcome. Prompt-only control is weak in general, so
|
|
123
|
-
* adherence is measured (declaredOutcome present or absent on the trace) and the regex over the
|
|
124
|
-
* paragraph stays as the fallback when the line is missing. This is a report format, deliberately
|
|
125
|
-
* not a behavioural instruction: it says how to label the ending, never how to act.
|
|
126
|
-
*/
|
|
127
|
-
export const CLOSING_LINE_DIRECTIVE = "When you stop, make the FIRST line of your last message exactly one of these three, on its own line: "
|
|
128
|
-
+ "REACHED THE GOAL. / DID NOT REACH THE GOAL. / BLOCKED. "
|
|
129
|
-
+ "Then, from the next line, say what you did, what confused you, and where you hesitated.";
|
|
130
|
-
/** Compose one lane's actor prompt: persona line + device line + mission + per-lane steer.
|
|
131
|
-
* At N=1 (homogeneous, no roster) this reproduces the prior composeInstructions byte-for-byte. */
|
|
132
|
-
export function composeLaneInstructions(args) {
|
|
133
|
-
const { name, preset } = args.device;
|
|
134
|
-
const deviceLine = preset.isMobile
|
|
135
|
-
? `You are a mobile user on a ${name} device (${preset.width}x${preset.height} @${preset.deviceScaleFactor}x). Expect a mobile/touch layout.`
|
|
136
|
-
: `You are a desktop user (${name}, ${preset.width}x${preset.height}).`;
|
|
137
|
-
// The protocol as the PARTICIPANT reads it: numbered goals, nothing else. The success criteria
|
|
138
|
-
// are the researcher's instrument and must never reach this prompt — a persona told how it will
|
|
139
|
-
// be measured optimizes for the measurement instead of using the product (src/tasks.ts).
|
|
140
|
-
const taskLines = renderTaskPrompt(args.tasks ?? []);
|
|
141
|
-
// A resolved persona contributes its compiled directives (friction tolerance, skill bias,
|
|
142
|
-
// accessibility behavior, constraints) through the SAME persona.ts compiler the terminal lane
|
|
143
|
-
// uses, so one persona file means one behavior across every route.
|
|
144
|
-
const personaLine = args.resolvedPersona
|
|
145
|
-
? renderPersonaPromptSection(args.resolvedPersona)
|
|
146
|
-
: args.persona ? `Persona: ${args.persona}.` : undefined;
|
|
147
|
-
const traitsApplied = args.resolvedPersona ? personaToDirectives(args.resolvedPersona).traitsApplied : [];
|
|
148
|
-
const surfaceLine = args.surface === "desktop-cli"
|
|
149
|
-
? "A terminal window is already open on this desktop, and there is a terminal in the dock at the bottom of the screen if you want another. Everything you need is on this machine; there is no browser task here."
|
|
150
|
-
: undefined;
|
|
151
|
-
const parts = [
|
|
152
|
-
personaLine,
|
|
153
|
-
deviceLine,
|
|
154
|
-
surfaceLine,
|
|
155
|
-
args.mission,
|
|
156
|
-
taskLines,
|
|
157
|
-
args.instruction ? `Lane focus: ${args.instruction}` : undefined,
|
|
158
|
-
CLOSING_LINE_DIRECTIVE
|
|
159
|
-
].filter((part) => Boolean(part));
|
|
160
|
-
const instructions = parts.join("\n\n");
|
|
161
|
-
return {
|
|
162
|
-
instructions,
|
|
163
|
-
persona: {
|
|
164
|
-
id: args.persona ?? "cua-operator",
|
|
165
|
-
traitsApplied,
|
|
166
|
-
promptDigest: digestText(instructions, 16)
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
/** Runtime-inject the persona inbox instruction into a lane's prompt (#297 slice B). The inbox URL is a
|
|
171
|
-
* runtime loopback/getHost address (not secret), so — mirroring the lobby-code runtime injection — this
|
|
172
|
-
* augments only the instructions the model receives; the authored prompt + its digest are unchanged.
|
|
173
|
-
* Returns a new spec (never mutates). Shared by the CUA + concurrent shared-world routes. */
|
|
174
|
-
export function withInboxMission(spec, inboxUrl, address, receiving = false) {
|
|
175
|
-
// No assigned identity means no participant inbox; never fall back to the shared operator view.
|
|
176
|
-
if (!address?.trim())
|
|
177
|
-
return spec;
|
|
178
|
-
// The address is half the handoff (#351): the drain matches captured mail against the DECLARED
|
|
179
|
-
// address, so an actor that invents its own at signup gets an inbox that stays empty forever.
|
|
180
|
-
// Telling it which address to use is what makes the funnel deterministic end to end. The
|
|
181
|
-
// wait-steering sentence exists because a mid-flow model treats "we emailed you" as a blocker
|
|
182
|
-
// and ends its session — the exact give-up class a live run documented — unless told the wait
|
|
183
|
-
// is expected and the inbox is the next step.
|
|
184
|
-
const identity = ` Your email address is ${address} — when the app asks for an email address, enter exactly that.`;
|
|
185
|
-
if (receiving)
|
|
186
|
-
return {
|
|
187
|
-
...spec,
|
|
188
|
-
instructions: `${spec.instructions}\n\nEmail inbox:${identity} This is a fresh test identity; it does not replace an existing account's email address. When the app says it sent email, open ${inboxUrl} to check your inbox. Read the original email and use its verification link or code. Delivery may take a little time; refresh if needed. If mail remains missing or unavailable, report what you observed rather than assuming the app failed to send. The inbox may block remote images or undeclared destinations; those are harness limitations.`
|
|
189
|
-
};
|
|
190
|
-
return {
|
|
191
|
-
...spec,
|
|
192
|
-
instructions: `${spec.instructions}\n\nEmail inbox:${identity} When the app tells you it has emailed you (a verification link, confirmation code, or magic link), open ${recipientInboxUrl(inboxUrl, address)} in the browser to read that email and follow its link or enter its code. All email the app sends you arrives there. Waiting for an email is normal, not a blocker — do not end your session while waiting; open the inbox and refresh it until the email appears.`
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* The narrowest browser WINDOW Chrome/Chromium will render on the E2B desktop. Chrome refuses to
|
|
197
|
-
* make its window narrower than this (~500 CSS px observed: a 414-wide X screen produced a 500-wide
|
|
198
|
-
* window that OVERFLOWED it, clipping the right edge of the page off-screen). So the physically
|
|
199
|
-
* RENDERED screen width is floored here: a sub-500 mobile preset (mobile 414, small-mobile 360,
|
|
200
|
-
* narrow-mobile 320) gets a 500-wide screen the window fits exactly — no clip. The device PRESET keeps
|
|
201
|
-
* its true identity (isMobile, nominal width) for the persona prompt + metadata; only the rendered
|
|
202
|
-
* screen is floored. True sub-500 CSS-viewport rendering (page laid out at 414 regardless of window
|
|
203
|
-
* width, via CDP device-metric emulation) is the separate #221 upgrade.
|
|
204
|
-
*/
|
|
205
|
-
export const MIN_DESKTOP_RENDER_WIDTH = 500;
|
|
206
|
-
/** Floor a screen resolution's WIDTH to what Chrome can actually render (see MIN_DESKTOP_RENDER_WIDTH). */
|
|
207
|
-
export function floorRenderResolution(resolution) {
|
|
208
|
-
return [Math.max(resolution[0], MIN_DESKTOP_RENDER_WIDTH), resolution[1]];
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Resolve a lane's device + rendered resolution (most-specific wins, exactly as the single-lane
|
|
212
|
-
* path always has): a raw execution.desktop.resolution escape hatch (only legal when no lane
|
|
213
|
-
* sets a device — XOR enforced at parse) → the lane's named device → the run-wide
|
|
214
|
-
* execution.desktop.device → the default preset. A raw resolution is an unnamed custom desktop
|
|
215
|
-
* (non-mobile, DSF 1): we never claim a named preset's mobile/DPR for hand-set geometry. The rendered
|
|
216
|
-
* `resolution` is floored to MIN_DESKTOP_RENDER_WIDTH so the browser window fits its X screen (no clip);
|
|
217
|
-
* `preset` keeps the declared device identity (a mobile preset stays 414/isMobile for the prompt).
|
|
218
|
-
*/
|
|
219
|
-
export function resolveLaneDevice(config, lane) {
|
|
220
|
-
const rawResolution = config.execution?.desktop?.resolution;
|
|
221
|
-
if (lane?.device === undefined && rawResolution) {
|
|
222
|
-
const preset = { width: rawResolution[0], height: rawResolution[1], isMobile: false, deviceScaleFactor: 1 };
|
|
223
|
-
return { name: "custom", preset, resolution: floorRenderResolution([rawResolution[0], rawResolution[1]]) };
|
|
224
|
-
}
|
|
225
|
-
const candidate = lane?.device ?? config.execution?.desktop?.device;
|
|
226
|
-
const presetName = isDevicePresetName(candidate) ? candidate : DEFAULT_DEVICE_PRESET;
|
|
227
|
-
const preset = resolveDevicePreset(presetName);
|
|
228
|
-
return { name: presetName, preset, resolution: floorRenderResolution([preset.width, preset.height]) };
|
|
229
|
-
}
|
|
230
|
-
/** Per-lane sandbox deadline (each lane owns its own desktop). Mirrors the single-lane formula
|
|
231
|
-
* verbatim so N=1 stays byte-stable: explicit sandboxTimeoutMs, else session budget + (clone
|
|
232
|
-
* or local-tree: provision budget + Σ state-step budgets) + the server-side
|
|
233
|
-
* reclamation buffer. Local-tree shares the clone route's provisioning budget: it swaps a
|
|
234
|
-
* git clone for an upload+extract, but the shared install/build/state/start/probe pipeline
|
|
235
|
-
* costs the same wall-clock room either way. */
|
|
236
|
-
function resolvePerLaneSandboxMs(config) {
|
|
237
|
-
if (isLocalBrowserLab(config))
|
|
238
|
-
return LOCAL_BROWSER_LIFETIME_MS;
|
|
239
|
-
const timeoutMs = config.execution?.timeoutMs ?? defaultSessionTimeoutMs(config);
|
|
240
|
-
const provisionedRoute = config.subject.source === "clone" || config.subject.source === "local-tree";
|
|
241
|
-
const stateBudgetMs = provisionedRoute
|
|
242
|
-
? (config.subject.state?.seed ?? []).reduce((sum, step) => sum + (step.timeoutMs ?? DEFAULT_STATE_STEP_TIMEOUT_MS), 0)
|
|
243
|
-
: 0;
|
|
244
|
-
return config.execution?.desktop?.sandboxTimeoutMs
|
|
245
|
-
?? timeoutMs + (provisionedRoute ? SUBJECT_PROVISION_BUDGET_MS + stateBudgetMs : 0) + SANDBOX_TIMEOUT_BUFFER_MS;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Effective in-flight lane bound. Defaults to laneCount — every declared seat runs at once,
|
|
249
|
-
* because a throttle nobody asked for silently turns "N actors live" into waves (#350); total
|
|
250
|
-
* session count and spend are the same either way, only wall-clock and simultaneity differ. A
|
|
251
|
-
* declared execution.concurrency is a CAP, clamped to [1, laneCount]; the env override may only
|
|
252
|
-
* LOWER it (never raise concurrent paid desktops — invariant 3), and a lowering is reported via
|
|
253
|
-
* envLoweredFrom so the plan never silently disagrees with the manifest. Pure given
|
|
254
|
-
* (config, laneCount, env).
|
|
255
|
-
*/
|
|
256
|
-
function resolveCuaConcurrency(config, laneCount, env) {
|
|
257
|
-
const declared = config.execution?.concurrency;
|
|
258
|
-
const base = Math.max(1, declared !== undefined ? Math.min(Math.max(1, declared), laneCount) : laneCount);
|
|
259
|
-
const envLower = readPositiveInt(env[CUA_MAX_CONCURRENCY_ENV], 0);
|
|
260
|
-
if (envLower > 0 && envLower < base) {
|
|
261
|
-
return { bound: Math.max(1, Math.min(base, envLower, laneCount)), envLoweredFrom: base };
|
|
262
|
-
}
|
|
263
|
-
return { bound: base };
|
|
264
|
-
}
|
|
265
|
-
/** Build the lane specs AND the public plan from a config (pure). countOverride is the CLI
|
|
266
|
-
* --count for homogeneous fan-out (ignored when a `lanes` roster is declared). */
|
|
267
|
-
function laneSpecsAndPlan(config, opts = {}) {
|
|
268
|
-
const env = opts.env ?? {};
|
|
269
|
-
const actor = config.actors[0];
|
|
270
|
-
const mission = actor?.mission ?? DEFAULT_MISSION;
|
|
271
|
-
const tasks = actor?.tasks;
|
|
272
|
-
const roster = actor?.lanes;
|
|
273
|
-
const laneCount = roster ? roster.length : Math.max(1, opts.countOverride ?? actor?.count ?? 1);
|
|
274
|
-
const lanes = [];
|
|
275
|
-
for (let i = 0; i < laneCount; i += 1) {
|
|
276
|
-
const lane = roster?.[i];
|
|
277
|
-
const laneId = lane?.id ?? `lane-${String(i + 1).padStart(2, "0")}`;
|
|
278
|
-
const simId = `sim-${String(i + 1).padStart(3, "0")}`;
|
|
279
|
-
const streamId = `stream-${String(i + 1).padStart(3, "0")}`;
|
|
280
|
-
const device = resolveLaneDevice(config, lane);
|
|
281
|
-
// A lane's persona FALLS BACK to actors[0].persona, matching this field's own doc comment
|
|
282
|
-
// in src/lab-config.ts and its sibling resolutions (stopWhen, reasoningEffort) two lines
|
|
283
|
-
// below. Reading only lane.persona when a roster was present meant every fan-out lane of
|
|
284
|
-
// every lab that declared actors[0].persona ran with no persona at all: no personaLine in
|
|
285
|
-
// the prompt, traitsApplied [], and nothing warned (#512).
|
|
286
|
-
const personaId = (lane?.persona ?? actor?.persona);
|
|
287
|
-
const resolvedPersona = personaId === undefined ? undefined : opts.personas?.get(personaId);
|
|
288
|
-
const composed = composeLaneInstructions({
|
|
289
|
-
mission,
|
|
290
|
-
...(tasks === undefined ? {} : { tasks }),
|
|
291
|
-
...(personaId === undefined ? {} : { persona: personaId }),
|
|
292
|
-
...(resolvedPersona === undefined ? {} : { resolvedPersona }),
|
|
293
|
-
...(((roster ? lane?.instruction : actor?.laneFocus?.instruction)) === undefined ? {} : { instruction: (roster ? lane?.instruction : actor?.laneFocus?.instruction) }),
|
|
294
|
-
device: { name: device.name, preset: device.preset },
|
|
295
|
-
...(config.subject.source === "desktop-cli" ? { surface: "desktop-cli" } : {})
|
|
296
|
-
});
|
|
297
|
-
lanes.push({
|
|
298
|
-
laneId,
|
|
299
|
-
...(lane?.actorType === undefined ? {} : { actorType: lane.actorType }),
|
|
300
|
-
...(lane?.surface === undefined ? {} : { surface: lane.surface }),
|
|
301
|
-
...(lane?.caseGroup === undefined ? {} : { caseGroup: lane.caseGroup }),
|
|
302
|
-
laneIndex: i,
|
|
303
|
-
simId,
|
|
304
|
-
streamId,
|
|
305
|
-
persona: composed.persona,
|
|
306
|
-
instructions: composed.instructions,
|
|
307
|
-
assignment: {
|
|
308
|
-
mission,
|
|
309
|
-
...((roster ? lane?.instruction : actor?.laneFocus?.instruction) === undefined
|
|
310
|
-
? {} : { focus: (roster ? lane?.instruction : actor?.laneFocus?.instruction) }),
|
|
311
|
-
...(tasks === undefined ? {} : { tasks: tasks.map(({ id, goal }) => ({ id, goal })) })
|
|
312
|
-
},
|
|
313
|
-
...(lane?.target === undefined ? {} : { targetUrl: lane.target }),
|
|
314
|
-
...((lane?.stopWhen ?? actor?.stopWhen) === undefined ? {} : { stopWhen: (lane?.stopWhen ?? actor?.stopWhen) }),
|
|
315
|
-
...((lane?.dwell ?? actor?.dwell) === undefined ? {} : { dwell: (lane?.dwell ?? actor?.dwell) }),
|
|
316
|
-
...((lane?.reasoningEffort ?? actor?.reasoningEffort) === undefined
|
|
317
|
-
? {}
|
|
318
|
-
: { reasoningEffort: (lane?.reasoningEffort ?? actor?.reasoningEffort) }),
|
|
319
|
-
...(actor?.maxOutputTokens === undefined ? {} : { maxOutputTokens: actor.maxOutputTokens }),
|
|
320
|
-
...(tasks === undefined ? {} : { tasks }),
|
|
321
|
-
deviceName: device.name,
|
|
322
|
-
devicePreset: device.preset,
|
|
323
|
-
resolution: device.resolution,
|
|
324
|
-
screenshotDir: laneCount === 1 ? "" : laneId,
|
|
325
|
-
traceArtifactPath: laneCount === 1 ? "actor.json" : `actors/${streamId}.json`
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
const resolved = resolveCuaConcurrency(config, laneCount, env);
|
|
329
|
-
const concurrency = resolved.bound;
|
|
330
|
-
const perLaneSessionBudgetMs = config.execution?.timeoutMs ?? defaultSessionTimeoutMs(config);
|
|
331
|
-
const perLaneSandboxMs = resolvePerLaneSandboxMs(config);
|
|
332
|
-
const plan = {
|
|
333
|
-
strategy: CUA_FANOUT_STRATEGY,
|
|
334
|
-
laneCount,
|
|
335
|
-
concurrency,
|
|
336
|
-
...(resolved.envLoweredFrom === undefined ? {} : { envLoweredConcurrencyFrom: resolved.envLoweredFrom }),
|
|
337
|
-
waves: Math.ceil(laneCount / concurrency),
|
|
338
|
-
perLaneSessionBudgetMs,
|
|
339
|
-
worstCaseSandboxMinutes: Math.round((laneCount * perLaneSandboxMs) / 60_000),
|
|
340
|
-
dryRun: opts.dryRun === true,
|
|
341
|
-
lanes: lanes.map((spec) => ({
|
|
342
|
-
id: spec.laneId,
|
|
343
|
-
...(spec.actorType === undefined ? {} : { actorType: spec.actorType }),
|
|
344
|
-
...(spec.surface === undefined ? {} : { surface: spec.surface }),
|
|
345
|
-
...(spec.caseGroup === undefined ? {} : { caseGroup: spec.caseGroup }),
|
|
346
|
-
index: spec.laneIndex + 1,
|
|
347
|
-
persona: spec.persona.id,
|
|
348
|
-
device: spec.deviceName,
|
|
349
|
-
resolution: spec.resolution,
|
|
350
|
-
instructionDigest: spec.persona.promptDigest,
|
|
351
|
-
...(spec.reasoningEffort === undefined ? {} : { reasoningEffort: spec.reasoningEffort }),
|
|
352
|
-
...(spec.maxOutputTokens === undefined ? {} : { maxOutputTokens: spec.maxOutputTokens }),
|
|
353
|
-
...(spec.targetUrl === undefined ? {} : { targetDigest: digestUrl(spec.targetUrl) })
|
|
354
|
-
}))
|
|
355
|
-
};
|
|
356
|
-
return { lanes, plan };
|
|
357
|
-
}
|
|
358
|
-
async function resolveCuaRerunSelection(args) {
|
|
359
|
-
const source = await loadRunBundle(args.cwd, args.sourceRunId);
|
|
360
|
-
if (!source) {
|
|
361
|
-
return { ok: false, message: `source run not found or invalid: ${args.sourceRunId}` };
|
|
362
|
-
}
|
|
363
|
-
const bundle = source.bundle;
|
|
364
|
-
if (bundle.mode !== "live") {
|
|
365
|
-
return { ok: false, message: `source run ${bundle.runId} is ${bundle.mode}; rerun selection only applies to live CUA fan-out evidence.` };
|
|
366
|
-
}
|
|
367
|
-
const fanoutEvent = bundle.events.some((event) => event.type === "cua-lab.fanout.plan");
|
|
368
|
-
if (!fanoutEvent || bundle.streams.length < 2) {
|
|
369
|
-
return { ok: false, message: `source run ${bundle.runId} is not a CUA fan-out run.` };
|
|
370
|
-
}
|
|
371
|
-
const prior = bundle.streams
|
|
372
|
-
.map(snapshotPriorCuaLane)
|
|
373
|
-
.filter((lane) => lane !== null);
|
|
374
|
-
const priorById = new Map(prior.map((lane) => [lane.laneId, lane]));
|
|
375
|
-
if (priorById.size < 2) {
|
|
376
|
-
return { ok: false, message: `source run ${bundle.runId} does not expose multiple lane ids.` };
|
|
377
|
-
}
|
|
378
|
-
const explicitLaneIds = uniqueLaneIds(args.laneIds ?? []);
|
|
379
|
-
const selectedLaneIds = explicitLaneIds.length > 0
|
|
380
|
-
? explicitLaneIds
|
|
381
|
-
: prior.filter((lane) => lane.rerunnable).map((lane) => lane.laneId);
|
|
382
|
-
if (selectedLaneIds.length === 0) {
|
|
383
|
-
return { ok: false, message: `source run ${bundle.runId} has no failed, blocked, timed-out, or hollow lanes to rerun.` };
|
|
384
|
-
}
|
|
385
|
-
const missingPrior = selectedLaneIds.filter((laneId) => !priorById.has(laneId));
|
|
386
|
-
if (missingPrior.length > 0) {
|
|
387
|
-
return { ok: false, message: `selected lane id(s) were not present in source run ${bundle.runId}: ${missingPrior.join(", ")}` };
|
|
388
|
-
}
|
|
389
|
-
const specsById = new Map(args.laneSpecs.map((spec) => [spec.laneId, spec]));
|
|
390
|
-
const missingCurrent = selectedLaneIds.filter((laneId) => !specsById.has(laneId));
|
|
391
|
-
if (missingCurrent.length > 0) {
|
|
392
|
-
return { ok: false, message: `selected lane id(s) are not present in the current lab config ${args.config.id}: ${missingCurrent.join(", ")}` };
|
|
393
|
-
}
|
|
394
|
-
const selectedSpecs = selectedLaneIds.map((laneId) => specsById.get(laneId));
|
|
395
|
-
const selectedPlanLaneIds = new Set(selectedLaneIds);
|
|
396
|
-
const selectedPlanEntries = args.plan.lanes.filter((lane) => selectedPlanLaneIds.has(lane.id));
|
|
397
|
-
const concurrency = Math.max(1, Math.min(args.plan.concurrency, selectedSpecs.length));
|
|
398
|
-
const plan = {
|
|
399
|
-
...args.plan,
|
|
400
|
-
laneCount: selectedSpecs.length,
|
|
401
|
-
concurrency,
|
|
402
|
-
waves: Math.ceil(selectedSpecs.length / concurrency),
|
|
403
|
-
worstCaseSandboxMinutes: Math.round((selectedSpecs.length * resolvePerLaneSandboxMs(args.config)) / 60_000),
|
|
404
|
-
lanes: selectedPlanEntries
|
|
405
|
-
};
|
|
406
|
-
const previous = selectedLaneIds.map((laneId) => priorById.get(laneId).previous);
|
|
407
|
-
return {
|
|
408
|
-
ok: true,
|
|
409
|
-
laneSpecs: selectedSpecs,
|
|
410
|
-
plan,
|
|
411
|
-
rerun: {
|
|
412
|
-
sourceRunId: bundle.runId,
|
|
413
|
-
selectedLaneIds,
|
|
414
|
-
previous
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
function uniqueLaneIds(values) {
|
|
419
|
-
const seen = new Set();
|
|
420
|
-
const result = [];
|
|
421
|
-
for (const value of values) {
|
|
422
|
-
const laneId = value.trim();
|
|
423
|
-
if (!laneId || seen.has(laneId))
|
|
424
|
-
continue;
|
|
425
|
-
seen.add(laneId);
|
|
426
|
-
result.push(laneId);
|
|
427
|
-
}
|
|
428
|
-
return result;
|
|
429
|
-
}
|
|
430
|
-
function snapshotPriorCuaLane(stream) {
|
|
431
|
-
if (stream.kind !== "browser" || typeof stream.laneId !== "string" || !stream.laneId.trim()) {
|
|
432
|
-
return null;
|
|
433
|
-
}
|
|
434
|
-
const actorStatus = stream.actor?.status;
|
|
435
|
-
const completionReason = stream.actor?.completionReason;
|
|
436
|
-
const reason = stream.ui?.state ?? stream.actor?.reason;
|
|
437
|
-
const actions = stream.actor?.counts.actions ?? 0;
|
|
438
|
-
const messages = stream.actor?.counts.messages ?? 0;
|
|
439
|
-
const hollow = completionReason === "goal_satisfied" && actions === 0 && messages === 0;
|
|
440
|
-
const rerunnable = stream.status !== "passed"
|
|
441
|
-
|| actorStatus === "failed"
|
|
442
|
-
|| actorStatus === "blocked"
|
|
443
|
-
|| actorStatus === "timed_out"
|
|
444
|
-
|| completionReason === "harness_error"
|
|
445
|
-
|| hollow;
|
|
446
|
-
return {
|
|
447
|
-
laneId: stream.laneId,
|
|
448
|
-
previous: {
|
|
449
|
-
laneId: stream.laneId,
|
|
450
|
-
streamId: stream.id,
|
|
451
|
-
status: stream.status,
|
|
452
|
-
...(reason === undefined ? {} : { reason }),
|
|
453
|
-
...(actorStatus === undefined ? {} : { actorStatus }),
|
|
454
|
-
...(completionReason === undefined ? {} : { completionReason })
|
|
455
|
-
},
|
|
456
|
-
rerunnable
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Pure pre-flight plan resolver (runs in dry-run AND live). Returns the lane table, the
|
|
461
|
-
* effective concurrency, the wave count, the per-lane session budget, and the worst-case total
|
|
462
|
-
* sandbox-minutes — BEFORE any sandbox or provider call. The same plan appears in dry-run,
|
|
463
|
-
* marked $0 (dryRun: true).
|
|
464
|
-
*/
|
|
465
|
-
export function resolveCuaLanePlan(config, opts = {}) {
|
|
466
|
-
return laneSpecsAndPlan(config, opts).plan;
|
|
467
|
-
}
|
|
468
|
-
/** Print the lane plan to stderr BEFORE any sandbox/provider call (public-safe: ids, devices,
|
|
469
|
-
* digests, and budgets only — no prompt text, no secrets). */
|
|
470
|
-
function emitPreflightPlan(plan, labId) {
|
|
471
|
-
const lines = [];
|
|
472
|
-
lines.push(`humanish cua fan-out plan (${labId}): ${plan.laneCount} lane(s), strategy ${plan.strategy}, concurrency ${plan.concurrency}${plan.envLoweredConcurrencyFrom === undefined ? "" : ` (lowered from ${plan.envLoweredConcurrencyFrom} by ${CUA_MAX_CONCURRENCY_ENV})`}, ${plan.waves} wave(s).`);
|
|
473
|
-
lines.push(` per-lane session budget ${Math.round(plan.perLaneSessionBudgetMs / 1000)}s; worst-case ~${plan.worstCaseSandboxMinutes} sandbox-minutes total${plan.dryRun ? " (dry-run: $0)" : ""}.`);
|
|
474
|
-
for (const lane of plan.lanes) {
|
|
475
|
-
lines.push(` - ${formatLanePlanEntry(lane)}`);
|
|
476
|
-
}
|
|
477
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
478
|
-
}
|
|
479
|
-
function formatLanePlanEntry(lane) {
|
|
480
|
-
const taxonomy = [
|
|
481
|
-
lane.actorType ? `type=${lane.actorType}` : undefined,
|
|
482
|
-
lane.surface ? `surface=${lane.surface}` : undefined,
|
|
483
|
-
lane.caseGroup ? `case=${lane.caseGroup}` : undefined,
|
|
484
|
-
lane.reasoningEffort ? `effort=${lane.reasoningEffort}` : undefined
|
|
485
|
-
].filter((part) => part !== undefined);
|
|
486
|
-
return `${lane.id}: persona=${lane.persona}${taxonomy.length > 0 ? ` ${taxonomy.join(" ")}` : ""} device=${lane.device} ${lane.resolution[0]}x${lane.resolution[1]} prompt#${lane.instructionDigest}${lane.targetDigest ? ` target#${lane.targetDigest}` : ""}`;
|
|
487
|
-
}
|
|
488
|
-
/** Short id-safe suffix for a subject-phase RunEvent: drops the shared prefix/suffix so each
|
|
489
|
-
* phase gets a distinct bundle event id (e.g. "clone", "state-before-build"). */
|
|
490
|
-
function phaseEventIdSuffix(type) {
|
|
491
|
-
return type
|
|
492
|
-
.replace(/^cua-lab\.subject\./, "")
|
|
493
|
-
.replace(/\.(started|completed)$/, "")
|
|
494
|
-
.replace(/\./g, "-");
|
|
495
|
-
}
|
|
496
|
-
export function makeCuaRunBudget(maxTotalUsd) {
|
|
497
|
-
const laneEstimates = new Map();
|
|
498
|
-
return {
|
|
499
|
-
maxTotalUsd,
|
|
500
|
-
note(laneId, estimateUsd) {
|
|
501
|
-
if (estimateUsd !== null)
|
|
502
|
-
laneEstimates.set(laneId, estimateUsd);
|
|
503
|
-
let total = 0;
|
|
504
|
-
for (const value of laneEstimates.values())
|
|
505
|
-
total += value;
|
|
506
|
-
return total;
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
/** Build a lane's writeScreenshot closure: writes under screenshots/<screenshotDir>/ and records
|
|
511
|
-
* the relative path the trace references (screenshots/<name> at N=1; screenshots/<laneId>/<name>
|
|
512
|
-
* at N>1). */
|
|
513
|
-
export function makeLaneWriteScreenshot(artifactRoot, spec, screenshots) {
|
|
514
|
-
if (spec.screenshotDir) {
|
|
515
|
-
assertSafeOutputPathSegment(spec.screenshotDir, "Screenshot lane id");
|
|
516
|
-
}
|
|
517
|
-
const dirParts = spec.screenshotDir ? ["screenshots", spec.screenshotDir] : ["screenshots"];
|
|
518
|
-
const relPrefix = spec.screenshotDir ? path.posix.join("screenshots", spec.screenshotDir) : "screenshots";
|
|
519
|
-
return async (name, bytes) => {
|
|
520
|
-
assertSafeOutputPathSegment(name, "Screenshot name");
|
|
521
|
-
const rel = path.posix.join(relPrefix, name);
|
|
522
|
-
assertScreenshotEvidence(rel, bytes);
|
|
523
|
-
await writeContainedOutputFile(artifactRoot, path.join(...dirParts, name), bytes);
|
|
524
|
-
screenshots.push(rel);
|
|
525
|
-
return rel;
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
/** A blocked lane outcome (pipeline gate / fail-fast skipped it before it ran). */
|
|
529
|
-
function blockedLaneOutcome(spec, reason) {
|
|
530
|
-
return {
|
|
531
|
-
spec,
|
|
532
|
-
killed: false,
|
|
533
|
-
streamUrlPresent: false,
|
|
534
|
-
screenshots: [],
|
|
535
|
-
stateStepRecords: [],
|
|
536
|
-
phaseRecords: [],
|
|
537
|
-
warnings: [],
|
|
538
|
-
skippedReason: reason,
|
|
539
|
-
noEngagement: false,
|
|
540
|
-
selfReportedBlocker: false,
|
|
541
|
-
reportedFriction: false,
|
|
542
|
-
harnessError: false
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
// "can't" followed by a PERCEPTION verb describes what the screen showed, not an inability to
|
|
546
|
-
// proceed: "the canvas truncates it so you can't even read the whole thing", "I can't tell from
|
|
547
|
-
// the screen whether the rename is persisted", "so I could not read its full description". Five
|
|
548
|
-
// of five completed live runs on 2026-09-01 (two on drawDB, three on the planted benchmark app)
|
|
549
|
-
// were refused as "not a credible pass" on exactly these sentences, every one a defect report
|
|
550
|
-
// written AFTER the participant reached the goal. The more precisely a participant describes a
|
|
551
|
-
// display defect, the more likely the scan was to refuse the run — the incentive inversion #453
|
|
552
|
-
// fixed for resolved arcs, back in a new shape. "could not complete", "could not connect",
|
|
553
|
-
// "unable to get focus" still count: those name an inability to act.
|
|
554
|
-
const PERCEPTION_AFTER_MODAL = /\b(can'?t|cannot|could ?not|couldn'?t|unable to|wasn'?t able to)\s+(even\s+|quite\s+|really\s+|fully\s+)?(read|see|tell|view|make out|verify|confirm|be sure|be certain|judge|know)\b/g;
|
|
555
|
-
function hasBlockerLanguage(text) {
|
|
556
|
-
return /\b(can'?t|cannot|could not|unable|blocked|blocker|failed|invalid|not set)\b/.test(text)
|
|
557
|
-
|| /\b(shows|showing|hit|encountered|returned|got)\b.{0,80}\berror\b/.test(text)
|
|
558
|
-
|| /\berror[:.]/.test(text)
|
|
559
|
-
|| /what would you like me to do|please tell me|need (the )?(task|credentials|instructions)/.test(text);
|
|
560
|
-
}
|
|
561
|
-
/** The friction scan (inclusive): does the narrative report ANY blocker-shaped language,
|
|
562
|
-
* resolved or not? Feeds the participants `reportedFriction` tally and feedback candidates. */
|
|
563
|
-
// Report-shaped language: what a participant writes when it finished AND has something to say.
|
|
564
|
-
// Every one of the day's eleven drawDB reports (2026-09-01) opened a section "What confused me"
|
|
565
|
-
// or "Accessibility defects:"; none contained a blocker word, so none became a feedback candidate
|
|
566
|
-
// and a lane that had just replicated a keyboard-accessibility defect three times drafted "Live
|
|
567
|
-
// study completed without a participant-reported finding". Friction is the INCLUSIVE scan; a
|
|
568
|
-
// false positive here adds a candidate a person then reads, which is the cheap direction.
|
|
569
|
-
const REPORTED_DEFECT_LANGUAGE = /\b(defects?|bugs?|accessibilit(y|ies)|inaccessible|not (keyboard|screen.?reader)[- ]?accessible|confus(ed|ing)|hesitat(ed|ion)|unexpected(ly)?|unclear|hard to (find|tell|see|read|reach)|no (visible )?focus|overlap(ped|ping|s)?|truncat(ed|es|ion)|cut off|did nothing|nothing happened|no effect)\b/;
|
|
570
|
-
// The friction scan's own negations (#614). "Nothing was confusing", "no defects", "not unclear"
|
|
571
|
-
// are what a participant writes when it has NOTHING to report, and until 2026-09-03 each of them
|
|
572
|
-
// counted as reported friction and became a feedback candidate whose "actual" was a sentence
|
|
573
|
-
// reporting no problem. Only the report-shaped adjectives are negatable here: "no visible focus",
|
|
574
|
-
// "not keyboard-accessible" and "did nothing" are defects and stay.
|
|
575
|
-
const NEGATED_REPORT_ITEM = String.raw `(?:confus(?:ed|ing|ion)|unclear(?:\s+error\s+output)?|unexpected(?:ly)?|hesitat(?:ed|ion|ions)|surpris(?:ed|ing|es)|defects?|bugs?|overlap(?:ped|ping|s)?|truncat(?:ed|es|ion)|hard to (?:find|tell|see|read|reach)|blockers?|blocking issues?|errors?(?:\s+output)?|failures?|problems?|issues?)`;
|
|
576
|
-
const NEGATED_REPORT_QUALIFIERS = String.raw `(?:(?:really|particularly|especially|major|minor|real|actual|remaining|functional|obvious|noticeable|significant|any|a|an)\s+)*`;
|
|
577
|
-
const NEGATED_REPORT_MODIFIERS = String.raw `(?:(?:was|were|felt|seemed|really|particularly|especially|major|minor|real|actual|remaining|functional|obvious|noticeable|significant|any|a|an|encounter(?:ed)?|experience(?:d)?|notice(?:d)?|observe(?:d)?|feel|find|found|have|had)\s+)*`;
|
|
578
|
-
const NEGATED_REPORT_LANGUAGE = new RegExp(String.raw `\b(?:nothing|no|not|never|without|none|(?:did|do|does|was|were|has|have|had)n['’]t)\s+${NEGATED_REPORT_MODIFIERS}${NEGATED_REPORT_ITEM}\b`
|
|
579
|
-
// Negation scopes over a coordinated report list, not the rest of the sentence. In
|
|
580
|
-
// particular, leave "but the label was confusing" and "and Save did nothing" intact.
|
|
581
|
-
+ String.raw `(?:\s*,?\s+(?:or|nor|and)\s+${NEGATED_REPORT_QUALIFIERS}${NEGATED_REPORT_ITEM}\b)*`
|
|
582
|
-
// Keep the predicate inside its negation: "No errors blocked me" reports no blocker.
|
|
583
|
-
+ String.raw `(?:\s+(?:blocked|stopped|prevented)\s+(?:me|us|it)\b)?`, "g");
|
|
584
|
-
function stripNegatedReportLanguage(text) {
|
|
585
|
-
// "Not without hesitation" reports hesitation; do not let the inner "without" erase it.
|
|
586
|
-
return text.replace(/\b(?:not|never)\s+without\b/g, "with").replace(NEGATED_REPORT_LANGUAGE, " ");
|
|
587
|
-
}
|
|
588
|
-
function completionReasonContradictsGoal(reason) {
|
|
589
|
-
// Preserve the full negated list before the older blocker-specific rules remove its first
|
|
590
|
-
// noun ("no issues or hesitation"). Matching reports first also avoids their broad encounter
|
|
591
|
-
// clause rule swallowing a genuine subsequent observation.
|
|
592
|
-
const text = stripQuotedSpans(stripNegatedNonBlockerPhrases(stripNegatedReportLanguage(stripCodeExamples(reason).toLowerCase())));
|
|
593
|
-
return hasBlockerLanguage(text) || REPORTED_DEFECT_LANGUAGE.test(text);
|
|
594
|
-
}
|
|
595
|
-
/** Code/documentation excerpts are quoted material, not participant observations. */
|
|
596
|
-
function stripCodeExamples(text) {
|
|
597
|
-
return text
|
|
598
|
-
// Include an unterminated fence: copied text is not promoted just because its closing fence
|
|
599
|
-
// was omitted. Match the same marker so backticks inside a tilde fence cannot end it early.
|
|
600
|
-
.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^[ \t]*\1[ \t]*$|(?![\s\S]))/gm, " ")
|
|
601
|
-
.replace(/(`+)[^`\n]*\1/g, " ");
|
|
602
|
-
}
|
|
603
|
-
/** Interim messages also contain plans and hypotheses. Admit observed-report clauses only;
|
|
604
|
-
* the established closing-report scan remains separate. This is a conservative text heuristic,
|
|
605
|
-
* not an assertion that every mention of a defect is evidence that one happened. */
|
|
606
|
-
function interimMessageReportsFriction(message) {
|
|
607
|
-
const prose = stripQuotedSpans(stripCodeExamples(message).toLowerCase());
|
|
608
|
-
const clauses = prose.split(/(?<=[.!?])\s+|[;\n]+|,\s*(?:but|so|however|yet)\s+|\s+so\s+(?=i\b|we\b)/);
|
|
609
|
-
return clauses.some((clause) => {
|
|
610
|
-
// A condition, question, intention, or conjecture does not assert an observed result.
|
|
611
|
-
// Clause splitting above keeps "Save did nothing, so I will try Enter" observable.
|
|
612
|
-
if (/\?|\b(?:if|unless|whether|maybe|perhaps|suppose|hypothetically|might|may|would|should)\b|\bcould\b(?!\s+not\b)/.test(clause)
|
|
613
|
-
|| /\b(?:i|we)(?:['’]ll|\s+(?:will|plan|intend|want|hope|suspect|wonder))\b|\bgoing to\b|\blet['’]s\b/.test(clause)
|
|
614
|
-
|| /\b(?:task|goal|mission|objective|plan)\s+(?:(?:is|was)\s+)?to\b|^\s*(?:check|test|look|checking|testing)\b/.test(clause))
|
|
615
|
-
return false;
|
|
616
|
-
// A topic is not a defect ("the accessibility guide is open", "shows error-handling docs").
|
|
617
|
-
// Actual friction in those surfaces still qualifies: "the error guide was confusing".
|
|
618
|
-
const observation = clause
|
|
619
|
-
.replace(/\baccessibilit(?:y|ies)\b/g, " ")
|
|
620
|
-
.replace(/\berror[- ]handling\b|\berror\s+(?:documentation|docs?|guides?|reference|examples?)\b/g, " ");
|
|
621
|
-
const assertsObservation = /\b(?:is|are|was|were|has|had|did|does|shows?|showed|seems?|seemed|looks?|looked|found|noticed|saw|hit|encountered|felt|got|failed|returned|cannot|can['’]?t|unable|could not)\b|\b(?:overlap(?:ped|ping|s)?|truncat(?:ed|es)|cut off|nothing happened|no (?:visible )?focus)\b/.test(observation);
|
|
622
|
-
return assertsObservation && completionReasonContradictsGoal(observation);
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
/** The verdict scan (strict): like the friction scan, but resolved-arc segments are stripped
|
|
626
|
-
* first — failure narration the participant itself reports as overcome is friction on the
|
|
627
|
-
* road, not a blocker at the destination (#453). */
|
|
628
|
-
function completionReasonBlocksVerdict(reason) {
|
|
629
|
-
// Perception phrases are stripped for the VERDICT only: "I could not read the full description"
|
|
630
|
-
// is friction worth a tally count and a feedback candidate (the friction scan above keeps it),
|
|
631
|
-
// and it is not a reason to refuse the pass.
|
|
632
|
-
return hasBlockerLanguage(stripResolvedArcSegments(stripQuotedSpans(stripNegatedNonBlockerPhrases(reason.toLowerCase())))
|
|
633
|
-
.replace(PERCEPTION_AFTER_MODAL, ""));
|
|
634
|
-
}
|
|
635
|
-
// A failure segment counts as a resolved arc when the recovery is self-reported either in the
|
|
636
|
-
// SAME segment ("the import failed but then went through") or — the common report shape — in the
|
|
637
|
-
// immediately FOLLOWING segment as a retry/alternative that succeeded ("my first import failed
|
|
638
|
-
// with a parser error. A simpler SQL import succeeded."). The lookahead demands the retry flavor
|
|
639
|
-
// on purpose: unrelated praise ("Separately, the search box worked") must never launder an
|
|
640
|
-
// unresolved failure. "Login failed so I gave up" has no recovery anywhere and stays a blocker.
|
|
641
|
-
// (#453 — the run-1 false negative: a defect report after demonstrated success failed the lane,
|
|
642
|
-
// an incentive inversion against exactly the participant behavior a study wants most.)
|
|
643
|
-
const RESOLUTION_TERMS = /\b(succeed(?:ed|s)?|success(?:ful|fully)?|worked|works around|then worked|now works?|resolved|fixed|recovered|got it working|went through)\b/;
|
|
644
|
-
const RETRY_RESOLUTION = /\b(simpler|simplified|retry(?:ing)?|retried|second (?:attempt|try)|another (?:attempt|try|approach)|different (?:approach|way|route)|instead|then|eventually|after that)\b[^.!?\n]{0,80}\b(succeed(?:ed|s)?|success(?:ful|fully)?|worked|went through|completed|passed)\b/;
|
|
645
|
-
/** Drop sentence/bullet segments whose failure language is part of a self-reported RESOLVED arc. */
|
|
646
|
-
function stripResolvedArcSegments(text) {
|
|
647
|
-
const segments = text.split(/(?<=[.!?])\s+|\n+/);
|
|
648
|
-
return segments
|
|
649
|
-
.filter((segment, index) => {
|
|
650
|
-
if (!hasBlockerLanguage(segment))
|
|
651
|
-
return true;
|
|
652
|
-
if (RESOLUTION_TERMS.test(segment))
|
|
653
|
-
return false;
|
|
654
|
-
const next = segments[index + 1];
|
|
655
|
-
return !(next !== undefined && RETRY_RESOLUTION.test(next));
|
|
656
|
-
})
|
|
657
|
-
.join(" ");
|
|
658
|
-
}
|
|
659
|
-
// Negations that DESCRIBE a defect rather than deny one. Kept out of every clause drop below.
|
|
660
|
-
const DEFECT_SHAPED_NEGATION = /\bno\s+(?:visible\s+)?focus\b|\bno\s+(?:keyboard|screen.?reader)[- ]?(?:access|path|route|way|alternative|equivalent)|\bnot\s+(?:keyboard|screen.?reader)[- ]?accessible\b|\bno\s+(?:effect|feedback|response)\b|\b(?:did|does)\s+nothing\b|\bnothing\s+happened\b/;
|
|
661
|
-
function stripNegatedNonBlockerPhrases(text) {
|
|
662
|
-
return text
|
|
663
|
-
// FIRST, before the narrower rules eat the "no blockers" and leave "encountered ... error"
|
|
664
|
-
// behind: "I encountered no blockers or unclear error output." refused a clean passing run on
|
|
665
|
-
// 2026-09-01. A verb of encounter followed by "no" negates the whole clause, so drop the clause.
|
|
666
|
-
// ... unless the clause names a DEFECT: "the delete control had no visible focus" is a
|
|
667
|
-
// finding, and the verb it happens to use must not decide whether it counts (#622).
|
|
668
|
-
.replace(/\b(?:encountered|hit|saw|found|met|had|got|ran into)\s+no\s+[^.!?\n]*/g, (clause) => DEFECT_SHAPED_NEGATION.test(clause) ? clause : " ")
|
|
669
|
-
.replace(/\bno\s+(?:real\s+|remaining\s+|actual\s+)?(?:blocker|blockers|blocking issue|blocking issues|error|errors|failure|failures)\s+(?:was\s+|were\s+)?(?:encountered|observed|found|hit|seen|reported|detected)\b/g, "")
|
|
670
|
-
.replace(/\bwithout\s+(?:a\s+|any\s+)?(?:real\s+|remaining\s+|actual\s+)?(?:blocker|blockers|blocking issue|blocking issues|error|errors|failure|failures)\b/g, "")
|
|
671
|
-
.replace(/\bnot\s+(?:blocked|a blocker|an error|failed)\b/g, "")
|
|
672
|
-
// "No functional failures blocked me" downgraded a clean passing run to a lab failure on
|
|
673
|
-
// 2026-09-01. The adjective list above is closed (real|remaining|actual), so an ordinary
|
|
674
|
-
// qualifier like "functional" slipped through and the trailing verb "blocked" tripped the
|
|
675
|
-
// scan. Allow up to two intervening words, and cover the verb form directly.
|
|
676
|
-
.replace(/\bno\s+(?:\w+\s+){0,2}(?:blocker|blockers|blocking issues?|errors?|failures?|problems?|issues?)\b(?:\s+(?:blocked|stopped|prevented)\s+(?:me|us|it))?/g, " ")
|
|
677
|
-
.replace(/\bnothing\s+(?:\w+\s+){0,2}(?:blocked|stopped|prevented)\s+(?:me|us|it)\b/g, " ");
|
|
678
|
-
}
|
|
679
|
-
/**
|
|
680
|
-
* Remove double-quoted spans and markdown blockquote lines before the blocker scan, so a persona
|
|
681
|
-
* that faithfully QUOTES the subject app's own copy (e.g. a banner reading "cannot be undone") is
|
|
682
|
-
* not misread as the actor reporting its OWN blocker. Only double quotes (straight and smart) and
|
|
683
|
-
* `>` blockquotes are stripped — never single quotes, which would mangle contractions like `can't`.
|
|
684
|
-
*/
|
|
685
|
-
function stripQuotedSpans(text) {
|
|
686
|
-
return text
|
|
687
|
-
.replace(/"[^"]*"/g, " ")
|
|
688
|
-
.replace(/“[^”]*”/g, " ")
|
|
689
|
-
.replace(/^\s*>.*$/gm, " ");
|
|
690
|
-
}
|
|
691
|
-
function traceHasStopWhenMatch(session) {
|
|
692
|
-
return session.trace.items.some((item) => item.kind === "notice"
|
|
693
|
-
&& item.status === "matched"
|
|
694
|
-
// A dwell window that ended the session (then: stop) is the same class of harness-owned,
|
|
695
|
-
// structured completion as a matched stopWhen (#510).
|
|
696
|
-
&& (item.title.startsWith("stopWhen matched") || item.title === "dwell window complete"));
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* A goal_satisfied lane counts as a self-reported blocker ONLY when its final narrative contradicts
|
|
700
|
-
* the goal AND the run's own stop predicate did NOT fire. A matched stopWhen is independent,
|
|
701
|
-
* structured completion evidence, so it overrides a text scan of the free-form narrative — which can
|
|
702
|
-
* otherwise trip on the subject app's OWN quoted copy (e.g. a relayed "cannot be undone" banner).
|
|
703
|
-
* Resolved-arc segments never block the verdict (#453). Returns the offending reason, or undefined
|
|
704
|
-
* when the lane is a clean pass. Exported for testing.
|
|
705
|
-
*/
|
|
706
|
-
export function resolveSelfReportedBlocker(session) {
|
|
707
|
-
// The participant's own word wins when it gave one (#570): a declared "reached" is not re-read
|
|
708
|
-
// for blocker-shaped phrases, and a declared "blocked" is a blocker whatever the paragraph says.
|
|
709
|
-
const declared = session?.trace.declaredOutcome;
|
|
710
|
-
if (session !== undefined && declared !== undefined) {
|
|
711
|
-
return declared === "blocked" && session.completionReason === "goal_satisfied" ? session.reason : undefined;
|
|
712
|
-
}
|
|
713
|
-
return session?.completionReason === "goal_satisfied"
|
|
714
|
-
&& completionReasonBlocksVerdict(session.reason)
|
|
715
|
-
&& !traceHasStopWhenMatch(session)
|
|
716
|
-
? session.reason
|
|
717
|
-
: undefined;
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* Friction is independent of how a completed session ended (#657). Read the participant's
|
|
721
|
-
* redacted messages, including earlier reports, rather than the harness-owned reason that
|
|
722
|
-
* stopWhen/dwell writes. Reasoning, observations, and notices are not participant reports.
|
|
723
|
-
* Resolved arcs still count (#453); quoted copy and negated reports still do not. This read
|
|
724
|
-
* never changes the verdict. Exported for testing.
|
|
725
|
-
*/
|
|
726
|
-
export function resolveSelfReportedFriction(session) {
|
|
727
|
-
if (session?.completionReason !== "goal_satisfied")
|
|
728
|
-
return undefined;
|
|
729
|
-
// Friction stays a read of the narrative even when the outcome was declared: a participant who
|
|
730
|
-
// reached the goal and described what was hard on the way has reported friction.
|
|
731
|
-
if (session.trace.declaredOutcome === "blocked")
|
|
732
|
-
return session.reason;
|
|
733
|
-
const messages = session.trace.items
|
|
734
|
-
.filter((item) => item.kind === "message" && item.id !== session.trace.debrief?.messageId)
|
|
735
|
-
.map((item) => item.text?.trim() ?? "")
|
|
736
|
-
.filter((text) => text.length > 0);
|
|
737
|
-
// A custom session may keep its closing report only in reason even when earlier messages exist.
|
|
738
|
-
// Structured stop/dwell reasons are controller text and never enter this closing-report path.
|
|
739
|
-
const closingReport = traceHasStopWhenMatch(session) ? undefined : session.reason.trim();
|
|
740
|
-
// One candidate per participant, with exact repeats removed (the closing report often repeats
|
|
741
|
-
// a prior turn). Earlier turns require observed-report clauses, not arbitrary defect mentions.
|
|
742
|
-
const typedReports = session.trace.debrief?.status === "completed"
|
|
743
|
-
? session.trace.debrief.report?.frictionReports ?? [] : [];
|
|
744
|
-
const reports = [...new Set([...typedReports, ...messages.filter((message) => message === closingReport
|
|
745
|
-
? completionReasonContradictsGoal(message)
|
|
746
|
-
: interimMessageReportsFriction(message))])];
|
|
747
|
-
if (closingReport && completionReasonContradictsGoal(closingReport) && !reports.includes(closingReport)) {
|
|
748
|
-
reports.push(closingReport);
|
|
749
|
-
}
|
|
750
|
-
if (reports.length > 0)
|
|
751
|
-
return reports.join("\n\n");
|
|
752
|
-
return undefined;
|
|
753
|
-
}
|
|
754
|
-
/** Run one participant against a prepared desktop. The adapter owns provisioning, final
|
|
755
|
-
* evidence and cleanup; this runner owns the model, trace and participant outcome. */
|
|
756
|
-
export async function runCuaLane(spec, deps) {
|
|
757
|
-
const { config, env } = deps;
|
|
758
|
-
let codexParticipant;
|
|
759
|
-
let claudeSession;
|
|
760
|
-
let localAgentProvider;
|
|
761
|
-
const warnings = [];
|
|
762
|
-
const screenshots = [];
|
|
763
|
-
const writeScreenshot = makeLaneWriteScreenshot(deps.artifactRoot, spec, screenshots);
|
|
764
|
-
let session;
|
|
765
|
-
let sessionError;
|
|
766
|
-
let provisioned = false;
|
|
767
|
-
let signaled = false;
|
|
768
|
-
const signal = (ok) => {
|
|
769
|
-
if (!signaled && deps.signalProvisioned) {
|
|
770
|
-
signaled = true;
|
|
771
|
-
deps.signalProvisioned(ok);
|
|
772
|
-
}
|
|
773
|
-
};
|
|
774
|
-
const desktopLane = deps.createDesktopLane?.(spec, warnings, deps.artifactRoot) ?? createE2BCuaDesktopLane(spec, deps, warnings);
|
|
775
|
-
try {
|
|
776
|
-
await desktopLane.prepare();
|
|
777
|
-
const ready = await desktopLane.openSession();
|
|
778
|
-
if (deps.hooks.buildProvider) {
|
|
779
|
-
localAgentProvider = await deps.hooks.buildProvider({ config, actor: deps.descriptor, lane: spec, executor: ready.executor });
|
|
780
|
-
}
|
|
781
|
-
else if (deps.localAgent === "codex") {
|
|
782
|
-
// Hosted local-agent studies use the same native participant engine as local desktops.
|
|
783
|
-
// Operator auth deliberately retains the operator's Codex home, config and supported auth
|
|
784
|
-
// stores instead of applying the isolated restricted-account profile used by local studies.
|
|
785
|
-
codexParticipant = createRestrictedCodexParticipant({
|
|
786
|
-
authMode: "operator",
|
|
787
|
-
...(spec.reasoningEffort === undefined ? {} : { reasoningEffort: spec.reasoningEffort }),
|
|
788
|
-
...(config.actors[0]?.model === undefined ? {} : { model: config.actors[0].model }),
|
|
789
|
-
...(ready.executor.speechEnabled === true ? { speechEnabled: true } : {}),
|
|
790
|
-
session: { env }
|
|
791
|
-
});
|
|
792
|
-
localAgentProvider = codexParticipant.provider;
|
|
793
|
-
}
|
|
794
|
-
else if (deps.localAgent === "claude") {
|
|
795
|
-
// One session for the whole run, like the codex thread above (#520). The one-shot
|
|
796
|
-
// provider (createLocalAgentProvider) spawned `claude -p` per turn, and every turn
|
|
797
|
-
// started with no memory of the last. HUMANISH_LOCAL_AGENT_ONE_SHOT=1 keeps that path
|
|
798
|
-
// reachable as a MEASUREMENT switch: MemTrapBench (2026-08) reports memory frameworks
|
|
799
|
-
// degrading agent performance by 10-40% on some tasks, so "remembers" has to be measured
|
|
800
|
-
// against "does not" on the same lab, not assumed. The trace records which one ran.
|
|
801
|
-
const oneShot = env.HUMANISH_LOCAL_AGENT_ONE_SHOT !== undefined
|
|
802
|
-
&& env.HUMANISH_LOCAL_AGENT_ONE_SHOT !== ""
|
|
803
|
-
&& env.HUMANISH_LOCAL_AGENT_ONE_SHOT !== "0";
|
|
804
|
-
if (oneShot) {
|
|
805
|
-
localAgentProvider = createLocalAgentProvider({
|
|
806
|
-
agent: "claude",
|
|
807
|
-
...(spec.reasoningEffort === undefined ? {} : { reasoningEffort: spec.reasoningEffort }),
|
|
808
|
-
...(config.actors[0]?.model === undefined ? {} : { model: config.actors[0].model })
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
else {
|
|
812
|
-
claudeSession = await startClaudeSession({
|
|
813
|
-
...(spec.reasoningEffort === undefined ? {} : { reasoningEffort: spec.reasoningEffort }),
|
|
814
|
-
...(config.actors[0]?.model === undefined ? {} : { model: config.actors[0].model })
|
|
815
|
-
});
|
|
816
|
-
localAgentProvider = claudeSession.provider;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
// World is ready: release the pipeline gate so the remaining lanes may start.
|
|
820
|
-
provisioned = true;
|
|
821
|
-
signal(true);
|
|
822
|
-
// The FAIL-CLOSED spend cap (execution.caps.maxUsd) is wired into the loop as maxUsd + an
|
|
823
|
-
// injected pure per-turn estimator keyed on the resolved model. Preflight already refused a
|
|
824
|
-
// cap on an unpriced model, so the estimate is measurable whenever a cap is in force. The
|
|
825
|
-
// model id here matches provider.version (openai-responses-cu resolves the default when unset).
|
|
826
|
-
const capModelId = config.actors[0]?.model ?? DEFAULT_OPENAI_CU_MODEL;
|
|
827
|
-
const maxUsd = config.execution?.caps?.maxUsd;
|
|
828
|
-
const sessionOptions = {
|
|
829
|
-
instructions: ready.inbox
|
|
830
|
-
? withInboxMission(spec, ready.inbox.url, ready.inbox.address, ready.inbox.receiving).instructions
|
|
831
|
-
: spec.instructions,
|
|
832
|
-
persona: spec.persona,
|
|
833
|
-
timeoutMs: deps.timeoutMs,
|
|
834
|
-
// The brain is either a keyed API client or a CLI the operator is already signed in to.
|
|
835
|
-
// Everything below this line — loop, executor, trace, affordances — is identical either
|
|
836
|
-
// way, which is what makes a local-agent run comparable to an API one.
|
|
837
|
-
...(localAgentProvider === undefined ? {} : { provider: localAgentProvider }),
|
|
838
|
-
openai: {
|
|
839
|
-
apiKey: deps.openaiApiKey,
|
|
840
|
-
...(config.actors[0]?.model ? { model: config.actors[0].model } : {}),
|
|
841
|
-
// Per-LANE, not per-actor: two lanes at different efforts is the control this exists for.
|
|
842
|
-
...(spec.reasoningEffort === undefined ? {} : { reasoningEffort: spec.reasoningEffort }),
|
|
843
|
-
...(spec.maxOutputTokens === undefined ? {} : { maxOutputTokens: spec.maxOutputTokens })
|
|
844
|
-
},
|
|
845
|
-
...(maxUsd === undefined
|
|
846
|
-
? {}
|
|
847
|
-
: {
|
|
848
|
-
maxUsd,
|
|
849
|
-
estimateTurnCostUsd: (usage) => estimateActorCostForExecution(usage, localAgentProvider?.version ?? capModelId, localAgentProvider?.executionProfile).estimatedCostUsd
|
|
850
|
-
}),
|
|
851
|
-
executor: ready.executor,
|
|
852
|
-
redactScreenshots: deps.redactScreenshots,
|
|
853
|
-
scrubText: deps.scrubKnownValues,
|
|
854
|
-
writeScreenshot,
|
|
855
|
-
...(spec.idleSteps === undefined ? {} : { idleSteps: spec.idleSteps }),
|
|
856
|
-
...(spec.noProgressSteps === undefined ? {} : { noProgressSteps: spec.noProgressSteps }),
|
|
857
|
-
...(spec.stopWhen === undefined ? {} : { stopWhen: spec.stopWhen }),
|
|
858
|
-
...(spec.dwell === undefined ? {} : { dwell: spec.dwell }),
|
|
859
|
-
...(spec.tasks === undefined ? {} : { tasks: spec.tasks }),
|
|
860
|
-
// The STUDY budget (#299): this lane notes its own running estimate on the shared ledger
|
|
861
|
-
// and stops when the RUN total crosses the cap — independent of the per-lane maxUsd above.
|
|
862
|
-
...(deps.runBudget === undefined
|
|
863
|
-
? {}
|
|
864
|
-
: {
|
|
865
|
-
overRunBudget: (usage) => {
|
|
866
|
-
const estimate = estimateActorCostForExecution(usage, localAgentProvider?.version ?? capModelId, localAgentProvider?.executionProfile).estimatedCostUsd;
|
|
867
|
-
const totalUsd = deps.runBudget.note(spec.laneId, estimate);
|
|
868
|
-
return totalUsd > deps.runBudget.maxTotalUsd
|
|
869
|
-
? `study budget reached: the run's estimated model spend $${round6(totalUsd)} crossed execution.caps.maxTotalUsd=$${deps.runBudget.maxTotalUsd}; this lane stops here and sibling lanes stop at their next turn`
|
|
870
|
-
: null;
|
|
871
|
-
}
|
|
872
|
-
}),
|
|
873
|
-
...(deps.onObservedUrl === undefined ? {} : { onObservedUrl: deps.onObservedUrl }),
|
|
874
|
-
...(deps.onMessage === undefined ? {} : { onMessage: deps.onMessage }),
|
|
875
|
-
...(deps.onScreenshot === undefined ? {} : { onScreenshot: deps.onScreenshot }),
|
|
876
|
-
...(deps.onTrace === undefined
|
|
877
|
-
? {}
|
|
878
|
-
: {
|
|
879
|
-
// Forwards the RUNNING usage as well: the lane is where both are known, and usage
|
|
880
|
-
// without it never reaches the flush — which is how the live cost stayed unknown.
|
|
881
|
-
onTrace: (items, usage, metadata) => deps.onTrace?.(spec.laneId, items, usage, metadata)
|
|
882
|
-
})
|
|
883
|
-
};
|
|
884
|
-
session = await deps.runSession(sessionOptions);
|
|
885
|
-
}
|
|
886
|
-
catch (error) {
|
|
887
|
-
sessionError = redactText(deps.scrubKnownValues(toErrorMessage(error)));
|
|
888
|
-
}
|
|
889
|
-
finally {
|
|
890
|
-
try {
|
|
891
|
-
if (codexParticipant === undefined)
|
|
892
|
-
await localAgentProvider?.close?.();
|
|
893
|
-
}
|
|
894
|
-
catch {
|
|
895
|
-
warnings.push("Model provider cleanup is unconfirmed.");
|
|
896
|
-
sessionError ??= "Model provider cleanup is unconfirmed.";
|
|
897
|
-
}
|
|
898
|
-
try {
|
|
899
|
-
const cleanup = await codexParticipant?.close();
|
|
900
|
-
if (cleanup?.status === "unconfirmed") {
|
|
901
|
-
warnings.push("Model provider cleanup is unconfirmed.");
|
|
902
|
-
sessionError ??= "Model provider cleanup is unconfirmed.";
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
catch {
|
|
906
|
-
warnings.push("Model provider cleanup is unconfirmed.");
|
|
907
|
-
sessionError ??= "Model provider cleanup is unconfirmed.";
|
|
908
|
-
}
|
|
909
|
-
try {
|
|
910
|
-
await claudeSession?.close();
|
|
911
|
-
}
|
|
912
|
-
catch {
|
|
913
|
-
warnings.push('Claude session cleanup failed; desktop cleanup will still run.');
|
|
914
|
-
}
|
|
915
|
-
try {
|
|
916
|
-
if (!provisioned)
|
|
917
|
-
signal(false);
|
|
918
|
-
}
|
|
919
|
-
finally {
|
|
920
|
-
await desktopLane.finalize({ failed: sessionError !== undefined || session === undefined });
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
if (session) {
|
|
924
|
-
// Per-lane model-token cost ESTIMATE, attached to the trace before it is persisted (the model
|
|
925
|
-
// id is authoritative here — provider.version). Kept at the lab boundary so the pure loop
|
|
926
|
-
// never depends on the operator rate table. estimateActorCost declares absent (null) for an
|
|
927
|
-
// unknown rate / missing usage rather than guessing.
|
|
928
|
-
session.trace.estimatedCost = estimateActorCostForExecution(session.trace.tokenUsage, session.trace.ids.model, session.trace.executionProfile);
|
|
929
|
-
await writeContainedOutputFile(deps.artifactRoot, spec.traceArtifactPath, `${JSON.stringify(session.trace, null, 2)}\n`, "utf8");
|
|
930
|
-
if (session.trace.redaction.screenshots === "raw") {
|
|
931
|
-
warnings.push("Screenshots are full-fidelity (raw) for local use — the bundle stays in gitignored .humanish and nothing scans these pixels; review them before sharing anywhere. Set policies.redactScreenshots: true to blur a share-as-is bundle.");
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
const noEngagement = session !== undefined
|
|
935
|
-
&& session.completionReason === "goal_satisfied"
|
|
936
|
-
&& (session.trace.counts.actions ?? 0) === 0
|
|
937
|
-
&& (session.trace.counts.messages ?? 0) === 0
|
|
938
|
-
&& !traceHasStopWhenMatch(session);
|
|
939
|
-
if (noEngagement) {
|
|
940
|
-
warnings.push("Actor returned goal_satisfied with ZERO actions and ZERO messages — it likely saw a blank or still-loading screen and stopped without engaging. NOT counted as a pass. Check the screenshot; raise execution.timeoutMs or confirm the subject painted before the first turn.");
|
|
941
|
-
}
|
|
942
|
-
const blockerReason = resolveSelfReportedBlocker(session);
|
|
943
|
-
const selfReportedBlocker = blockerReason !== undefined;
|
|
944
|
-
const reportedFriction = resolveSelfReportedFriction(session) !== undefined;
|
|
945
|
-
if (selfReportedBlocker) {
|
|
946
|
-
warnings.push(`Actor returned goal_satisfied while its final message describes a blocker or asks for missing instructions — NOT counted as a pass: ${redactText(deps.scrubKnownValues(blockerReason))}`);
|
|
947
|
-
}
|
|
948
|
-
const harnessError = sessionError !== undefined || session?.completionReason === "harness_error";
|
|
949
|
-
return {
|
|
950
|
-
spec,
|
|
951
|
-
...(session ? { session } : {}),
|
|
952
|
-
...(sessionError === undefined ? {} : { sessionError }),
|
|
953
|
-
...desktopLane.snapshot(),
|
|
954
|
-
screenshots,
|
|
955
|
-
warnings,
|
|
956
|
-
noEngagement,
|
|
957
|
-
selfReportedBlocker,
|
|
958
|
-
reportedFriction,
|
|
959
|
-
harnessError,
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
/** Run the single IN-PROCESS lane (a custom executor + provider; NO E2B). Always one lane. */
|
|
963
|
-
async function runInProcessLane(spec, deps) {
|
|
964
|
-
const warnings = [];
|
|
965
|
-
const screenshots = [];
|
|
966
|
-
const writeScreenshot = makeLaneWriteScreenshot(deps.artifactRoot, spec, screenshots);
|
|
967
|
-
let session;
|
|
968
|
-
let sessionError;
|
|
969
|
-
let provider;
|
|
970
|
-
try {
|
|
971
|
-
const executor = await deps.hooks.buildExecutor({ config: deps.config, actor: deps.descriptor, appUrl: deps.appUrl });
|
|
972
|
-
provider = await deps.hooks.buildProvider({ config: deps.config, actor: deps.descriptor, lane: spec, executor });
|
|
973
|
-
const sessionOptions = {
|
|
974
|
-
instructions: spec.instructions,
|
|
975
|
-
persona: spec.persona,
|
|
976
|
-
timeoutMs: deps.timeoutMs,
|
|
977
|
-
provider,
|
|
978
|
-
executor,
|
|
979
|
-
redactScreenshots: deps.redactScreenshots,
|
|
980
|
-
scrubText: deps.scrubKnownValues,
|
|
981
|
-
writeScreenshot,
|
|
982
|
-
...(deps.onTrace === undefined ? {} : { onTrace: (items, usage, metadata) => deps.onTrace?.(spec.laneId, items, usage, metadata) }),
|
|
983
|
-
...(spec.stopWhen === undefined ? {} : { stopWhen: spec.stopWhen }),
|
|
984
|
-
...(spec.dwell === undefined ? {} : { dwell: spec.dwell }),
|
|
985
|
-
...(spec.tasks === undefined ? {} : { tasks: spec.tasks })
|
|
986
|
-
};
|
|
987
|
-
session = await deps.runSession(sessionOptions);
|
|
988
|
-
}
|
|
989
|
-
catch (error) {
|
|
990
|
-
sessionError = redactText(deps.scrubKnownValues(toErrorMessage(error)));
|
|
991
|
-
}
|
|
992
|
-
finally {
|
|
993
|
-
try {
|
|
994
|
-
await provider?.close?.();
|
|
995
|
-
}
|
|
996
|
-
catch {
|
|
997
|
-
sessionError ??= "Model provider cleanup is unconfirmed.";
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
if (session) {
|
|
1001
|
-
await writeContainedOutputFile(deps.artifactRoot, spec.traceArtifactPath, `${JSON.stringify(session.trace, null, 2)}\n`, "utf8");
|
|
1002
|
-
if (session.trace.redaction.screenshots === "raw") {
|
|
1003
|
-
warnings.push("Screenshots are full-fidelity (raw) for local use — the bundle stays in gitignored .humanish and nothing scans these pixels; review them before sharing anywhere. Set policies.redactScreenshots: true to blur a share-as-is bundle.");
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
const noEngagement = session !== undefined
|
|
1007
|
-
&& session.completionReason === "goal_satisfied"
|
|
1008
|
-
&& (session.trace.counts.actions ?? 0) === 0
|
|
1009
|
-
&& (session.trace.counts.messages ?? 0) === 0
|
|
1010
|
-
&& !traceHasStopWhenMatch(session);
|
|
1011
|
-
if (noEngagement) {
|
|
1012
|
-
warnings.push("Actor returned goal_satisfied with ZERO actions and ZERO messages — it likely saw a blank or still-loading screen and stopped without engaging. NOT counted as a pass. Check the screenshot; raise execution.timeoutMs or confirm the subject painted before the first turn.");
|
|
1013
|
-
}
|
|
1014
|
-
const blockerReason = resolveSelfReportedBlocker(session);
|
|
1015
|
-
const selfReportedBlocker = blockerReason !== undefined;
|
|
1016
|
-
const reportedFriction = resolveSelfReportedFriction(session) !== undefined;
|
|
1017
|
-
if (selfReportedBlocker) {
|
|
1018
|
-
warnings.push(`Actor returned goal_satisfied while its final message describes a blocker or asks for missing instructions — NOT counted as a pass: ${blockerReason}`);
|
|
1019
|
-
}
|
|
1020
|
-
return {
|
|
1021
|
-
spec,
|
|
1022
|
-
...(session ? { session } : {}),
|
|
1023
|
-
...(sessionError === undefined ? {} : { sessionError }),
|
|
1024
|
-
killed: false,
|
|
1025
|
-
streamUrlPresent: false,
|
|
1026
|
-
screenshots,
|
|
1027
|
-
stateStepRecords: [],
|
|
1028
|
-
phaseRecords: [],
|
|
1029
|
-
warnings,
|
|
1030
|
-
noEngagement,
|
|
1031
|
-
selfReportedBlocker,
|
|
1032
|
-
reportedFriction,
|
|
1033
|
-
harnessError: sessionError !== undefined || session?.completionReason === "harness_error",
|
|
1034
|
-
entryKind: "local-app"
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Run N>1 E2B lanes with bounded concurrency, a pipeline gate (lane 1 provisions before the rest
|
|
1039
|
-
* start), and session fail-fast on HARNESS errors only (queued lanes become `blocked` with a
|
|
1040
|
-
* pinned reason + a fail-fast event; mission verdicts never trip it). Each lane tears down ITS
|
|
1041
|
-
* OWN sandbox by id; nothing here ever enumerates.
|
|
1042
|
-
*/
|
|
1043
|
-
/** Exported for the #342 total-runner tests: the injectable runner lets a test make one lane
|
|
1044
|
-
* THROW (the exact class the guard exists for) without a live sandbox. Production always uses
|
|
1045
|
-
* the default. */
|
|
1046
|
-
export async function runCuaLanes(laneSpecs, deps, concurrency, runLane = runCuaLane) {
|
|
1047
|
-
const failFast = { tripped: false, reason: "" };
|
|
1048
|
-
let resolveGate;
|
|
1049
|
-
let rejectGate;
|
|
1050
|
-
const gate = new Promise((resolve, reject) => {
|
|
1051
|
-
resolveGate = resolve;
|
|
1052
|
-
rejectGate = () => reject(new Error("gate"));
|
|
1053
|
-
});
|
|
1054
|
-
// The gate is rejected on lane-0 provisioning failure; swallow the unhandled rejection if no
|
|
1055
|
-
// later lane ever awaits it (concurrency could let lane 0 finish alone).
|
|
1056
|
-
gate.catch(() => undefined);
|
|
1057
|
-
const outcomes = await mapWithConcurrency(laneSpecs, concurrency, async (spec, index) => {
|
|
1058
|
-
if (index > 0) {
|
|
1059
|
-
try {
|
|
1060
|
-
await gate;
|
|
1061
|
-
}
|
|
1062
|
-
catch {
|
|
1063
|
-
return blockedLaneOutcome(spec, `skipped: lane ${laneSpecs[0]?.laneId ?? "lane-01"} failed to provision its world (pipeline gate)`);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
if (failFast.tripped) {
|
|
1067
|
-
return blockedLaneOutcome(spec, `skipped: ${failFast.reason}`);
|
|
1068
|
-
}
|
|
1069
|
-
// The lane runner is TOTAL (#342): every exit path returns a recorded outcome. Without this
|
|
1070
|
-
// guard, one lane's late throw (e.g. its trace write hitting ENOSPC after its own sandbox was
|
|
1071
|
-
// already torn down) rejected the whole map while sibling workers kept launching sandboxes
|
|
1072
|
-
// nobody would ever record — the run spent money and then reported nothing.
|
|
1073
|
-
let outcome;
|
|
1074
|
-
try {
|
|
1075
|
-
outcome = await runLane(spec, {
|
|
1076
|
-
...deps,
|
|
1077
|
-
...(index === 0
|
|
1078
|
-
? {
|
|
1079
|
-
signalProvisioned: (ok) => {
|
|
1080
|
-
if (ok) {
|
|
1081
|
-
resolveGate?.();
|
|
1082
|
-
}
|
|
1083
|
-
else {
|
|
1084
|
-
rejectGate?.();
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
: {})
|
|
1089
|
-
});
|
|
1090
|
-
}
|
|
1091
|
-
catch (error) {
|
|
1092
|
-
// Lane 0 may have thrown before signaling the provisioning gate — release the followers as
|
|
1093
|
-
// blocked rather than leaving them awaiting a gate that will never settle.
|
|
1094
|
-
if (index === 0)
|
|
1095
|
-
rejectGate?.();
|
|
1096
|
-
const detail = redactText(toErrorMessage(error));
|
|
1097
|
-
outcome = {
|
|
1098
|
-
spec,
|
|
1099
|
-
killed: false,
|
|
1100
|
-
streamUrlPresent: false,
|
|
1101
|
-
screenshots: [],
|
|
1102
|
-
stateStepRecords: [],
|
|
1103
|
-
phaseRecords: [],
|
|
1104
|
-
warnings: [],
|
|
1105
|
-
noEngagement: false,
|
|
1106
|
-
selfReportedBlocker: false,
|
|
1107
|
-
reportedFriction: false,
|
|
1108
|
-
harnessError: true,
|
|
1109
|
-
sessionError: `lane runner threw outside the session guard: ${detail}`
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
if (outcome.harnessError && !failFast.tripped) {
|
|
1113
|
-
failFast.tripped = true;
|
|
1114
|
-
failFast.reason = `a prior lane (${outcome.spec.laneId}) ended in a harness error (fail-fast)`;
|
|
1115
|
-
}
|
|
1116
|
-
return outcome;
|
|
1117
|
-
});
|
|
1118
|
-
return { outcomes, ...(failFast.tripped ? { failFastReason: failFast.reason } : {}) };
|
|
1119
|
-
}
|
|
1120
|
-
/** Project one lane outcome (or a dry-run contract spec) into the public CuaLaneResult. */
|
|
1121
|
-
function toLaneResult(spec, outcome, subject, dryRun) {
|
|
1122
|
-
const base = {
|
|
1123
|
-
id: spec.laneId,
|
|
1124
|
-
...(spec.actorType === undefined ? {} : { actorType: spec.actorType }),
|
|
1125
|
-
...(spec.surface === undefined ? {} : { surface: spec.surface }),
|
|
1126
|
-
...(spec.caseGroup === undefined ? {} : { caseGroup: spec.caseGroup }),
|
|
1127
|
-
index: spec.laneIndex + 1,
|
|
1128
|
-
persona: spec.persona.id,
|
|
1129
|
-
device: spec.deviceName,
|
|
1130
|
-
resolution: spec.resolution,
|
|
1131
|
-
subject
|
|
1132
|
-
};
|
|
1133
|
-
if (!outcome || dryRun) {
|
|
1134
|
-
return { ...base, status: "contract_proof_only", ok: dryRun, diagnostics: cuaLaneDiagnostics({ dryRun }) };
|
|
1135
|
-
}
|
|
1136
|
-
if (outcome.skippedReason !== undefined) {
|
|
1137
|
-
return {
|
|
1138
|
-
...base,
|
|
1139
|
-
status: "blocked",
|
|
1140
|
-
ok: false,
|
|
1141
|
-
diagnostics: cuaLaneDiagnostics({ dryRun, skipped: true }),
|
|
1142
|
-
skippedReason: outcome.skippedReason,
|
|
1143
|
-
error: { code: "HUMANISH_CUA_LAB_FAILED", message: outcome.skippedReason }
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
const session = outcome.session;
|
|
1147
|
-
const laneOk = laneOutcomeOk(outcome, dryRun);
|
|
1148
|
-
const status = session ? session.status : "failed";
|
|
1149
|
-
return {
|
|
1150
|
-
...base,
|
|
1151
|
-
status,
|
|
1152
|
-
ok: laneOk,
|
|
1153
|
-
diagnostics: cuaLaneDiagnostics({
|
|
1154
|
-
dryRun, executionError: outcome.sessionError !== undefined, noEngagement: outcome.noEngagement,
|
|
1155
|
-
...(session ? { session: { status: session.status, completionReason: session.completionReason, ...(session.trace.stopCause === undefined ? {} : { stopCause: session.trace.stopCause }) } } : {})
|
|
1156
|
-
}),
|
|
1157
|
-
...(session
|
|
1158
|
-
? {
|
|
1159
|
-
session: {
|
|
1160
|
-
status: session.status,
|
|
1161
|
-
completionReason: session.completionReason,
|
|
1162
|
-
...(session.trace.stopCause === undefined ? {} : { stopCause: session.trace.stopCause }),
|
|
1163
|
-
reason: session.reason,
|
|
1164
|
-
screenshots: outcome.screenshots.length
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
: {}),
|
|
1168
|
-
...(outcome.sandboxId === undefined
|
|
1169
|
-
? {}
|
|
1170
|
-
: { sandbox: { sandboxId: outcome.sandboxId, killed: outcome.killed, streamUrlPresent: outcome.streamUrlPresent } }),
|
|
1171
|
-
...(laneOk
|
|
1172
|
-
? {}
|
|
1173
|
-
: {
|
|
1174
|
-
error: {
|
|
1175
|
-
code: outcome.failureCode ?? "HUMANISH_CUA_LAB_FAILED",
|
|
1176
|
-
message: outcome.sessionError
|
|
1177
|
-
?? (outcome.noEngagement
|
|
1178
|
-
? "Actor took no actions and produced no message (likely a blank/still-loading screen); not a credible goal_satisfied."
|
|
1179
|
-
: outcome.selfReportedBlocker
|
|
1180
|
-
? "Actor reported goal_satisfied while its final message described a blocker or asked for missing instructions; not a credible pass."
|
|
1181
|
-
: session?.completionReason === "harness_error"
|
|
1182
|
-
? `Computer-use session ended with a harness error: ${session.reason}`
|
|
1183
|
-
: session?.status !== "passed"
|
|
1184
|
-
? `Computer-use session ended with ${session?.status ?? "unknown"}: ${session?.reason ?? "no terminal reason"}`
|
|
1185
|
-
: "Computer-use lab did not produce a terminal session.")
|
|
1186
|
-
}
|
|
1187
|
-
})
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
function laneOutcomeOk(outcome, dryRun) {
|
|
1191
|
-
if (dryRun)
|
|
1192
|
-
return true;
|
|
1193
|
-
if (!outcome || outcome.skippedReason !== undefined)
|
|
1194
|
-
return false;
|
|
1195
|
-
return outcome.session !== undefined
|
|
1196
|
-
&& outcome.session.status === "passed"
|
|
1197
|
-
&& outcome.session.completionReason !== "harness_error"
|
|
1198
|
-
&& outcome.sessionError === undefined
|
|
1199
|
-
&& !outcome.noEngagement
|
|
1200
|
-
&& !outcome.selfReportedBlocker;
|
|
1201
|
-
}
|
|
1202
|
-
function fanoutReviewVerdict(args) {
|
|
1203
|
-
if (args.dryRun)
|
|
1204
|
-
return "contract_proof_only";
|
|
1205
|
-
const outcomes = args.outcomes ?? [];
|
|
1206
|
-
if (outcomes.length !== args.expectedLaneCount)
|
|
1207
|
-
return "fail";
|
|
1208
|
-
if (outcomes.some((outcome) => !laneOutcomeOk(outcome, false))) {
|
|
1209
|
-
return outcomes.some((outcome) => outcome.session?.status === "timed_out")
|
|
1210
|
-
? "timed_out"
|
|
1211
|
-
: "fail";
|
|
1212
|
-
}
|
|
1213
|
-
return "pass";
|
|
1214
|
-
}
|
|
1215
|
-
/** Build the per-lane subject projection (invariant 5). Local-tree lanes all share ONE
|
|
1216
|
-
* host-packed archive, so every lane's projection carries the identical archiveSha256/
|
|
1217
|
-
* commit/dirty (no per-lane divergence is possible, unlike the clone route's per-lane
|
|
1218
|
-
* in-sandbox commit). */
|
|
1219
|
-
function laneSubjectProjection(args) {
|
|
1220
|
-
if (args.cloneRoute && args.publicRepo) {
|
|
1221
|
-
return {
|
|
1222
|
-
source: "clone",
|
|
1223
|
-
repo: args.publicRepo,
|
|
1224
|
-
...(args.subjectCommit === undefined ? {} : { commit: args.subjectCommit }),
|
|
1225
|
-
envNames: args.subjectEnvNames,
|
|
1226
|
-
state: args.subjectState
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
if (args.localTreeRoute) {
|
|
1230
|
-
const archive = args.localTreeArchive;
|
|
1231
|
-
return {
|
|
1232
|
-
source: "local-tree",
|
|
1233
|
-
...(archive === undefined ? {} : { archiveSha256: archive.archiveSha256 }),
|
|
1234
|
-
...(archive?.git === undefined ? {} : { commit: archive.git.commit, dirty: archive.git.dirty }),
|
|
1235
|
-
envNames: args.subjectEnvNames,
|
|
1236
|
-
state: args.subjectState
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
return { source: "app-url", state: args.subjectState };
|
|
1240
|
-
}
|
|
1241
|
-
/** Narrow a resolved CuaSubjectProjection into the shape buildCuaBundle/buildSingleLaneBundle's
|
|
1242
|
-
* subjectProvenance param wants (provisioned-route sources only; app-url stays undeclared, the
|
|
1243
|
-
* default branch buildCuaBundle already handles). */
|
|
1244
|
-
function subjectProvenanceArg(subject, publicRepo, subjectEnvNames) {
|
|
1245
|
-
if (subject.source === "clone" && publicRepo) {
|
|
1246
|
-
return {
|
|
1247
|
-
source: "clone",
|
|
1248
|
-
repo: publicRepo,
|
|
1249
|
-
...(subject.commit === undefined ? {} : { commit: subject.commit }),
|
|
1250
|
-
envNames: subjectEnvNames,
|
|
1251
|
-
state: subject.state
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
if (subject.source === "local-tree") {
|
|
1255
|
-
return {
|
|
1256
|
-
source: "local-tree",
|
|
1257
|
-
...(subject.archiveSha256 === undefined ? {} : { archiveSha256: subject.archiveSha256 }),
|
|
1258
|
-
...(subject.commit === undefined ? {} : { commit: subject.commit }),
|
|
1259
|
-
...(subject.dirty === undefined ? {} : { dirty: subject.dirty }),
|
|
1260
|
-
envNames: subjectEnvNames,
|
|
1261
|
-
state: subject.state
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
return undefined;
|
|
1265
|
-
}
|
|
1266
|
-
/**
|
|
1267
|
-
* Wrapped so a DIRECT library caller gets the same status-record lifetime the CLI does: returning
|
|
1268
|
-
* from this function finalizes any record the run opened, whichever of its fail-closed exits it
|
|
1269
|
-
* took. `runLab` establishes a scope too and nesting is harmless — the inner scope owns what it
|
|
1270
|
-
* opened. Without this a test or an adopter calling the backend directly leaves the 5s cadence
|
|
1271
|
-
* ticking into a directory something else is deleting, which surfaces as an unrelated ENOTEMPTY.
|
|
1272
|
-
*/
|
|
1273
|
-
export async function runCuaActorLab(options) {
|
|
1274
|
-
return withTransientCommsSecrets(() => runCuaActorLabWithSecrets(options));
|
|
1275
|
-
}
|
|
1276
|
-
async function runCuaActorLabWithSecrets(options) {
|
|
1277
|
-
const analysisReason = resolveAutomaticAnalysis(options.config.review?.analysis);
|
|
1278
|
-
const tasksReason = analysisReason.ok ? taskProtocolValidationReason(options.config, true) : analysisReason.message;
|
|
1279
|
-
if (tasksReason)
|
|
1280
|
-
return {
|
|
1281
|
-
schema: CUA_ACTOR_LAB_SCHEMA, ok: false, cwd: path.resolve(options.cwd), labId: options.config.id,
|
|
1282
|
-
actor: options.config.actors[0]?.type ?? "", dryRun: options.dryRun,
|
|
1283
|
-
runId: options.runId ?? "not-created", appUrl: options.config.subject.appUrl ?? options.config.subject.serve?.url ?? "", lanes: [], warnings: [],
|
|
1284
|
-
error: { code: analysisReason.ok ? "HUMANISH_LAB_TASKS_UNSUPPORTED" : "HUMANISH_LAB_ANALYSIS_INVALID", message: tasksReason }
|
|
1285
|
-
};
|
|
1286
|
-
const analysis = resolveAutomaticAnalysis(options.config.review?.analysis);
|
|
1287
|
-
const result = await withRunStatusScope(() => runCuaActorLabInScope(options));
|
|
1288
|
-
return completeAutomaticAnalysis(result, analysis.ok ? analysis.config : undefined, options.automaticAnalysis, options.config.review?.analysis === undefined ? "default" : "explicit", analysis.ok && analysis.preferLargerOutput === true);
|
|
1289
|
-
}
|
|
1290
|
-
async function runCuaActorLabInScope(options) {
|
|
1291
|
-
const { config, dryRun } = options;
|
|
1292
|
-
// Capture the physical project before reading or invoking any caller hook. A supported
|
|
1293
|
-
// symlink cwd remains valid, but retargeting that alias from a hook cannot redirect source
|
|
1294
|
-
// reads, local-tree packing, managed run storage, or Observer output into another project.
|
|
1295
|
-
const physicalCwd = await realpath(path.resolve(options.cwd));
|
|
1296
|
-
const projectRoot = await prepareSelectedOutputDirectory(path.dirname(physicalCwd), physicalCwd);
|
|
1297
|
-
const cwd = projectRoot.physicalPath;
|
|
1298
|
-
const hooks = options.hooks ?? {};
|
|
1299
|
-
let liveObserver;
|
|
1300
|
-
const runtimeStreamUrls = [];
|
|
1301
|
-
const liveHooks = {
|
|
1302
|
-
...hooks,
|
|
1303
|
-
onRuntimeStreamReady: async (stream) => {
|
|
1304
|
-
await hooks.onRuntimeStreamReady?.(stream);
|
|
1305
|
-
runtimeStreamUrls.push({ streamId: stream.streamId, url: stream.url });
|
|
1306
|
-
if (liveObserver) {
|
|
1307
|
-
attachObserverRuntimeStreamUrls(liveObserver, runtimeStreamUrls);
|
|
1308
|
-
}
|
|
1309
|
-
},
|
|
1310
|
-
onRuntimeStreamEnded: async (stream) => {
|
|
1311
|
-
await hooks.onRuntimeStreamEnded?.(stream);
|
|
1312
|
-
// Mark, never remove: the tile needs to KNOW the live view ended (and say so) rather than
|
|
1313
|
-
// have the stream silently vanish from the overlay (#357).
|
|
1314
|
-
for (const entry of runtimeStreamUrls) {
|
|
1315
|
-
if (entry.streamId === stream.streamId)
|
|
1316
|
-
entry.ended = true;
|
|
1317
|
-
}
|
|
1318
|
-
if (liveObserver) {
|
|
1319
|
-
attachObserverRuntimeStreamUrls(liveObserver, runtimeStreamUrls);
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
};
|
|
1323
|
-
const env = hooks.env ?? process.env;
|
|
1324
|
-
const render = hooks.renderObserverFn ?? renderObserver;
|
|
1325
|
-
const cloneRoute = config.subject.source === "clone";
|
|
1326
|
-
// A CLI studied at a desktop (#495): nothing cloned, no browser, a terminal instead.
|
|
1327
|
-
const desktopCliRoute = config.subject.source === "desktop-cli";
|
|
1328
|
-
const localTreeRoute = config.subject.source === "local-tree";
|
|
1329
|
-
// Both routes provision the subject in-sandbox (clone via git, local-tree via pack+upload)
|
|
1330
|
-
// and then share the identical install/build/state/start/probe pipeline, so every seam that
|
|
1331
|
-
// gates on "does this route provision a subject" is keyed on this union, not on cloneRoute
|
|
1332
|
-
// alone.
|
|
1333
|
-
const provisionedRoute = cloneRoute || localTreeRoute;
|
|
1334
|
-
const serve = config.subject.serve;
|
|
1335
|
-
const appUrl = (provisionedRoute ? serve?.url : config.subject.appUrl) ?? "";
|
|
1336
|
-
const subjectRepo = cloneRoute ? config.subject.repos?.[0] ?? "" : undefined;
|
|
1337
|
-
const subjectEnvNames = provisionedRoute ? config.subject.env ?? [] : [];
|
|
1338
|
-
const actor = config.actors[0];
|
|
1339
|
-
const actorType = actor?.type ?? "";
|
|
1340
|
-
const fail = (code, message, actorLabel) => ({
|
|
1341
|
-
schema: CUA_ACTOR_LAB_SCHEMA,
|
|
1342
|
-
ok: false,
|
|
1343
|
-
cwd,
|
|
1344
|
-
labId: config.id,
|
|
1345
|
-
actor: actorLabel ?? actorType,
|
|
1346
|
-
appUrl,
|
|
1347
|
-
dryRun,
|
|
1348
|
-
runId: options.runId ?? "not-created",
|
|
1349
|
-
lanes: [],
|
|
1350
|
-
warnings: [],
|
|
1351
|
-
error: { code, message }
|
|
1352
|
-
});
|
|
1353
|
-
// Resolve the actor through the registry — the parse layer validated this, but the engine fails
|
|
1354
|
-
// closed rather than trusting a config that arrived through another door.
|
|
1355
|
-
const descriptor = actorRegistry[actorType];
|
|
1356
|
-
if (!descriptor || !isCuaActorDescriptor(descriptor)) {
|
|
1357
|
-
return fail("HUMANISH_CUA_LAB_ACTOR_UNSUPPORTED", `actors[0].type "${actorType}" is not a registered computer-use actor.`);
|
|
1358
|
-
}
|
|
1359
|
-
const runSession = hooks.runSession ?? descriptor.runSession;
|
|
1360
|
-
const mediaReason = desktopMediaValidationReason(config);
|
|
1361
|
-
if (mediaReason)
|
|
1362
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", mediaReason, descriptor.id);
|
|
1363
|
-
const outputLimitReason = outputTokenLimitValidationReason(config);
|
|
1364
|
-
if (outputLimitReason)
|
|
1365
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", outputLimitReason, descriptor.id);
|
|
1366
|
-
if (actor?.maxOutputTokens !== undefined && (hooks.runSession || hooks.buildProvider || hooks.buildExecutor)) {
|
|
1367
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", "maxOutputTokens cannot be enforced by a custom runSession/provider/executor route.", descriptor.id);
|
|
1368
|
-
}
|
|
1369
|
-
const receivingReason = receivingEmailValidationReason(config);
|
|
1370
|
-
if (receivingReason)
|
|
1371
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", receivingReason, descriptor.id);
|
|
1372
|
-
const inProcessRoute = hooks.buildExecutor !== undefined;
|
|
1373
|
-
if (inProcessRoute && config.comms?.email?.kind === "real")
|
|
1374
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", "Real email receiving requires hosted participant desktops.", descriptor.id);
|
|
1375
|
-
if (inProcessRoute && config.execution?.desktop?.media !== undefined) {
|
|
1376
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", "execution.desktop.media is not provisioned by a caller-supplied executor. Remove the declaration or use a hosted computer-use browser lane.", descriptor.id);
|
|
1377
|
-
}
|
|
1378
|
-
if (inProcessRoute && config.execution?.desktop?.recording !== undefined) {
|
|
1379
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", "execution.desktop.recording is not provisioned by a caller-supplied executor.", descriptor.id);
|
|
1380
|
-
}
|
|
1381
|
-
const localAppSubject = config.subject.source === "local-app";
|
|
1382
|
-
// Adopter-hosted comms plane on the app-url route (#380): humanish provisions no subject here,
|
|
1383
|
-
// so it cannot host a catch — the OPERATOR runs one, and humanish still does every other part
|
|
1384
|
-
// of the funnel: tells each persona its address and inbox URL, drains the catch over HTTP after
|
|
1385
|
-
// the lanes, and writes the same digest-only evidence. Declaring `external` previously did
|
|
1386
|
-
// nothing on this route (and, per #387, on every other) while its docs said otherwise.
|
|
1387
|
-
const externalCommsConfig = !cloneRoute && !localTreeRoute && !inProcessRoute
|
|
1388
|
-
? config.comms?.email?.external
|
|
1389
|
-
: undefined;
|
|
1390
|
-
const externalCommsEmail = externalCommsConfig ? config.comms?.email : undefined;
|
|
1391
|
-
// Engine re-enforcement of the clone-route structure (library API surface).
|
|
1392
|
-
if (cloneRoute && (!serve || !subjectRepo || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(subjectRepo))) {
|
|
1393
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", !serve
|
|
1394
|
-
? "clone subjects on the computer-use route require `subject.serve` (start + url) — the lab serves the app in-sandbox."
|
|
1395
|
-
: `subject.repos[0] must be an owner/repo slug (got "${subjectRepo ?? ""}").`, descriptor.id);
|
|
1396
|
-
}
|
|
1397
|
-
// Engine re-enforcement of the local-tree-route structure (library API surface): a caller
|
|
1398
|
-
// driving this function directly (bypassing parseLabConfig) still gets the same fail-closed
|
|
1399
|
-
// shape the parser enforces, naming which requirement is missing.
|
|
1400
|
-
if (localTreeRoute && (!serve || config.execution?.target !== "e2b-desktop")) {
|
|
1401
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", !serve
|
|
1402
|
-
? "local-tree subjects on the computer-use route require `subject.serve` (start + url): the lab packs and serves the working tree in-sandbox."
|
|
1403
|
-
: "local-tree subjects require `execution.target: e2b-desktop`: the packed working tree is provisioned and served inside a hosted desktop sandbox.", descriptor.id);
|
|
1404
|
-
}
|
|
1405
|
-
// Engine re-enforcement of the state declaration (library API surface).
|
|
1406
|
-
if (config.subject.state) {
|
|
1407
|
-
const stateReason = !provisionedRoute
|
|
1408
|
-
? "`subject.state` applies only to clone subjects or local-tree subjects (the lab seeds the state it serves)."
|
|
1409
|
-
: subjectStateInvalidReason(config.subject.state, config.subject.env);
|
|
1410
|
-
if (stateReason) {
|
|
1411
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", stateReason, descriptor.id);
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
// Re-enforce the entry-target boundary (library API surface). A desktop-cli study has no entry
|
|
1415
|
-
// target at all — the subject is a program on the machine, not an address — so the boundary is
|
|
1416
|
-
// vacuous there rather than violated by an empty string.
|
|
1417
|
-
const allowPublicTargets = config.policies?.allowPublicTargets === true;
|
|
1418
|
-
const declaredTargets = [appUrl, ...(actor?.lanes ?? []).map((lane) => lane.target).filter((target) => target !== undefined)];
|
|
1419
|
-
const entryTargetSafe = desktopCliRoute || declaredTargets.every((target) => provisionedRoute || localAppSubject
|
|
1420
|
-
? isLoopbackUrl(target)
|
|
1421
|
-
: allowPublicTargets
|
|
1422
|
-
? isHttpUrl(target)
|
|
1423
|
-
: isLoopbackUrl(target));
|
|
1424
|
-
if (!entryTargetSafe) {
|
|
1425
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_UNSAFE", provisionedRoute || localAppSubject || !allowPublicTargets
|
|
1426
|
-
? "subject.appUrl and any actors[0].lanes[].target entries must be loopback (127.0.0.1 or localhost) unless policies.allowPublicTargets is set for an app-url subject."
|
|
1427
|
-
: "subject.appUrl and actors[0].lanes[].target entries must be valid http(s) URLs.", descriptor.id);
|
|
1428
|
-
}
|
|
1429
|
-
// In-process route pairing guard (boot-time, BEFORE key-gating): a custom executor needs a
|
|
1430
|
-
// custom provider too (the default OpenAI provider is vision-based and would fail closed).
|
|
1431
|
-
if (hooks.buildExecutor !== undefined && hooks.buildProvider === undefined) {
|
|
1432
|
-
return fail("HUMANISH_CUA_LAB_EXECUTOR_NO_PROVIDER", "cuaHooks.buildExecutor requires cuaHooks.buildProvider — a state-driven executor returns no screenshot, so it must be paired with a NON-vision provider (the default OpenAI computer-use provider is vision-based and would fail closed).", descriptor.id);
|
|
1433
|
-
}
|
|
1434
|
-
// local-app fail-closed (BEFORE key-gating): there is no built-in in-process driver.
|
|
1435
|
-
if (localAppSubject && !inProcessRoute) {
|
|
1436
|
-
return fail("HUMANISH_CUA_LAB_LOCAL_APP_NO_EXECUTOR", "subject.source: local-app requires a library caller to supply cuaHooks.buildExecutor + buildProvider; there is no built-in driver for an in-process JS contract. (Drive the app via runLab(..., { cuaHooks: { buildExecutor, buildProvider } }).)", descriptor.id);
|
|
1437
|
-
}
|
|
1438
|
-
if (config.subject.source === "app-url" && config.execution?.target === "local" && !hooks.createDesktopLane) {
|
|
1439
|
-
return fail("HUMANISH_CUA_LAB_LOCAL_APP_NO_EXECUTOR", "Local browser studies require a configured local desktop runtime.", descriptor.id);
|
|
1440
|
-
}
|
|
1441
|
-
// Re-enforce the fan-out cross-validation (library API surface): lanes XOR count/laneFocus,
|
|
1442
|
-
// device XOR raw resolution, cap, unique ids, allowPublicTargets+N>1, clone.fanout.
|
|
1443
|
-
const fanoutReason = cuaLaneValidationReason(config);
|
|
1444
|
-
if (fanoutReason) {
|
|
1445
|
-
return fail("HUMANISH_CUA_LAB_FANOUT_INVALID", fanoutReason, descriptor.id);
|
|
1446
|
-
}
|
|
1447
|
-
// The sandbox deadline is DERIVED from the session budget, so a lab can ask for a session that
|
|
1448
|
-
// cannot legally be provisioned. Catch it here, before anything is created, and show the
|
|
1449
|
-
// arithmetic — the provider's own error names a limit but not which knob produced it.
|
|
1450
|
-
const derivedSandboxMs = resolvePerLaneSandboxMs(config);
|
|
1451
|
-
if (derivedSandboxMs > MAX_SANDBOX_MS) {
|
|
1452
|
-
const provisionedRoute = config.subject.source === "clone" || config.subject.source === "local-tree";
|
|
1453
|
-
const sessionMs = config.execution?.timeoutMs ?? defaultSessionTimeoutMs(config);
|
|
1454
|
-
const headroomMs = derivedSandboxMs - sessionMs;
|
|
1455
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", `execution.timeoutMs ${Math.round(sessionMs / 60_000)}m derives a ${Math.round(derivedSandboxMs / 60_000)}m sandbox deadline, and a sandbox may not live longer than ${MAX_SANDBOX_MS / 60_000}m. The deadline is the session budget plus ${Math.round(headroomMs / 60_000)}m of provisioning and teardown headroom${provisionedRoute ? " (this route clones, installs, builds and serves the subject before the actor starts)" : ""}. Lower execution.timeoutMs to at most ${Math.round((MAX_SANDBOX_MS - headroomMs) / 60_000)}m, or set execution.desktop.sandboxTimeoutMs explicitly.`, descriptor.id);
|
|
1456
|
-
}
|
|
1457
|
-
// Compile any committed personas BEFORE planning, so the plan builder stays pure and each lane's
|
|
1458
|
-
// prompt carries real behavioral directives rather than a bare `Persona: <id>.` label (#381).
|
|
1459
|
-
const personaResolution = await resolveCommittedPersonas(projectRoot, labPersonaIds(config));
|
|
1460
|
-
for (const warning of personaResolution.warnings) {
|
|
1461
|
-
process.stderr.write(`humanish: ${warning}\n`);
|
|
1462
|
-
}
|
|
1463
|
-
// Resolve the lane plan (pure) — the SAME table for dry-run and live.
|
|
1464
|
-
let { lanes: laneSpecs, plan } = laneSpecsAndPlan(config, {
|
|
1465
|
-
...(options.countOverride === undefined ? {} : { countOverride: options.countOverride }),
|
|
1466
|
-
env,
|
|
1467
|
-
dryRun,
|
|
1468
|
-
personas: personaResolution.personas
|
|
1469
|
-
});
|
|
1470
|
-
let laneCount = laneSpecs.length;
|
|
1471
|
-
if (laneCount > MAX_CUA_LANES) {
|
|
1472
|
-
return fail("HUMANISH_CUA_LAB_FANOUT_INVALID", `Computer-use fan-out is capped at ${MAX_CUA_LANES} lanes (resolved ${laneCount}); N concurrent paid desktops is real spend.`, descriptor.id);
|
|
1473
|
-
}
|
|
1474
|
-
if (inProcessRoute && laneCount > 1) {
|
|
1475
|
-
return fail("HUMANISH_CUA_LAB_FANOUT_INVALID", "Multi-lane fan-out is not supported on the in-process route (cuaHooks.buildExecutor) — fan-out provisions one independent E2B desktop per lane, which the in-process route deliberately skips. Run a single in-process lane, or fan out on the E2B route.", descriptor.id);
|
|
1476
|
-
}
|
|
1477
|
-
let rerunLineage;
|
|
1478
|
-
if (options.rerun) {
|
|
1479
|
-
const selected = await resolveCuaRerunSelection({
|
|
1480
|
-
cwd,
|
|
1481
|
-
config,
|
|
1482
|
-
sourceRunId: options.rerun.sourceRunId,
|
|
1483
|
-
...(options.rerun.laneIds === undefined ? {} : { laneIds: options.rerun.laneIds }),
|
|
1484
|
-
laneSpecs,
|
|
1485
|
-
plan
|
|
1486
|
-
});
|
|
1487
|
-
if (!selected.ok) {
|
|
1488
|
-
return fail("HUMANISH_CUA_LAB_RERUN_INVALID", selected.message, descriptor.id);
|
|
1489
|
-
}
|
|
1490
|
-
laneSpecs = selected.laneSpecs;
|
|
1491
|
-
plan = selected.plan;
|
|
1492
|
-
laneCount = laneSpecs.length;
|
|
1493
|
-
rerunLineage = selected.rerun;
|
|
1494
|
-
}
|
|
1495
|
-
// Pre-flight plan: BEFORE any sandbox or provider call (dry-run AND live). The hook fires for
|
|
1496
|
-
// every N (observable + testable); the stderr table prints for fan-out (N>1) so single-lane
|
|
1497
|
-
// runs stay as quiet as they always were.
|
|
1498
|
-
if (laneCount > 1) {
|
|
1499
|
-
emitPreflightPlan(plan, config.id);
|
|
1500
|
-
}
|
|
1501
|
-
hooks.onPreflight?.(plan);
|
|
1502
|
-
await assertPreparedSelectedOutputDirectory(projectRoot);
|
|
1503
|
-
// Read keys once into locals (names only; values never logged or persisted).
|
|
1504
|
-
const openaiApiKey = env.OPENAI_API_KEY?.trim() ?? "";
|
|
1505
|
-
const e2bApiKey = env.E2B_API_KEY?.trim() ?? "";
|
|
1506
|
-
// Literal scrubber for every known provisioned value (no secret "shape" to pattern-match).
|
|
1507
|
-
const knownSecretValues = [
|
|
1508
|
-
openaiApiKey,
|
|
1509
|
-
e2bApiKey,
|
|
1510
|
-
...subjectEnvNames.map((name) => env[name] ?? "")
|
|
1511
|
-
].filter((value) => value.length >= 4);
|
|
1512
|
-
const scrubKnownValues = (text) => knownSecretValues.reduce((current, value) => current.split(value).join("[REDACTED_SECRET]"), text);
|
|
1513
|
-
// Sanitize the declarative snapshot before initial, partial, or final bundle construction.
|
|
1514
|
-
for (const spec of laneSpecs) {
|
|
1515
|
-
if (spec.assignment)
|
|
1516
|
-
spec.assignment = participantAssignment(spec.assignment, scrubKnownValues);
|
|
1517
|
-
spec.evidenceInstructions = redactText(scrubKnownValues(spec.instructions));
|
|
1518
|
-
}
|
|
1519
|
-
const redactRepoLabel = config.policies?.redactRepos ?? subjectEnvNames.includes("GITHUB_TOKEN");
|
|
1520
|
-
const publicRepo = cloneRoute && subjectRepo ? (redactRepoLabel ? "repo-01" : subjectRepo) : undefined;
|
|
1521
|
-
const hasGithubToken = subjectEnvNames.includes("GITHUB_TOKEN");
|
|
1522
|
-
// The operator's own signed-in coding agent is the brain, so there is no provider key to ask
|
|
1523
|
-
// for — the entire point of the actor. E2B is still required: the persona needs a machine.
|
|
1524
|
-
const localAgentRoute = actorType === "local-agent";
|
|
1525
|
-
// Which local CLI, from its OWN field: `model` means the model, so that "Claude Code running
|
|
1526
|
-
// Opus" is sayable. Preflight below refuses when the chosen one is missing or signed out — that
|
|
1527
|
-
// news is worthless after a sandbox is paid for.
|
|
1528
|
-
const preferredLocalAgent = config.actors[0]?.localAgent ?? "codex";
|
|
1529
|
-
// Key-gating is route-aware: the in-process route uses the caller's OWN model + executor, and
|
|
1530
|
-
// the local-agent route uses a CLI the operator has already signed in to.
|
|
1531
|
-
if (!dryRun && !inProcessRoute) {
|
|
1532
|
-
const missingKeys = [
|
|
1533
|
-
...(openaiApiKey || localAgentRoute || hooks.buildProvider ? [] : ["OPENAI_API_KEY"]),
|
|
1534
|
-
...(e2bApiKey || hooks.createDesktopLane ? [] : ["E2B_API_KEY"])
|
|
1535
|
-
];
|
|
1536
|
-
if (missingKeys.length > 0) {
|
|
1537
|
-
// The moment someone new actually hits the wall. If a signed-in coding agent is sitting
|
|
1538
|
-
// right there, say so HERE rather than making them go and find an API key — that detour is
|
|
1539
|
-
// where most people trying humanish stop.
|
|
1540
|
-
const suggestion = missingKeys.includes("OPENAI_API_KEY")
|
|
1541
|
-
? await (async () => {
|
|
1542
|
-
const ready = (await detectLocalAgents({ env })).filter((agent) => agent.authStatus === "authenticated");
|
|
1543
|
-
return ready.length === 0
|
|
1544
|
-
? ""
|
|
1545
|
-
: ` ${ready.map((agent) => agent.label).join(" and ")} reports authenticated on this machine`
|
|
1546
|
-
+ ` — set actors[0].type: local-agent to use ${ready.length === 1 ? "it" : "one"} instead of a key.`;
|
|
1547
|
-
})()
|
|
1548
|
-
: "";
|
|
1549
|
-
return fail("HUMANISH_CUA_LAB_KEYS_MISSING", `Live computer-use labs need ${missingKeys.join(" and ")} in the environment (values are never persisted). ${describeMissingKeys(missingKeys, env)}${suggestion}`, descriptor.id);
|
|
1550
|
-
}
|
|
1551
|
-
if (localAgentRoute && !hooks.buildProvider) {
|
|
1552
|
-
// Refuse HERE, before a sandbox exists. "codex is not installed" discovered after the
|
|
1553
|
-
// machine is paid for is the same information delivered at the worst possible moment.
|
|
1554
|
-
const available = await detectLocalAgents({ env });
|
|
1555
|
-
const chosen = available.find((agent) => agent.id === preferredLocalAgent);
|
|
1556
|
-
if (chosen === undefined) {
|
|
1557
|
-
return fail("HUMANISH_CUA_LAB_KEYS_MISSING", `actors[0].type: local-agent needs the ${preferredLocalAgent} CLI on PATH and signed in. `
|
|
1558
|
-
+ `Install it, or set OPENAI_API_KEY and use actors[0].type: openai-computer-use instead.`, descriptor.id);
|
|
1559
|
-
}
|
|
1560
|
-
if (chosen.authStatus !== "authenticated") {
|
|
1561
|
-
return fail("HUMANISH_CUA_LAB_KEYS_MISSING", chosen.authStatus === "unauthenticated"
|
|
1562
|
-
? `${chosen.label} reports not signed in — run \`${chosen.id === "codex" ? "codex login" : "claude auth login"}\`, then retry.`
|
|
1563
|
-
: `${chosen.label} authentication status could not be checked. Run \`${chosen.id === "codex" ? "codex login status" : "claude auth status"}\` and update the CLI if needed. No desktop was launched.`, descriptor.id);
|
|
1564
|
-
}
|
|
1565
|
-
if (chosen.id === "codex") {
|
|
1566
|
-
const compatibility = await checkHostedCodexCompatibility(chosen.binPath, { env });
|
|
1567
|
-
if (compatibility !== "supported") {
|
|
1568
|
-
return fail("HUMANISH_CUA_LAB_ACTOR_UNSUPPORTED", compatibility === "unsupported_platform"
|
|
1569
|
-
? `Hosted Codex participants require Linux or macOS on x64 or arm64. This host is ${process.platform}/${process.arch}; no desktop was launched.`
|
|
1570
|
-
: `Hosted Codex participants require Codex CLI 0.154.0. Run \`codex --version\` and install the supported version before retrying; no desktop was launched.`, descriptor.id);
|
|
1571
|
-
}
|
|
1572
|
-
if (chosen.billing === "account-unknown" &&
|
|
1573
|
-
(config.execution?.caps?.maxUsd !== undefined || config.execution?.caps?.maxTotalUsd !== undefined)) {
|
|
1574
|
-
return fail("HUMANISH_CUA_LAB_UNPRICED_CAP", "A ChatGPT-account Codex participant has no API-dollar price, so execution.caps.maxUsd/maxTotalUsd cannot be enforced. Remove the dollar cap and use finite execution timeout/step limits, or use an API-backed participant; no desktop was launched.", descriptor.id);
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
const missingSubjectEnv = subjectEnvNames.filter((name) => !env[name]?.trim());
|
|
1579
|
-
if (missingSubjectEnv.length > 0) {
|
|
1580
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_ENV_MISSING", `subject.env declares ${missingSubjectEnv.join(", ")} but the environment does not provide ${missingSubjectEnv.length === 1 ? "it" : "them"} (pass via --env-file; values are never persisted).`, descriptor.id);
|
|
1581
|
-
}
|
|
1582
|
-
// FAIL-CLOSED CAP TENSION (discipline #3): a maxUsd cap needs a MEASURABLE per-turn estimate.
|
|
1583
|
-
// If the operator set execution.caps.maxUsd but src/pricing.ts has no rate for the resolved
|
|
1584
|
-
// model, the loop could not enforce the cap — and silently running uncapped would break the
|
|
1585
|
-
// runaway-retry protection. Refuse at PREFLIGHT (before any sandbox/spend) rather than run
|
|
1586
|
-
// uncapped: an unenforceable cap is more dangerous than none. The operator adds a rate to
|
|
1587
|
-
// src/pricing.ts (the honest place) or removes the cap.
|
|
1588
|
-
if (config.execution?.caps?.maxUsd !== undefined || config.execution?.caps?.maxTotalUsd !== undefined) {
|
|
1589
|
-
const capModelId = (config.actors[0]?.model ?? DEFAULT_OPENAI_CU_MODEL).trim().toLowerCase();
|
|
1590
|
-
if (!MODEL_RATES[capModelId]) {
|
|
1591
|
-
return fail("HUMANISH_CUA_LAB_UNPRICED_CAP", `execution.caps declares a spend cap (maxUsd/maxTotalUsd) but src/pricing.ts has no rate for model "${config.actors[0]?.model ?? DEFAULT_OPENAI_CU_MODEL}"; add a rate or remove the cap — an unenforceable cap is refused rather than run uncapped.`, descriptor.id);
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
// Adopter-hosted comms catch (#380): fail closed BEFORE any sandbox is created — a comms lab
|
|
1595
|
-
// whose catch is unreachable collects nothing while every lane still spends. The probe asserts
|
|
1596
|
-
// OUR service marker in /health, so an adopter's proxy answering 200 for everything cannot
|
|
1597
|
-
// pass for a catch.
|
|
1598
|
-
if (externalCommsConfig && !(await externalCatchHealthy(externalCommsConfig))) {
|
|
1599
|
-
return fail("HUMANISH_CUA_LAB_COMMS_CATCH_UNREACHABLE", "The external comms catch or inbox is unreachable or incompatible (GET /health must identify humanish-comms-catch and advertise recipient-inbox-v1). Update Humanish on the catch host and restart it with `humanish comms catch` on that host, or drop comms.email to run without the inbox funnel.", descriptor.id);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
const runId = options.runId ?? makeCuaRunId();
|
|
1603
|
-
const runPaths = await prepareRunArtifactPaths(cwd, runId);
|
|
1604
|
-
// Identity + liveness on disk from the first moment (#455): anything watching the runs
|
|
1605
|
-
// directory — the TUI, another terminal, an agent — can now tell which lab this is and that
|
|
1606
|
-
// it is alive, without waiting for the interactive observer flush that used to be the only
|
|
1607
|
-
// mid-run write. The success path finalizes it with the real outcome; the fail-closed returns
|
|
1608
|
-
// below do not, so `runLab`'s status scope finalizes those with no outcome. A crash reaches
|
|
1609
|
-
// neither and leaves the record stale, which reads as interrupted rather than as a lie.
|
|
1610
|
-
const runStatus = beginRunStatus(runPaths, {
|
|
1611
|
-
runId,
|
|
1612
|
-
mode: dryRun ? "dry-run" : "live",
|
|
1613
|
-
...(options.lab === undefined ? {} : { lab: options.lab })
|
|
1614
|
-
});
|
|
1615
|
-
const artifactRoot = runPaths.absoluteRunRoot;
|
|
1616
|
-
const physicalArtifactRoot = runPaths.physicalRunRoot;
|
|
1617
|
-
const createdAt = new Date().toISOString();
|
|
1618
|
-
const timeoutMs = config.execution?.timeoutMs ?? defaultSessionTimeoutMs(config);
|
|
1619
|
-
const requestTimeoutMs = readPositiveInt(env.HUMANISH_E2B_REQUEST_TIMEOUT_MS, 60_000);
|
|
1620
|
-
const redactScreenshots = config.policies?.redactScreenshots === true;
|
|
1621
|
-
await prepareContainedOutputDirectory(runPaths, "screenshots");
|
|
1622
|
-
const source = await buildRunSource({
|
|
1623
|
-
capturedAt: createdAt,
|
|
1624
|
-
cwd,
|
|
1625
|
-
humanishSource: "present",
|
|
1626
|
-
packageName: "humanish"
|
|
1627
|
-
});
|
|
1628
|
-
// Pack the working tree ONCE per run, on the host, BEFORE any sandbox or provider call: every
|
|
1629
|
-
// fan-out lane below uploads this SAME archive, so one archiveSha256 describes every lane's
|
|
1630
|
-
// digest. Dry-run packs nothing (no fs side effects; the contract bundle carries no
|
|
1631
|
-
// archiveSha256). A packing failure fails the run closed here, before createDesktopSandbox is
|
|
1632
|
-
// ever reached.
|
|
1633
|
-
let localTreeArchive;
|
|
1634
|
-
let localTreeArchiveBuffer;
|
|
1635
|
-
if (localTreeRoute && !dryRun) {
|
|
1636
|
-
const packLocalTree = hooks.packLocalTree ?? defaultPackLocalTree;
|
|
1637
|
-
try {
|
|
1638
|
-
const packed = await packLocalTree({
|
|
1639
|
-
root: cwd,
|
|
1640
|
-
...(config.subject.localTree?.exclude === undefined ? {} : { extraExclude: config.subject.localTree.exclude }),
|
|
1641
|
-
...(config.subject.localTree?.maxArchiveBytes === undefined ? {} : { maxArchiveBytes: config.subject.localTree.maxArchiveBytes })
|
|
1642
|
-
});
|
|
1643
|
-
localTreeArchive = packed.archive;
|
|
1644
|
-
localTreeArchiveBuffer = packed.buffer;
|
|
1645
|
-
// One operator-facing line (stderr, same channel as emitPreflightPlan): what left the
|
|
1646
|
-
// host, by counts and digest only, never paths or file names.
|
|
1647
|
-
process.stderr.write(`humanish local-tree: packed ${packed.archive.fileCount} entries, ${packed.archive.totalBytes} bytes, archiveSha256 ${packed.archive.archiveSha256}`
|
|
1648
|
-
+ `${packed.archive.git ? ` (commit ${packed.archive.git.commit.slice(0, 12)}, ${packed.archive.git.dirty ? "dirty" : "clean"} working tree)` : " (not a git work tree)"}\n`);
|
|
1649
|
-
}
|
|
1650
|
-
catch (error) {
|
|
1651
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", `local-tree packing failed: ${redactText(scrubKnownValues(toErrorMessage(error)))}`, descriptor.id);
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
// Live-trace flush seam (#441): assigned by the attached-Observer block below when a live
|
|
1655
|
-
// run has an in-progress bundle to grow; lanes call it through deps.onTrace. Declared here
|
|
1656
|
-
// (before deps) so deps can reference it as a stable indirection.
|
|
1657
|
-
let flushLiveTrace;
|
|
1658
|
-
let stopLiveFlush;
|
|
1659
|
-
const deps = {
|
|
1660
|
-
...(hooks.createDesktopLane ? { createDesktopLane: hooks.createDesktopLane } : {}),
|
|
1661
|
-
onTrace: (laneId, items, usage, metadata) => flushLiveTrace?.(laneId, items, usage, metadata),
|
|
1662
|
-
config,
|
|
1663
|
-
descriptor,
|
|
1664
|
-
appUrl,
|
|
1665
|
-
...(localAgentRoute ? { localAgent: preferredLocalAgent } : {}),
|
|
1666
|
-
cloneRoute,
|
|
1667
|
-
desktopCliRoute,
|
|
1668
|
-
localTreeRoute,
|
|
1669
|
-
...(serve === undefined ? {} : { serve }),
|
|
1670
|
-
...(subjectRepo === undefined ? {} : { subjectRepo }),
|
|
1671
|
-
subjectEnvNames,
|
|
1672
|
-
hasGithubToken,
|
|
1673
|
-
...(localTreeArchiveBuffer === undefined ? {} : { localTreeArchiveBuffer }),
|
|
1674
|
-
env,
|
|
1675
|
-
openaiApiKey,
|
|
1676
|
-
e2bApiKey,
|
|
1677
|
-
requestTimeoutMs,
|
|
1678
|
-
perLaneSandboxMs: resolvePerLaneSandboxMs(config),
|
|
1679
|
-
timeoutMs,
|
|
1680
|
-
laneCount,
|
|
1681
|
-
artifactRoot: runPaths,
|
|
1682
|
-
labCwd: options.cwd,
|
|
1683
|
-
redactScreenshots,
|
|
1684
|
-
scrubKnownValues,
|
|
1685
|
-
runSession,
|
|
1686
|
-
// The study-level ledger exists once per RUN, shared by every lane (#299). Dry runs never
|
|
1687
|
-
// spend, so they carry none.
|
|
1688
|
-
...(dryRun || config.execution?.caps?.maxTotalUsd === undefined
|
|
1689
|
-
? {}
|
|
1690
|
-
: { runBudget: makeCuaRunBudget(config.execution.caps.maxTotalUsd) }),
|
|
1691
|
-
...(externalCommsConfig === undefined || externalCommsEmail === undefined
|
|
1692
|
-
? {}
|
|
1693
|
-
: { externalComms: { email: externalCommsEmail, inboxUrl: externalInboxUrl(externalCommsConfig) } }),
|
|
1694
|
-
now: hooks.now ?? Date.now,
|
|
1695
|
-
hooks: liveHooks
|
|
1696
|
-
};
|
|
1697
|
-
const inProgressLaneSubjects = laneSpecs.map(() => laneSubjectProjection({
|
|
1698
|
-
cloneRoute,
|
|
1699
|
-
localTreeRoute,
|
|
1700
|
-
...(publicRepo === undefined ? {} : { publicRepo }),
|
|
1701
|
-
subjectEnvNames,
|
|
1702
|
-
...(localTreeArchive === undefined ? {} : { localTreeArchive }),
|
|
1703
|
-
subjectState: resolveSubjectState({
|
|
1704
|
-
declared: provisionedRoute ? config.subject.state : undefined,
|
|
1705
|
-
dryRun: false,
|
|
1706
|
-
executed: []
|
|
1707
|
-
})
|
|
1708
|
-
}));
|
|
1709
|
-
const inProgressAggregateSubject = inProgressLaneSubjects[0];
|
|
1710
|
-
const inProgressProvenance = subjectProvenanceArg(inProgressAggregateSubject, publicRepo, subjectEnvNames);
|
|
1711
|
-
// A live run writes what it is doing AS IT DOES IT, whether or not anyone is currently watching.
|
|
1712
|
-
// This used to be gated on `options.onObserverReady` — the interactive Observer callback — so a
|
|
1713
|
-
// run launched by an agent (`lab run --json`), detached, or from the terminal surface recorded
|
|
1714
|
-
// nothing at all until it completed, and anything asking "what is this participant doing right
|
|
1715
|
-
// now" got silence for the whole run. Who reads the evidence is not the run's business; the
|
|
1716
|
-
// callback below stays conditional, the writing does not.
|
|
1717
|
-
if (!dryRun) {
|
|
1718
|
-
const inProgressBundle = laneCount === 1 && rerunLineage === undefined
|
|
1719
|
-
? buildSingleLaneBundle({
|
|
1720
|
-
...(options.lab === undefined ? {} : { lab: options.lab }),
|
|
1721
|
-
spec: laneSpecs[0],
|
|
1722
|
-
outcome: undefined,
|
|
1723
|
-
descriptor,
|
|
1724
|
-
appUrl: laneSpecs[0].targetUrl ?? appUrl,
|
|
1725
|
-
createdAt,
|
|
1726
|
-
dryRun: false,
|
|
1727
|
-
config,
|
|
1728
|
-
runId,
|
|
1729
|
-
source,
|
|
1730
|
-
redactScreenshots,
|
|
1731
|
-
inProgress: true,
|
|
1732
|
-
...(inProgressProvenance === undefined ? {} : { subjectProvenance: inProgressProvenance }),
|
|
1733
|
-
inProcessRoute,
|
|
1734
|
-
localAppSubject
|
|
1735
|
-
})
|
|
1736
|
-
: buildCuaFanoutBundle({
|
|
1737
|
-
...(options.lab === undefined ? {} : { lab: options.lab }),
|
|
1738
|
-
specs: laneSpecs,
|
|
1739
|
-
laneSubjects: inProgressLaneSubjects,
|
|
1740
|
-
aggregateSubject: inProgressAggregateSubject,
|
|
1741
|
-
descriptor,
|
|
1742
|
-
appUrl,
|
|
1743
|
-
createdAt,
|
|
1744
|
-
dryRun: false,
|
|
1745
|
-
config,
|
|
1746
|
-
runId,
|
|
1747
|
-
source,
|
|
1748
|
-
plan,
|
|
1749
|
-
...(rerunLineage === undefined ? {} : { rerun: rerunLineage }),
|
|
1750
|
-
cloneRoute,
|
|
1751
|
-
localTreeRoute,
|
|
1752
|
-
...(publicRepo === undefined ? {} : { publicRepo }),
|
|
1753
|
-
subjectEnvNames,
|
|
1754
|
-
inProgress: true
|
|
1755
|
-
});
|
|
1756
|
-
await writeCuaRunArtifacts(inProgressBundle, createdAt, runPaths);
|
|
1757
|
-
liveObserver = observerResultForCuaArtifacts(cwd, runId, artifactRoot, [
|
|
1758
|
-
"Live CUA Observer is attached before final verification; stream auth URLs are runtime-only and are not persisted."
|
|
1759
|
-
]);
|
|
1760
|
-
if (options.onObserverReady)
|
|
1761
|
-
await options.onObserverReady(liveObserver);
|
|
1762
|
-
// Incremental live flush (#441): as each lane's loop reports its recorded-so-far items,
|
|
1763
|
-
// rewrite the in-progress bundle with per-stream `liveActor` partials so the attached
|
|
1764
|
-
// Observer's 5s poll sees the timeline grow. Throttled (one write per interval, trailing
|
|
1765
|
-
// write guaranteed), serialized (never two writers), and CLOSED before the final artifact
|
|
1766
|
-
// write so a stale flush can never resurrect the in-progress bundle. A flush failure is
|
|
1767
|
-
// swallowed: mid-run observability must never break the run itself.
|
|
1768
|
-
const streamIdByLane = new Map(laneSpecs.map((spec) => [spec.laneId, spec.streamId]));
|
|
1769
|
-
// The persona each lane is running, so the live flush can say who is in it.
|
|
1770
|
-
const personaByStream = new Map(laneSpecs
|
|
1771
|
-
.map((spec) => [spec.streamId, spec.persona?.id])
|
|
1772
|
-
.filter((entry) => typeof entry[1] === "string"));
|
|
1773
|
-
const liveItemsByStream = new Map();
|
|
1774
|
-
// Running token usage per lane, so a run in flight can price itself instead of reporting the
|
|
1775
|
-
// cost as unknown until the moment it ends.
|
|
1776
|
-
const liveUsageByStream = new Map();
|
|
1777
|
-
const liveMetadataByStream = new Map();
|
|
1778
|
-
// The rate the running usage prices at. Usage without its model is not a cost, so both travel
|
|
1779
|
-
// together or neither does.
|
|
1780
|
-
const modelForLiveCost = config.actors[0]?.model ?? DEFAULT_OPENAI_CU_MODEL;
|
|
1781
|
-
let flushWriting;
|
|
1782
|
-
let flushDirty = false;
|
|
1783
|
-
let flushClosed = false;
|
|
1784
|
-
let flushTimer;
|
|
1785
|
-
let lastFlushAtMs = 0;
|
|
1786
|
-
const FLUSH_MIN_INTERVAL_MS = 2_000;
|
|
1787
|
-
const flushNow = async () => {
|
|
1788
|
-
while (flushDirty && !flushClosed) {
|
|
1789
|
-
flushDirty = false;
|
|
1790
|
-
lastFlushAtMs = Date.now();
|
|
1791
|
-
const updatedAt = new Date(lastFlushAtMs).toISOString();
|
|
1792
|
-
const patched = {
|
|
1793
|
-
...inProgressBundle,
|
|
1794
|
-
streams: inProgressBundle.streams.map((stream) => {
|
|
1795
|
-
const liveItems = liveItemsByStream.get(stream.id);
|
|
1796
|
-
return liveItems === undefined
|
|
1797
|
-
? stream
|
|
1798
|
-
: {
|
|
1799
|
-
...stream,
|
|
1800
|
-
liveActor: {
|
|
1801
|
-
schema: "humanish.live-actor.v1",
|
|
1802
|
-
updatedAt,
|
|
1803
|
-
// WHO is in this lane, carried while the run is live. Without it a surface
|
|
1804
|
-
// watching a live run can only name the lane, and "CUA browser — observer-live-
|
|
1805
|
-
// check" is the harness talking about itself where the participant should be.
|
|
1806
|
-
...(personaByStream.get(stream.id) === undefined
|
|
1807
|
-
? {}
|
|
1808
|
-
: { persona: { id: personaByStream.get(stream.id) } }),
|
|
1809
|
-
...(liveUsageByStream.get(stream.id) === undefined
|
|
1810
|
-
? {}
|
|
1811
|
-
: {
|
|
1812
|
-
tokenUsage: liveUsageByStream.get(stream.id),
|
|
1813
|
-
// The model too: usage without the rate it prices at is not a cost.
|
|
1814
|
-
ids: { model: modelForLiveCost }
|
|
1815
|
-
}),
|
|
1816
|
-
...liveMetadataByStream.get(stream.id),
|
|
1817
|
-
items: [...liveItems]
|
|
1818
|
-
}
|
|
1819
|
-
};
|
|
1820
|
-
})
|
|
1821
|
-
};
|
|
1822
|
-
try {
|
|
1823
|
-
await writeCuaRunArtifacts(patched, createdAt, runPaths);
|
|
1824
|
-
}
|
|
1825
|
-
catch {
|
|
1826
|
-
// Swallowed by design; the final write is the evidence of record.
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
flushWriting = undefined;
|
|
1830
|
-
};
|
|
1831
|
-
const scheduleFlush = () => {
|
|
1832
|
-
if (flushClosed || flushWriting !== undefined)
|
|
1833
|
-
return;
|
|
1834
|
-
const sinceMs = Date.now() - lastFlushAtMs;
|
|
1835
|
-
if (sinceMs >= FLUSH_MIN_INTERVAL_MS) {
|
|
1836
|
-
flushWriting = flushNow();
|
|
1837
|
-
return;
|
|
1838
|
-
}
|
|
1839
|
-
if (flushTimer === undefined) {
|
|
1840
|
-
flushTimer = setTimeout(() => {
|
|
1841
|
-
flushTimer = undefined;
|
|
1842
|
-
scheduleFlush();
|
|
1843
|
-
}, FLUSH_MIN_INTERVAL_MS - sinceMs);
|
|
1844
|
-
flushTimer.unref?.();
|
|
1845
|
-
}
|
|
1846
|
-
};
|
|
1847
|
-
flushLiveTrace = (laneId, items, usage, metadata) => {
|
|
1848
|
-
// An empty snapshot (the initial observation on a frameless route) carries no
|
|
1849
|
-
// evidence worth a disk write; the first real item triggers the first flush.
|
|
1850
|
-
if (items.length === 0)
|
|
1851
|
-
return;
|
|
1852
|
-
const streamId = streamIdByLane.get(laneId);
|
|
1853
|
-
if (streamId === undefined)
|
|
1854
|
-
return;
|
|
1855
|
-
liveItemsByStream.set(streamId, items.slice());
|
|
1856
|
-
if (metadata !== undefined)
|
|
1857
|
-
liveMetadataByStream.set(streamId, metadata);
|
|
1858
|
-
if (usage !== undefined)
|
|
1859
|
-
liveUsageByStream.set(streamId, usage);
|
|
1860
|
-
flushDirty = true;
|
|
1861
|
-
scheduleFlush();
|
|
1862
|
-
};
|
|
1863
|
-
stopLiveFlush = async () => {
|
|
1864
|
-
flushClosed = true;
|
|
1865
|
-
if (flushTimer !== undefined) {
|
|
1866
|
-
clearTimeout(flushTimer);
|
|
1867
|
-
flushTimer = undefined;
|
|
1868
|
-
}
|
|
1869
|
-
await flushWriting;
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
const receivingWarnings = [];
|
|
1873
|
-
let receiving;
|
|
1874
|
-
if (!dryRun && config.comms?.email?.kind === "real") {
|
|
1875
|
-
try {
|
|
1876
|
-
receiving = await prepareReceivingRun({ cwd, runId, config, env, participants: laneSpecs.map(spec => spec.laneId), runPaths,
|
|
1877
|
-
registerSecrets: values => { for (const value of values)
|
|
1878
|
-
if (value.length >= 4 && !knownSecretValues.includes(value))
|
|
1879
|
-
knownSecretValues.push(value); }
|
|
1880
|
-
});
|
|
1881
|
-
if (receiving)
|
|
1882
|
-
deps.receiving = receiving;
|
|
1883
|
-
}
|
|
1884
|
-
catch {
|
|
1885
|
-
await stopLiveFlush?.();
|
|
1886
|
-
return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", "Real email setup failed before desktop allocation. Run humanish comms check --online and humanish comms recover to inspect authentication and pending cleanup.", descriptor.id);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
// Run lanes (dry-run runs none). In-process is always one lane.
|
|
1890
|
-
let outcomes;
|
|
1891
|
-
let failFastReason;
|
|
1892
|
-
try {
|
|
1893
|
-
if (!dryRun) {
|
|
1894
|
-
if (inProcessRoute) {
|
|
1895
|
-
outcomes = [await runInProcessLane(laneSpecs[0], deps)];
|
|
1896
|
-
}
|
|
1897
|
-
else if (laneCount === 1) {
|
|
1898
|
-
outcomes = [await runCuaLane(laneSpecs[0], deps)];
|
|
1899
|
-
}
|
|
1900
|
-
else {
|
|
1901
|
-
const ran = await runCuaLanes(laneSpecs, deps, plan.concurrency);
|
|
1902
|
-
outcomes = ran.outcomes;
|
|
1903
|
-
failFastReason = ran.failFastReason;
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
finally {
|
|
1908
|
-
try {
|
|
1909
|
-
await receiving?.finish();
|
|
1910
|
-
}
|
|
1911
|
-
catch {
|
|
1912
|
-
receivingWarnings.push("Email finalization could not complete. Inspect humanish comms recover; provider cleanup remains unresolved.");
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
// Close the live flush BEFORE any final artifact work: no new flush may start, and an
|
|
1916
|
-
// in-flight one is awaited, so the final bundle write can never race a stale in-progress
|
|
1917
|
-
// rewrite (which would resurrect `liveActor` after completion).
|
|
1918
|
-
await stopLiveFlush?.();
|
|
1919
|
-
const externalCommsWarnings = [];
|
|
1920
|
-
// Adopter-hosted drain (#380): once per RUN, after every lane finished — the catch is one
|
|
1921
|
-
// shared external endpoint, not a per-sandbox file. Same routing and digest-only artifact as
|
|
1922
|
-
// the in-sandbox drain; the artifact is registered on every lane that declared a recipient
|
|
1923
|
-
// address, since the thread carries each inbox's mail. A drain failure never fails the run.
|
|
1924
|
-
if (!dryRun && externalCommsConfig && externalCommsEmail && outcomes !== undefined) {
|
|
1925
|
-
try {
|
|
1926
|
-
const commsChannel = new FakeInbox();
|
|
1927
|
-
const commsInboxes = [];
|
|
1928
|
-
for (const recipient of externalCommsEmail.recipients ?? []) {
|
|
1929
|
-
if (recipient.address !== undefined) {
|
|
1930
|
-
commsInboxes.push(await commsChannel.provisionAddress(recipient.lane, recipient.address));
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
const authToken = externalCommsConfig.authTokenEnv === undefined ? undefined : env[externalCommsConfig.authTokenEnv];
|
|
1934
|
-
const collected = await collectExternalCommsThread({
|
|
1935
|
-
external: { ...externalCommsConfig, ...(authToken === undefined ? {} : { authToken }) },
|
|
1936
|
-
channel: commsChannel,
|
|
1937
|
-
inboxes: commsInboxes
|
|
1938
|
-
});
|
|
1939
|
-
if (collected.artifact) {
|
|
1940
|
-
const commsPath = "comms/thread.json";
|
|
1941
|
-
await writeContainedOutputFile(runPaths, commsPath, `${JSON.stringify(collected.artifact, null, 2)}\n`, "utf8");
|
|
1942
|
-
for (const [index, outcome] of outcomes.entries()) {
|
|
1943
|
-
const laneId = laneSpecs[index]?.laneId;
|
|
1944
|
-
if (laneId !== undefined && outcome.commsArtifactPath === undefined && laneHasInboxRecipient(externalCommsEmail, laneId)) {
|
|
1945
|
-
outcome.commsArtifactPath = commsPath;
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
else if (collected.captured > 0) {
|
|
1950
|
-
externalCommsWarnings.push(`Comms catch captured ${collected.captured} email send(s) but none matched a declared recipient inbox — no comms evidence written. Declare comms.email.recipients[].address to match the address the app sends to.`);
|
|
1951
|
-
}
|
|
1952
|
-
else {
|
|
1953
|
-
externalCommsWarnings.push(`Comms catch captured ZERO email sends — your app never delivered mail through the catch at ${externalCommsConfig.catchBaseUrl}. Verify the app's email-API base URL points at it and that the flow reached an email step.`);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
catch (error) {
|
|
1957
|
-
externalCommsWarnings.push(`Comms evidence collection failed against the adopter-hosted catch (run continues): ${redactText(scrubKnownValues(toErrorMessage(error)))}`);
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
// Per-lane subject projections (invariant 5).
|
|
1961
|
-
const laneSubjects = laneSpecs.map((_spec, index) => {
|
|
1962
|
-
const outcome = outcomes?.[index];
|
|
1963
|
-
const subjectState = resolveSubjectState({
|
|
1964
|
-
declared: provisionedRoute ? config.subject.state : undefined,
|
|
1965
|
-
dryRun,
|
|
1966
|
-
executed: outcome?.stateStepRecords ?? []
|
|
1967
|
-
});
|
|
1968
|
-
return laneSubjectProjection({
|
|
1969
|
-
cloneRoute,
|
|
1970
|
-
localTreeRoute,
|
|
1971
|
-
...(publicRepo === undefined ? {} : { publicRepo }),
|
|
1972
|
-
subjectEnvNames,
|
|
1973
|
-
...(outcome?.subjectCommit === undefined ? {} : { subjectCommit: outcome.subjectCommit }),
|
|
1974
|
-
...(localTreeArchive === undefined ? {} : { localTreeArchive }),
|
|
1975
|
-
subjectState
|
|
1976
|
-
});
|
|
1977
|
-
});
|
|
1978
|
-
// Aggregate subject (top-level + bundle): unanimity-gated commit (+ divergence warning) on
|
|
1979
|
-
// the clone route. Local-tree lanes all pack from the SAME once-per-run archive, so every
|
|
1980
|
-
// lane's projection already carries the identical archiveSha256/commit/dirty: the
|
|
1981
|
-
// `first.source !== "clone"` branch below returns it directly, with no unanimity math needed
|
|
1982
|
-
// (there is nothing that could diverge).
|
|
1983
|
-
const aggregateWarnings = [...externalCommsWarnings];
|
|
1984
|
-
// execution.caps.maxUsd is a PER-LANE cap: it is enforced INSIDE each lane's loop independently,
|
|
1985
|
-
// so an N-lane fan-out can spend up to N × maxUsd before any lane aborts, while the run cost
|
|
1986
|
-
// summary reports the (larger) aggregate. Warn at run level so the operator sees the true
|
|
1987
|
-
// ceiling — unless the study declared the shared budget (#299), which caps the run as a whole.
|
|
1988
|
-
const perLaneCapUsd = config.execution?.caps?.maxUsd;
|
|
1989
|
-
if (perLaneCapUsd !== undefined && laneCount > 1 && config.execution?.caps?.maxTotalUsd === undefined) {
|
|
1990
|
-
aggregateWarnings.push(`execution.caps.maxUsd ($${perLaneCapUsd}) is a PER-LANE cap; ${laneCount} lanes may spend up to ${laneCount} × $${perLaneCapUsd} (~$${round6(perLaneCapUsd * laneCount)} total) before any lane aborts. Set execution.caps.maxTotalUsd for a shared study budget.`);
|
|
1991
|
-
}
|
|
1992
|
-
const aggregateSubject = (() => {
|
|
1993
|
-
const first = laneSubjects[0];
|
|
1994
|
-
if (first.source !== "clone") {
|
|
1995
|
-
return first;
|
|
1996
|
-
}
|
|
1997
|
-
const commits = (outcomes ?? []).map((outcome) => outcome.subjectCommit).filter((commit) => commit !== undefined);
|
|
1998
|
-
const unanimous = !dryRun && commits.length === laneCount && new Set(commits).size === 1;
|
|
1999
|
-
if (!dryRun && laneCount > 1 && new Set(commits).size > 1) {
|
|
2000
|
-
aggregateWarnings.push("Fan-out lanes resolved DIVERGENT subject commits — the top-level subject.commit is omitted; see per-lane provenance in result.lanes for each lane's pinned commit.");
|
|
2001
|
-
}
|
|
2002
|
-
// Build without commit, then add it only when unanimous (avoids an explicit commit:undefined
|
|
2003
|
-
// under exactOptionalPropertyTypes).
|
|
2004
|
-
return {
|
|
2005
|
-
source: "clone",
|
|
2006
|
-
...(first.repo === undefined ? {} : { repo: first.repo }),
|
|
2007
|
-
...(first.envNames === undefined ? {} : { envNames: first.envNames }),
|
|
2008
|
-
state: first.state,
|
|
2009
|
-
...(unanimous && commits[0] !== undefined ? { commit: commits[0] } : {})
|
|
2010
|
-
};
|
|
2011
|
-
})();
|
|
2012
|
-
const finalProvenance = subjectProvenanceArg(aggregateSubject, publicRepo, subjectEnvNames);
|
|
2013
|
-
const bundle = laneCount === 1 && rerunLineage === undefined
|
|
2014
|
-
? buildSingleLaneBundle({
|
|
2015
|
-
...(options.lab === undefined ? {} : { lab: options.lab }),
|
|
2016
|
-
spec: laneSpecs[0],
|
|
2017
|
-
outcome: outcomes?.[0],
|
|
2018
|
-
descriptor,
|
|
2019
|
-
appUrl: laneSpecs[0].targetUrl ?? appUrl,
|
|
2020
|
-
createdAt,
|
|
2021
|
-
dryRun,
|
|
2022
|
-
config,
|
|
2023
|
-
runId,
|
|
2024
|
-
source,
|
|
2025
|
-
redactScreenshots,
|
|
2026
|
-
...(finalProvenance === undefined ? {} : { subjectProvenance: finalProvenance }),
|
|
2027
|
-
inProcessRoute,
|
|
2028
|
-
localAppSubject
|
|
2029
|
-
})
|
|
2030
|
-
: buildCuaFanoutBundle({
|
|
2031
|
-
...(options.lab === undefined ? {} : { lab: options.lab }),
|
|
2032
|
-
specs: laneSpecs,
|
|
2033
|
-
...(outcomes === undefined ? {} : { outcomes }),
|
|
2034
|
-
laneSubjects,
|
|
2035
|
-
aggregateSubject,
|
|
2036
|
-
descriptor,
|
|
2037
|
-
appUrl,
|
|
2038
|
-
createdAt,
|
|
2039
|
-
dryRun,
|
|
2040
|
-
config,
|
|
2041
|
-
runId,
|
|
2042
|
-
source,
|
|
2043
|
-
plan,
|
|
2044
|
-
...(rerunLineage === undefined ? {} : { rerun: rerunLineage }),
|
|
2045
|
-
...(failFastReason === undefined ? {} : { failFastReason }),
|
|
2046
|
-
cloneRoute,
|
|
2047
|
-
localTreeRoute,
|
|
2048
|
-
...(publicRepo === undefined ? {} : { publicRepo }),
|
|
2049
|
-
subjectEnvNames
|
|
2050
|
-
});
|
|
2051
|
-
const adapterWarnings = [];
|
|
2052
|
-
const scorerResult = await applyBrowserAdapterHooks({
|
|
2053
|
-
hooks,
|
|
2054
|
-
bundle,
|
|
2055
|
-
context: {
|
|
2056
|
-
bundle,
|
|
2057
|
-
runDir: physicalArtifactRoot,
|
|
2058
|
-
labId: config.id,
|
|
2059
|
-
runId,
|
|
2060
|
-
actor: descriptor.id,
|
|
2061
|
-
backend: "cua",
|
|
2062
|
-
dryRun,
|
|
2063
|
-
laneCount
|
|
2064
|
-
},
|
|
2065
|
-
sanitize: (text) => redactText(scrubKnownValues(text)),
|
|
2066
|
-
warnings: adapterWarnings,
|
|
2067
|
-
hookLabel: "cuaHooks",
|
|
2068
|
-
...(options.scorerProvenance === undefined ? {} : { scorerProvenance: options.scorerProvenance })
|
|
2069
|
-
});
|
|
2070
|
-
if (receiving)
|
|
2071
|
-
bundle.commsReceiving = receiving.snapshot();
|
|
2072
|
-
await writeCuaRunArtifacts(bundle, createdAt, runPaths);
|
|
2073
|
-
// Finalize the status record from the bundle that was just written, so the index can never
|
|
2074
|
-
// claim an outcome the evidence does not carry. A run that throws before reaching here leaves
|
|
2075
|
-
// its record `running` and goes stale — read as interrupted, which is the truth.
|
|
2076
|
-
await runStatus.finish({
|
|
2077
|
-
...(bundle.review?.verdict === undefined ? {} : { verdict: bundle.review.verdict }),
|
|
2078
|
-
...(bundle.review?.participants === undefined
|
|
2079
|
-
? {}
|
|
2080
|
-
: {
|
|
2081
|
-
participants: {
|
|
2082
|
-
total: bundle.review.participants.total,
|
|
2083
|
-
reachedGoal: bundle.review.participants.reachedGoal,
|
|
2084
|
-
...(bundle.review.participants.reportedFriction === undefined
|
|
2085
|
-
? {}
|
|
2086
|
-
: { reportedFriction: bundle.review.participants.reportedFriction })
|
|
2087
|
-
}
|
|
2088
|
-
}),
|
|
2089
|
-
...(bundle.cost?.estimatedTotalUsd === undefined ? {} : { estimatedCostUsd: bundle.cost.estimatedTotalUsd })
|
|
2090
|
-
});
|
|
2091
|
-
const observer = await render(cwd, runId, { open: options.open === true });
|
|
2092
|
-
if (observer.ok && runtimeStreamUrls.length > 0) {
|
|
2093
|
-
attachObserverRuntimeStreamUrls(observer, runtimeStreamUrls);
|
|
2094
|
-
}
|
|
2095
|
-
// Lane-level pass: dry-run lanes are contract-ok; live lanes need a passed, engaged session.
|
|
2096
|
-
const laneOk = (outcome) => laneOutcomeOk(outcome, dryRun);
|
|
2097
|
-
const allLanesOk = laneSpecs.every((_, index) => laneOk(outcomes?.[index]));
|
|
2098
|
-
const adapterFailure = adapterScoreFailureMessage(bundle);
|
|
2099
|
-
const ok = observer.ok && allLanesOk && adapterFailure === undefined && scorerResult.declaredVerdictFailure === undefined;
|
|
2100
|
-
const laneWarnings = (outcomes ?? []).flatMap((outcome) => outcome.warnings);
|
|
2101
|
-
const warnings = [...receivingWarnings, ...laneWarnings, ...aggregateWarnings, ...adapterWarnings, ...observer.warnings];
|
|
2102
|
-
const laneResults = laneSpecs.map((spec, index) => toLaneResult(spec, outcomes?.[index], laneSubjects[index], dryRun));
|
|
2103
|
-
const laneSummary = buildLaneSummary(outcomes, laneCount, plan, dryRun);
|
|
2104
|
-
const firstOutcome = outcomes?.[0];
|
|
2105
|
-
const errorResult = (() => {
|
|
2106
|
-
if (ok)
|
|
2107
|
-
return undefined;
|
|
2108
|
-
if (adapterFailure !== undefined) {
|
|
2109
|
-
return {
|
|
2110
|
-
code: "HUMANISH_CUA_LAB_FAILED",
|
|
2111
|
-
message: adapterFailure
|
|
2112
|
-
};
|
|
2113
|
-
}
|
|
2114
|
-
if (laneCount === 1) {
|
|
2115
|
-
const outcome = firstOutcome;
|
|
2116
|
-
return {
|
|
2117
|
-
code: outcome?.failureCode ?? "HUMANISH_CUA_LAB_FAILED",
|
|
2118
|
-
message: outcome?.sessionError
|
|
2119
|
-
?? (outcome?.noEngagement
|
|
2120
|
-
? "Actor took no actions and produced no message (likely a blank/still-loading screen); not a credible goal_satisfied."
|
|
2121
|
-
// The lane result (toLaneResult) named this refusal; the N=1 envelope fell through to
|
|
2122
|
-
// "did not produce a terminal session", which is false — it produced one and refused it.
|
|
2123
|
-
: outcome?.selfReportedBlocker
|
|
2124
|
-
? "Actor reported goal_satisfied while its final message described a blocker or asked for missing instructions; not a credible pass."
|
|
2125
|
-
: observer.ok
|
|
2126
|
-
? outcome?.session?.completionReason === "harness_error"
|
|
2127
|
-
? `Computer-use session ended with a harness error: ${outcome.session.reason}`
|
|
2128
|
-
: outcome?.session?.status !== "passed"
|
|
2129
|
-
? `Computer-use session ended with ${outcome?.session?.status ?? "unknown"}: ${outcome?.session?.reason ?? "no terminal reason"}`
|
|
2130
|
-
: "Computer-use lab did not produce a terminal session."
|
|
2131
|
-
: observer.error?.message ?? "Observer failed for the computer-use lab run.")
|
|
2132
|
-
};
|
|
2133
|
-
}
|
|
2134
|
-
const failingLane = (outcomes ?? []).find((outcome) => !laneOk(outcome));
|
|
2135
|
-
const geometryLane = (outcomes ?? []).find((outcome) => outcome.failureCode === "HUMANISH_CUA_LAB_DEVICE_GEOMETRY");
|
|
2136
|
-
const code = geometryLane?.failureCode ?? "HUMANISH_CUA_LAB_FAILED";
|
|
2137
|
-
return {
|
|
2138
|
-
code,
|
|
2139
|
-
message: observer.ok
|
|
2140
|
-
? `Fan-out run failed: ${laneSummary.passed}/${laneCount} lane(s) passed (${laneSummary.skipped} skipped, ${laneSummary.harnessErrors} harness error(s), ${laneSummary.hollow} hollow)${failingLane?.sessionError ? `; first failure: ${failingLane.sessionError}` : ""}.`
|
|
2141
|
-
: observer.error?.message ?? "Observer failed for the computer-use fan-out run."
|
|
2142
|
-
};
|
|
2143
|
-
})();
|
|
2144
|
-
return markFinalizedStudyResult({
|
|
2145
|
-
schema: CUA_ACTOR_LAB_SCHEMA,
|
|
2146
|
-
ok,
|
|
2147
|
-
cwd,
|
|
2148
|
-
labId: config.id,
|
|
2149
|
-
actor: descriptor.id,
|
|
2150
|
-
appUrl,
|
|
2151
|
-
dryRun,
|
|
2152
|
-
runId,
|
|
2153
|
-
...(firstOutcome?.session
|
|
2154
|
-
? {
|
|
2155
|
-
session: {
|
|
2156
|
-
status: firstOutcome.session.status,
|
|
2157
|
-
completionReason: firstOutcome.session.completionReason,
|
|
2158
|
-
...(firstOutcome.session.trace.stopCause === undefined ? {} : { stopCause: firstOutcome.session.trace.stopCause }),
|
|
2159
|
-
reason: firstOutcome.session.reason,
|
|
2160
|
-
screenshots: firstOutcome.screenshots.length
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
: {}),
|
|
2164
|
-
...(firstOutcome?.sandboxId
|
|
2165
|
-
? { sandbox: { sandboxId: firstOutcome.sandboxId, killed: firstOutcome.killed, streamUrlPresent: firstOutcome.streamUrlPresent } }
|
|
2166
|
-
: {}),
|
|
2167
|
-
subject: aggregateSubject,
|
|
2168
|
-
plan,
|
|
2169
|
-
lanes: laneResults,
|
|
2170
|
-
diagnostics: summarizeCuaDiagnostics({ dryRun, evidenceInvalid: !observer.ok, lanes: laneResults }),
|
|
2171
|
-
laneSummary,
|
|
2172
|
-
...(rerunLineage === undefined ? {} : { rerun: rerunLineage }),
|
|
2173
|
-
observer,
|
|
2174
|
-
warnings,
|
|
2175
|
-
...(errorResult === undefined ? {} : { error: errorResult })
|
|
2176
|
-
}, runPaths);
|
|
2177
|
-
}
|
|
2178
|
-
/** Aggregate lane counts for the result projection. */
|
|
2179
|
-
function buildLaneSummary(outcomes, laneCount, plan, dryRun) {
|
|
2180
|
-
if (dryRun || !outcomes) {
|
|
2181
|
-
return {
|
|
2182
|
-
strategy: CUA_FANOUT_STRATEGY,
|
|
2183
|
-
total: laneCount,
|
|
2184
|
-
passed: 0,
|
|
2185
|
-
skipped: 0,
|
|
2186
|
-
harnessErrors: 0,
|
|
2187
|
-
hollow: 0,
|
|
2188
|
-
concurrency: plan.concurrency,
|
|
2189
|
-
waves: plan.waves
|
|
2190
|
-
};
|
|
2191
|
-
}
|
|
2192
|
-
let passed = 0;
|
|
2193
|
-
let skipped = 0;
|
|
2194
|
-
let harnessErrors = 0;
|
|
2195
|
-
let hollow = 0;
|
|
2196
|
-
for (const outcome of outcomes) {
|
|
2197
|
-
if (outcome.skippedReason !== undefined) {
|
|
2198
|
-
skipped += 1;
|
|
2199
|
-
continue;
|
|
2200
|
-
}
|
|
2201
|
-
if (outcome.harnessError)
|
|
2202
|
-
harnessErrors += 1;
|
|
2203
|
-
if (outcome.noEngagement)
|
|
2204
|
-
hollow += 1;
|
|
2205
|
-
if (laneOutcomeOk(outcome, dryRun))
|
|
2206
|
-
passed += 1;
|
|
2207
|
-
}
|
|
2208
|
-
return {
|
|
2209
|
-
strategy: CUA_FANOUT_STRATEGY,
|
|
2210
|
-
total: laneCount,
|
|
2211
|
-
passed,
|
|
2212
|
-
skipped,
|
|
2213
|
-
harnessErrors,
|
|
2214
|
-
hollow,
|
|
2215
|
-
concurrency: plan.concurrency,
|
|
2216
|
-
waves: plan.waves
|
|
2217
|
-
};
|
|
2218
|
-
}
|
|
2219
|
-
async function writeCuaRunArtifacts(bundle, updatedAt, preparedRunPaths) {
|
|
2220
|
-
const runPaths = await validatePreparedRunArtifactPaths(preparedRunPaths);
|
|
2221
|
-
const publicBundle = {
|
|
2222
|
-
...bundle,
|
|
2223
|
-
cwd: PUBLIC_TARGET_CWD
|
|
2224
|
-
};
|
|
2225
|
-
await writeContainedOutputFile(runPaths, "run.json", `${JSON.stringify(publicBundle, null, 2)}\n`, "utf8");
|
|
2226
|
-
await writeContainedOutputFile(runPaths, "review.json", `${JSON.stringify(publicBundle.review, null, 2)}\n`, "utf8");
|
|
2227
|
-
await writeContainedOutputFile(runPaths, "review.md", renderCuaReviewMarkdown(publicBundle), "utf8");
|
|
2228
|
-
await writeContainedOutputFile(runPaths, "events.ndjson", `${publicBundle.events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
|
|
2229
|
-
await writeContainedOutputFile(runPaths, "observer/observer-data.json", `${JSON.stringify(buildObserverData(publicBundle), null, 2)}\n`, "utf8");
|
|
2230
|
-
await writePreparedRunLatestPointer(runPaths, `${JSON.stringify({
|
|
2231
|
-
schema: "humanish.latest-run.v1",
|
|
2232
|
-
runId: publicBundle.runId,
|
|
2233
|
-
path: runPaths.relativeRunRoot,
|
|
2234
|
-
updatedAt
|
|
2235
|
-
}, null, 2)}\n`, "utf8");
|
|
2236
|
-
}
|
|
2237
|
-
function observerResultForCuaArtifacts(cwd, runId, artifactRoot, warnings = []) {
|
|
2238
|
-
const observerPath = path.join(artifactRoot, "observer", "index.html");
|
|
2239
|
-
const observerDataPath = path.join(artifactRoot, "observer", "observer-data.json");
|
|
2240
|
-
const eventsPath = path.join(artifactRoot, "events.ndjson");
|
|
2241
|
-
return {
|
|
2242
|
-
schema: "humanish.observer-result.v1",
|
|
2243
|
-
ok: true,
|
|
2244
|
-
cwd,
|
|
2245
|
-
run: runId,
|
|
2246
|
-
observerPath: path.relative(cwd, observerPath),
|
|
2247
|
-
observerDataPath: path.relative(cwd, observerDataPath),
|
|
2248
|
-
eventsPath: path.relative(cwd, eventsPath),
|
|
2249
|
-
observerUrl: pathToFileURL(observerPath).href,
|
|
2250
|
-
bundlePath: path.join(artifactRoot, "run.json"),
|
|
2251
|
-
opened: false,
|
|
2252
|
-
warnings
|
|
2253
|
-
};
|
|
2254
|
-
}
|
|
2255
|
-
/** Build the N=1 bundle via the unchanged buildCuaBundle (byte-stable). */
|
|
2256
|
-
function buildSingleLaneBundle(args) {
|
|
2257
|
-
const { spec, outcome, config } = args;
|
|
2258
|
-
const desktopTemplate = e2bDesktopTemplate(config);
|
|
2259
|
-
return buildCuaBundle({
|
|
2260
|
-
realEmail: config.comms?.email?.kind === "real",
|
|
2261
|
-
...(args.lab === undefined ? {} : { lab: args.lab }),
|
|
2262
|
-
actorId: args.descriptor.id,
|
|
2263
|
-
appUrl: args.appUrl,
|
|
2264
|
-
laneId: spec.laneId,
|
|
2265
|
-
...(spec.actorType === undefined ? {} : { actorType: spec.actorType }),
|
|
2266
|
-
...(spec.surface === undefined ? {} : { surface: spec.surface }),
|
|
2267
|
-
...(spec.caseGroup === undefined ? {} : { caseGroup: spec.caseGroup }),
|
|
2268
|
-
createdAt: args.createdAt,
|
|
2269
|
-
dryRun: args.dryRun,
|
|
2270
|
-
labId: config.id,
|
|
2271
|
-
...(config.title ? { labTitle: config.title } : {}),
|
|
2272
|
-
mission: spec.evidenceInstructions ?? spec.instructions,
|
|
2273
|
-
...(spec.assignment === undefined ? {} : { assignment: spec.assignment }),
|
|
2274
|
-
persona: spec.persona,
|
|
2275
|
-
resolution: spec.resolution,
|
|
2276
|
-
desktopRoute: !args.inProcessRoute,
|
|
2277
|
-
feedbackSubstrate: args.inProcessRoute ? "local-filesystem" : args.config.execution?.target === "local" ? "local-desktop" : "e2b-desktop",
|
|
2278
|
-
...(outcome?.desktopGeometry === undefined ? {} : { desktopGeometry: outcome.desktopGeometry }),
|
|
2279
|
-
...(outcome?.recording === undefined ? {} : { recording: outcome.recording }),
|
|
2280
|
-
isMobile: spec.devicePreset.isMobile,
|
|
2281
|
-
runId: args.runId,
|
|
2282
|
-
screenshots: outcome?.screenshots ?? [],
|
|
2283
|
-
captureRedaction: args.redactScreenshots ? "blurred" : "raw",
|
|
2284
|
-
...(outcome?.session ? { session: outcome.session } : {}),
|
|
2285
|
-
...(outcome?.sessionError ? { sessionError: outcome.sessionError } : {}),
|
|
2286
|
-
...(outcome === undefined
|
|
2287
|
-
? {}
|
|
2288
|
-
: {
|
|
2289
|
-
credibility: {
|
|
2290
|
-
noEngagement: outcome.noEngagement === true,
|
|
2291
|
-
selfReportedBlocker: outcome.selfReportedBlocker === true,
|
|
2292
|
-
reportedFriction: outcome.reportedFriction === true
|
|
2293
|
-
}
|
|
2294
|
-
}),
|
|
2295
|
-
source: args.source,
|
|
2296
|
-
...(args.inProgress === undefined ? {} : { inProgress: args.inProgress }),
|
|
2297
|
-
...(args.subjectProvenance === undefined ? {} : { subjectProvenance: args.subjectProvenance }),
|
|
2298
|
-
...(desktopTemplate === undefined ? {} : { desktopTemplate }),
|
|
2299
|
-
...(outcome?.desktopBrowser === undefined ? {} : { desktopBrowser: outcome.desktopBrowser }),
|
|
2300
|
-
providerResources: providerResourcesForOutcome({
|
|
2301
|
-
outcome,
|
|
2302
|
-
createdAt: args.createdAt,
|
|
2303
|
-
simId: spec.simId,
|
|
2304
|
-
streamId: spec.streamId,
|
|
2305
|
-
laneId: spec.laneId
|
|
2306
|
-
}),
|
|
2307
|
-
...(args.localAppSubject || args.inProcessRoute ? { entryKind: "local-app" } : {}),
|
|
2308
|
-
...(outcome?.session ? { traceArtifactPath: spec.traceArtifactPath } : {}),
|
|
2309
|
-
...(outcome?.commsArtifactPath === undefined ? {} : { commsArtifactPath: outcome.commsArtifactPath }),
|
|
2310
|
-
...(desktopSpanToMinutes(outcome?.desktopDurationMs) === undefined
|
|
2311
|
-
? {}
|
|
2312
|
-
: { desktopMinutes: desktopSpanToMinutes(outcome?.desktopDurationMs) }),
|
|
2313
|
-
...(outcome?.sandboxId === undefined ? {} : { desktopUsage: {
|
|
2314
|
-
laneId: spec.laneId,
|
|
2315
|
-
minutes: desktopSpanToMinutes(outcome.desktopDurationMs),
|
|
2316
|
-
observation: outcome.desktopResources,
|
|
2317
|
-
lifetimeComplete: outcome.killed
|
|
2318
|
-
} }),
|
|
2319
|
-
phaseEvents: outcome?.phaseRecords ?? []
|
|
2320
|
-
});
|
|
2321
|
-
}
|
|
2322
|
-
/**
|
|
2323
|
-
* Default local-tree packing implementation: createLocalTreeArchive(root, opts) on the host,
|
|
2324
|
-
* then a single read of the produced archive file into an ArrayBuffer for upload. The DI seam
|
|
2325
|
-
* (CuaActorLabHooks.packLocalTree) overrides this in deterministic tests so they never require
|
|
2326
|
-
* tar/git.
|
|
2327
|
-
*/
|
|
2328
|
-
export async function defaultPackLocalTree(args) {
|
|
2329
|
-
const archive = createLocalTreeArchive(args.root, {
|
|
2330
|
-
...(args.extraExclude === undefined ? {} : { extraExclude: args.extraExclude }),
|
|
2331
|
-
...(args.maxArchiveBytes === undefined ? {} : { maxArchiveBytes: args.maxArchiveBytes })
|
|
2332
|
-
});
|
|
2333
|
-
const bytes = await readFile(archive.archivePath);
|
|
2334
|
-
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2335
|
-
// The archive was written to a fresh mkdtemp dir (no outputPath passed above); once the
|
|
2336
|
-
// bytes are buffered the on-disk copy is pure residue, and a packed working tree left in
|
|
2337
|
-
// the host tmpdir is itself a small leak surface. Best-effort removal.
|
|
2338
|
-
await rm(path.dirname(archive.archivePath), { recursive: true, force: true }).catch(() => undefined);
|
|
2339
|
-
return { archive, buffer };
|
|
2340
|
-
}
|
|
2341
|
-
/**
|
|
2342
|
-
* Resolve the bundle's state marker from the declaration and what actually ran.
|
|
2343
|
-
* Precedence: external declared → "unpinned" (seed records, if any, stay attached — a
|
|
2344
|
-
* migrated external DB is still unpinned overall); else seed declared → "seeded" only when
|
|
2345
|
-
* every declared step executed ok on a live run, otherwise "declared-not-run" (dry-run
|
|
2346
|
-
* contract bundles and failed live provisioning); no declaration → "undeclared".
|
|
2347
|
-
*/
|
|
2348
|
-
export function resolveSubjectState(args) {
|
|
2349
|
-
const declared = args.declared;
|
|
2350
|
-
if (!declared) {
|
|
2351
|
-
return { provenance: "undeclared" };
|
|
2352
|
-
}
|
|
2353
|
-
const declaredSeed = declared.seed ?? [];
|
|
2354
|
-
const external = declared.external ?? [];
|
|
2355
|
-
// Dry-run: nothing executes (no sandbox) — record the DECLARED recipe: name, phase, and
|
|
2356
|
-
// command digest only, with NO execution fields.
|
|
2357
|
-
const seed = args.dryRun
|
|
2358
|
-
? declaredSeed.map((step) => ({
|
|
2359
|
-
name: step.name,
|
|
2360
|
-
when: step.when ?? "before-start",
|
|
2361
|
-
commandDigest: commandDigestOf(step.command)
|
|
2362
|
-
}))
|
|
2363
|
-
: args.executed;
|
|
2364
|
-
const allRanOk = !args.dryRun
|
|
2365
|
-
&& declaredSeed.length > 0
|
|
2366
|
-
&& seed.length === declaredSeed.length
|
|
2367
|
-
&& seed.every((record) => record.ok === true);
|
|
2368
|
-
const provenance = external.length > 0
|
|
2369
|
-
? "unpinned"
|
|
2370
|
-
: declaredSeed.length === 0
|
|
2371
|
-
? "undeclared"
|
|
2372
|
-
: allRanOk
|
|
2373
|
-
? "seeded"
|
|
2374
|
-
: "declared-not-run";
|
|
2375
|
-
return {
|
|
2376
|
-
provenance,
|
|
2377
|
-
...(seed.length > 0 ? { seed } : {}),
|
|
2378
|
-
...(external.length > 0 ? { externalEnvNames: external } : {})
|
|
2379
|
-
};
|
|
2380
|
-
}
|
|
2381
|
-
/** The human-readable state story appended to the provenance event (and review.md via it). */
|
|
2382
|
-
function describeSubjectState(state, dryRun) {
|
|
2383
|
-
switch (state.provenance) {
|
|
2384
|
-
case "seeded":
|
|
2385
|
-
return `seeded (${state.seed?.length ?? 0} step(s): ${(state.seed ?? []).map((record) => record.name).join(", ")})`;
|
|
2386
|
-
case "unpinned":
|
|
2387
|
-
return `UNPINNED (external: ${(state.externalEnvNames ?? []).join(", ")})`;
|
|
2388
|
-
case "declared-not-run":
|
|
2389
|
-
return `declared, not run (${dryRun ? "dry-run contract" : "provisioning did not complete"})`;
|
|
2390
|
-
case "undeclared":
|
|
2391
|
-
return "undeclared";
|
|
2392
|
-
case "external-public":
|
|
2393
|
-
return "external-public (operator-declared, operator-owned public deployment; neither provisioned nor seeded)";
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
export function buildCuaCostSummary(args) {
|
|
2397
|
-
const breakdown = [];
|
|
2398
|
-
let sumInput = 0;
|
|
2399
|
-
let sumOutput = 0;
|
|
2400
|
-
for (const lane of args.lanes) {
|
|
2401
|
-
const usage = lane.trace.tokenUsage;
|
|
2402
|
-
if (usage) {
|
|
2403
|
-
sumInput += usage.input ?? 0;
|
|
2404
|
-
sumOutput += usage.output ?? 0;
|
|
2405
|
-
}
|
|
2406
|
-
// A stalled/ambiguous interaction can remain unreported after a later successful retry.
|
|
2407
|
-
// Keep known token estimates and make the additional unknown explicit.
|
|
2408
|
-
if (lane.trace.interactionUsageIncomplete === true) {
|
|
2409
|
-
breakdown.push({
|
|
2410
|
-
kind: "model-tokens",
|
|
2411
|
-
...(lane.laneId === undefined ? {} : { laneId: lane.laneId }),
|
|
2412
|
-
...(lane.trace.providerVersion === undefined ? {} : { modelId: lane.trace.providerVersion }),
|
|
2413
|
-
estimatedCostUsd: null,
|
|
2414
|
-
reason: "interaction_usage_unreported",
|
|
2415
|
-
ratesAsOf: null
|
|
2416
|
-
});
|
|
2417
|
-
}
|
|
2418
|
-
// An attempted closing request has its own accounting boundary.
|
|
2419
|
-
if (lane.trace.debrief?.usageReported === false) {
|
|
2420
|
-
breakdown.push({
|
|
2421
|
-
kind: "model-tokens",
|
|
2422
|
-
...(lane.laneId === undefined ? {} : { laneId: lane.laneId }),
|
|
2423
|
-
...(lane.trace.providerVersion === undefined ? {} : { modelId: lane.trace.providerVersion }),
|
|
2424
|
-
estimatedCostUsd: null,
|
|
2425
|
-
reason: "closing_usage_unreported",
|
|
2426
|
-
ratesAsOf: null
|
|
2427
|
-
});
|
|
2428
|
-
}
|
|
2429
|
-
const est = lane.trace.executionProfile?.billing === "account-unknown"
|
|
2430
|
-
? estimateActorCostForExecution(usage, lane.trace.ids.model, lane.trace.executionProfile) : lane.trace.estimatedCost;
|
|
2431
|
-
if (!est) {
|
|
2432
|
-
continue;
|
|
2433
|
-
}
|
|
2434
|
-
breakdown.push({
|
|
2435
|
-
kind: "model-tokens",
|
|
2436
|
-
...(lane.laneId === undefined ? {} : { laneId: lane.laneId }),
|
|
2437
|
-
...(est.modelId === undefined ? {} : { modelId: est.modelId }),
|
|
2438
|
-
estimatedCostUsd: est.estimatedCostUsd,
|
|
2439
|
-
...(est.reason === undefined ? {} : { reason: est.reason }),
|
|
2440
|
-
ratesAsOf: est.ratesAsOf,
|
|
2441
|
-
...(est.source === undefined ? {} : { source: est.source }),
|
|
2442
|
-
...(est.placeholder ? { placeholder: true } : {})
|
|
2443
|
-
});
|
|
2444
|
-
}
|
|
2445
|
-
for (const usage of args.desktops ?? []) {
|
|
2446
|
-
const observation = usage.observation;
|
|
2447
|
-
const resources = observation && "resources" in observation ? observation.resources : undefined;
|
|
2448
|
-
const estimate = estimateAllocatedDesktopCost(usage.minutes, resources);
|
|
2449
|
-
breakdown.push({
|
|
2450
|
-
kind: "desktop-minutes",
|
|
2451
|
-
...(usage.laneId === undefined ? {} : { laneId: usage.laneId }),
|
|
2452
|
-
estimatedCostUsd: estimate.estimatedCostUsd,
|
|
2453
|
-
...(estimate.reason === undefined ? {} : { reason: estimate.reason }),
|
|
2454
|
-
ratesAsOf: estimate.ratesAsOf,
|
|
2455
|
-
...(estimate.source === undefined ? {} : { source: estimate.source }),
|
|
2456
|
-
desktop: {
|
|
2457
|
-
minutes: estimate.minutes,
|
|
2458
|
-
durationBasis: "host-acquired-to-cleanup",
|
|
2459
|
-
...(resources === undefined ? {} : { resources, resourceSource: "e2b.getInfo" }),
|
|
2460
|
-
...(observation && "reason" in observation ? { resourceUnavailableReason: observation.reason } : {}),
|
|
2461
|
-
...(estimate.usdPerSecond === undefined ? {} : { usdPerSecond: estimate.usdPerSecond })
|
|
2462
|
-
}
|
|
2463
|
-
});
|
|
2464
|
-
if (!usage.lifetimeComplete) {
|
|
2465
|
-
breakdown.push({ kind: "desktop-minutes", ...(usage.laneId === undefined ? {} : { laneId: usage.laneId }),
|
|
2466
|
-
estimatedCostUsd: null, reason: "desktop_lifetime_incomplete", ratesAsOf: null });
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
if (args.desktops === undefined && args.desktopMinutes !== undefined) {
|
|
2470
|
-
const desktop = estimateDesktopCost(args.desktopMinutes);
|
|
2471
|
-
breakdown.push({
|
|
2472
|
-
kind: "desktop-minutes",
|
|
2473
|
-
estimatedCostUsd: desktop.estimatedCostUsd,
|
|
2474
|
-
...(desktop.reason === undefined ? {} : { reason: desktop.reason }),
|
|
2475
|
-
ratesAsOf: desktop.ratesAsOf,
|
|
2476
|
-
...(desktop.source === undefined ? {} : { source: desktop.source }),
|
|
2477
|
-
...(desktop.placeholder ? { placeholder: true } : {})
|
|
2478
|
-
});
|
|
2479
|
-
}
|
|
2480
|
-
if (breakdown.length === 0) {
|
|
2481
|
-
return undefined;
|
|
2482
|
-
}
|
|
2483
|
-
let knownSum = 0;
|
|
2484
|
-
let anyKnown = false;
|
|
2485
|
-
let anyNull = false;
|
|
2486
|
-
let placeholder = false;
|
|
2487
|
-
// Aggregate freshness is CONSERVATIVE: an aggregate estimate is only as current as its OLDEST
|
|
2488
|
-
// contributing rate, so ratesAsOf takes the MIN (oldest) asOf — MAX would overclaim freshness the
|
|
2489
|
-
// moment operator-edited rates in src/pricing.ts diverge. Each breakdown line keeps its own true asOf.
|
|
2490
|
-
let minRatesAsOf = null;
|
|
2491
|
-
for (const line of breakdown) {
|
|
2492
|
-
if (line.estimatedCostUsd === null) {
|
|
2493
|
-
anyNull = true;
|
|
2494
|
-
continue;
|
|
2495
|
-
}
|
|
2496
|
-
anyKnown = true;
|
|
2497
|
-
knownSum += line.estimatedCostUsd;
|
|
2498
|
-
if (line.placeholder)
|
|
2499
|
-
placeholder = true;
|
|
2500
|
-
if (line.ratesAsOf !== null && (minRatesAsOf === null || line.ratesAsOf < minRatesAsOf)) {
|
|
2501
|
-
minRatesAsOf = line.ratesAsOf;
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
|
-
const estimatedTotalUsd = anyKnown ? round6(knownSum) : null;
|
|
2505
|
-
const estimateNote = estimatedTotalUsd === null
|
|
2506
|
-
? `No priced spend lines this run — every cost line is DECLARED ABSENT (unknown rate / no usage / no duration); nothing is guessed. ${args.lanes.some(lane => lane.trace.executionProfile?.billing === "account-unknown") ? "Account billing remains unknown; API prices do not measure account spend." : "Add a rate to src/pricing.ts to estimate this model."}`
|
|
2507
|
-
: `Estimated ${estimatedTotalUsd} USD total${anyNull ? " (LOWER BOUND — some lines unmeasured/unpriced)" : ""}${placeholder ? "; includes PLACEHOLDER rate(s) — confirm before trusting the magnitude" : ""}. Every figure is an ESTIMATE (rates as of ${minRatesAsOf} — the OLDEST contributing rate, since an aggregate is only as fresh as its stalest input), a rate-table multiply, NOT an authoritative provider charge.`;
|
|
2508
|
-
const note = estimateNote + ((args.desktops?.length ?? 0) > 0
|
|
2509
|
-
? args.desktops.some(usage => usage.observation !== undefined && "resources" in usage.observation)
|
|
2510
|
-
? " Desktop compute uses observed CPU/RAM and a host-acquired-to-cleanup span where available; pre-handle startup, plan fees, credits, and negotiated pricing are excluded."
|
|
2511
|
-
: " Desktop compute is unmeasured: no allocation CPU/RAM observation is available."
|
|
2512
|
-
: "");
|
|
2513
|
-
return {
|
|
2514
|
-
schema: "humanish.run-cost-summary.v1",
|
|
2515
|
-
currency: "usd",
|
|
2516
|
-
estimatedTotalUsd,
|
|
2517
|
-
ratesAsOf: minRatesAsOf,
|
|
2518
|
-
fullyEstimated: !anyNull,
|
|
2519
|
-
placeholder,
|
|
2520
|
-
breakdown,
|
|
2521
|
-
tokenUsage: args.lanes.some(lane => lane.trace.executionProfile?.billing === "account-unknown") ? {
|
|
2522
|
-
...(args.lanes.some(lane => lane.trace.tokenUsage?.input !== undefined) ? { input: sumInput } : {}),
|
|
2523
|
-
...(args.lanes.some(lane => lane.trace.tokenUsage?.output !== undefined) ? { output: sumOutput } : {}),
|
|
2524
|
-
...(args.lanes.every(lane => lane.trace.tokenUsage?.input !== undefined && lane.trace.tokenUsage?.output !== undefined &&
|
|
2525
|
-
lane.trace.interactionUsageIncomplete !== true && lane.trace.debrief?.usageReported !== false &&
|
|
2526
|
-
(lane.trace.executionProfile === undefined || lane.trace.providerRequests?.every(r => r.usageComplete) === true))
|
|
2527
|
-
? { total: sumInput + sumOutput } : {})
|
|
2528
|
-
} : { input: sumInput, output: sumOutput, total: sumInput + sumOutput },
|
|
2529
|
-
desktopMinutes: args.desktops === undefined ? args.desktopMinutes ?? null
|
|
2530
|
-
: args.desktops.some(usage => usage.minutes !== undefined)
|
|
2531
|
-
? round6(args.desktops.reduce((sum, usage) => sum + (usage.minutes ?? 0), 0)) : null,
|
|
2532
|
-
note
|
|
2533
|
-
};
|
|
2534
|
-
}
|
|
2535
|
-
// Convert a host-side desktop span (ms) into billed minutes, or undefined when no sandbox ran.
|
|
2536
|
-
function desktopSpanToMinutes(desktopDurationMs) {
|
|
2537
|
-
return desktopDurationMs === undefined ? undefined : desktopDurationMs / 60_000;
|
|
2538
|
-
}
|
|
2539
|
-
/**
|
|
2540
|
-
* Feedback candidates derived from what LIVE participants actually reported (#392).
|
|
2541
|
-
*
|
|
2542
|
-
* A live run's feedback draft used to fall through to a dry-run template, because no browser route
|
|
2543
|
-
* ever built a candidate. The candidate worth filing is the one the study produced: a participant
|
|
2544
|
-
* who reported friction on the way (the most valuable thing a run captures), or one who stopped
|
|
2545
|
-
* trying. A clean pass files nothing here — feedback exists to carry findings, and a run without
|
|
2546
|
-
* any falls back to an honest live summary in the draft layer instead of a template.
|
|
2547
|
-
*
|
|
2548
|
-
* Everything quoted is already scrub+redacted — participant messages and `session.reason` pass
|
|
2549
|
-
* through redactNarration in the loop — and passes redactText again here as defense-in-depth.
|
|
2550
|
-
*/
|
|
2551
|
-
export function participantFeedbackCandidates(args) {
|
|
2552
|
-
const candidates = [];
|
|
2553
|
-
for (const lane of args.lanes) {
|
|
2554
|
-
const session = lane.session;
|
|
2555
|
-
if (session === undefined)
|
|
2556
|
-
continue;
|
|
2557
|
-
const friction = resolveSelfReportedFriction(session);
|
|
2558
|
-
const abandoned = session.status === "abandoned";
|
|
2559
|
-
if (friction === undefined && !abandoned)
|
|
2560
|
-
continue;
|
|
2561
|
-
const summary = friction !== undefined
|
|
2562
|
-
? `Participant ${lane.personaId} (${lane.laneId}) reported friction on the way through the study goal`
|
|
2563
|
-
: `Participant ${lane.personaId} (${lane.laneId}) stopped before completing the study goal`;
|
|
2564
|
-
const lastScreenshot = lane.screenshots[lane.screenshots.length - 1];
|
|
2565
|
-
candidates.push({
|
|
2566
|
-
schema: "humanish.feedback-candidate.v1",
|
|
2567
|
-
id: `participant-report-${lane.laneId}`,
|
|
2568
|
-
run_id: args.runId,
|
|
2569
|
-
stream_id: lane.streamId,
|
|
2570
|
-
adapter_id: args.adapterId,
|
|
2571
|
-
scenario_id: args.scenarioId,
|
|
2572
|
-
persona_id: lane.personaId,
|
|
2573
|
-
actor: "computer-use",
|
|
2574
|
-
substrate: args.substrate,
|
|
2575
|
-
// The participant is reporting on the PRODUCT: friction and abandonment are target-app
|
|
2576
|
-
// findings by the three-roles rule. A harness failure never reaches this builder — it is
|
|
2577
|
-
// not a participant report.
|
|
2578
|
-
failure_owner: "target-app",
|
|
2579
|
-
summary,
|
|
2580
|
-
expected: args.goal,
|
|
2581
|
-
actual: redactText(friction ?? session.reason),
|
|
2582
|
-
evidence: [
|
|
2583
|
-
...(lane.traceArtifactPath === undefined ? [] : [{
|
|
2584
|
-
path: lane.traceArtifactPath,
|
|
2585
|
-
kind: "trace",
|
|
2586
|
-
note: "Full actor trace: turns, actions, and the participant's own report."
|
|
2587
|
-
}]),
|
|
2588
|
-
...(lastScreenshot === undefined ? [] : [{
|
|
2589
|
-
path: lastScreenshot,
|
|
2590
|
-
kind: "screenshot",
|
|
2591
|
-
note: "Final screenshot at the moment the session ended."
|
|
2592
|
-
}]),
|
|
2593
|
-
...(lane.commsArtifactPath === undefined ? [] : [{
|
|
2594
|
-
path: lane.commsArtifactPath,
|
|
2595
|
-
kind: "log",
|
|
2596
|
-
note: "Digest-only comms thread captured in-sandbox."
|
|
2597
|
-
}])
|
|
2598
|
-
],
|
|
2599
|
-
redaction: {
|
|
2600
|
-
status: "passed",
|
|
2601
|
-
notes: "Quoted participant text passed the loop's known-value scrub and pattern redaction before persisting, and redactText again here."
|
|
2602
|
-
},
|
|
2603
|
-
idempotency_key: `humanish:${args.runId}:${lane.laneId}:participant-report`,
|
|
2604
|
-
proposed_next_state: "study-quality-review",
|
|
2605
|
-
acceptance_proof: [
|
|
2606
|
-
feedbackProofCommands(args.runId).verify,
|
|
2607
|
-
feedbackProofCommands(args.runId).watch
|
|
2608
|
-
]
|
|
2609
|
-
});
|
|
2610
|
-
}
|
|
2611
|
-
return candidates;
|
|
2612
|
-
}
|
|
2613
|
-
export function buildCuaBundle(args) {
|
|
2614
|
-
const publicAppUrl = publicSafeAppUrlLabel(args.appUrl);
|
|
2615
|
-
// Run-level cost ESTIMATE (advisory; omitted when nothing was priced and no sandbox ran).
|
|
2616
|
-
const cost = buildCuaCostSummary({
|
|
2617
|
-
lanes: args.session ? [{ ...(args.laneId === undefined ? {} : { laneId: args.laneId }), trace: args.session.trace }] : [],
|
|
2618
|
-
desktopMinutes: args.desktopMinutes,
|
|
2619
|
-
...(args.desktopUsage === undefined ? {} : { desktops: [args.desktopUsage] })
|
|
2620
|
-
});
|
|
2621
|
-
const status = args.inProgress === true
|
|
2622
|
-
? "running"
|
|
2623
|
-
: args.session
|
|
2624
|
-
? args.session.status
|
|
2625
|
-
: args.sessionError
|
|
2626
|
-
? "failed"
|
|
2627
|
-
: "contract_proof_only";
|
|
2628
|
-
const reason = args.inProgress === true
|
|
2629
|
-
? "Live computer-use session is running; stream auth URL is available only through the attached Observer server."
|
|
2630
|
-
: args.session?.reason
|
|
2631
|
-
?? args.sessionError
|
|
2632
|
-
?? "Contract bundle only: dry-run produced the evidence shape without launching a desktop or spending provider tokens.";
|
|
2633
|
-
const lastScreenshot = args.screenshots[args.screenshots.length - 1];
|
|
2634
|
-
const desktopGeometry = args.desktopRoute === false
|
|
2635
|
-
? undefined
|
|
2636
|
-
: args.desktopGeometry ?? {
|
|
2637
|
-
screen: { requested: { width: args.resolution[0], height: args.resolution[1] } }
|
|
2638
|
-
};
|
|
2639
|
-
// Honest labels (invariant 6: claims match mechanism): every screenshot label names the
|
|
2640
|
-
// run's ACTUAL mode. The session trace is the evidence-of-record; the capture policy covers
|
|
2641
|
-
// frames written before a mid-session failure produced a trace.
|
|
2642
|
-
const traceScreenshotMode = args.session?.trace.redaction.screenshots;
|
|
2643
|
-
const screenshotMode = traceScreenshotMode === "raw" || traceScreenshotMode === "blurred"
|
|
2644
|
-
? traceScreenshotMode
|
|
2645
|
-
: args.captureRedaction ?? "raw";
|
|
2646
|
-
const simulation = {
|
|
2647
|
-
id: "sim-001",
|
|
2648
|
-
index: 1,
|
|
2649
|
-
personaId: args.persona.id,
|
|
2650
|
-
scenarioId: `cua-${args.labId}`,
|
|
2651
|
-
status,
|
|
2652
|
-
streamKind: "browser",
|
|
2653
|
-
mode: "browser-sim",
|
|
2654
|
-
progress: args.inProgress === true ? 20 : 100,
|
|
2655
|
-
currentStep: reason,
|
|
2656
|
-
summary: args.session
|
|
2657
|
-
? `Computer-use actor (${args.actorId}) drove the subject app in a hosted desktop browser; ${args.session.completionReason}.`
|
|
2658
|
-
: args.inProgress === true
|
|
2659
|
-
? `Computer-use actor (${args.actorId}) is driving the subject app in a hosted desktop browser.`
|
|
2660
|
-
: args.sessionError
|
|
2661
|
-
? `Computer-use lab failed before a terminal session verdict: ${args.sessionError}`
|
|
2662
|
-
: `Contract lane for the computer-use actor (${args.actorId}) against ${publicAppUrl}.`,
|
|
2663
|
-
streamIds: ["stream-001"],
|
|
2664
|
-
startedAt: args.createdAt,
|
|
2665
|
-
updatedAt: args.createdAt
|
|
2666
|
-
};
|
|
2667
|
-
const stream = {
|
|
2668
|
-
id: "stream-001",
|
|
2669
|
-
simId: "sim-001",
|
|
2670
|
-
laneId: args.laneId ?? "lane-01",
|
|
2671
|
-
...(args.assignment === undefined ? {} : { assignment: participantAssignment(args.assignment) }),
|
|
2672
|
-
...(args.actorType === undefined ? {} : { actorType: args.actorType }),
|
|
2673
|
-
...(args.surface === undefined ? {} : { surface: args.surface }),
|
|
2674
|
-
...(args.caseGroup === undefined ? {} : { caseGroup: args.caseGroup }),
|
|
2675
|
-
kind: "browser",
|
|
2676
|
-
label: `CUA browser — ${args.labId}`,
|
|
2677
|
-
status,
|
|
2678
|
-
transport: "snapshot",
|
|
2679
|
-
updatedAt: args.createdAt,
|
|
2680
|
-
embed: lastScreenshot
|
|
2681
|
-
? { kind: "screenshot", url: lastScreenshot, title: `CUA desktop (${screenshotMode})` }
|
|
2682
|
-
: { kind: "placeholder", title: "CUA desktop" },
|
|
2683
|
-
...(desktopGeometry?.viewport === undefined
|
|
2684
|
-
? {}
|
|
2685
|
-
: {
|
|
2686
|
-
viewport: {
|
|
2687
|
-
width: desktopGeometry.viewport.width,
|
|
2688
|
-
height: desktopGeometry.viewport.height,
|
|
2689
|
-
deviceScaleFactor: desktopGeometry.viewport.deviceScaleFactor,
|
|
2690
|
-
...(args.isMobile === undefined ? {} : { isMobile: args.isMobile })
|
|
2691
|
-
}
|
|
2692
|
-
}),
|
|
2693
|
-
...(desktopGeometry === undefined ? {} : { desktopGeometry }),
|
|
2694
|
-
...(args.recording === undefined ? {} : { recording: args.recording }),
|
|
2695
|
-
ui: {
|
|
2696
|
-
route: publicAppUrl,
|
|
2697
|
-
intent: "Watch the computer-use actor drive the subject app in a hosted desktop browser.",
|
|
2698
|
-
state: reason,
|
|
2699
|
-
...(args.session ? { actorStatus: args.session.status } : {}),
|
|
2700
|
-
...(lastScreenshot ? { screenshotUrl: lastScreenshot } : {})
|
|
2701
|
-
},
|
|
2702
|
-
// The seam this lab exists to fill: the provider-neutral actor evidence projection.
|
|
2703
|
-
...(args.session ? { actor: args.session.trace } : {}),
|
|
2704
|
-
artifacts: [
|
|
2705
|
-
{ label: "run bundle", path: "run.json", kind: "bundle" },
|
|
2706
|
-
{ label: "review", path: "review.md", kind: "review" },
|
|
2707
|
-
{ label: "events", path: "events.ndjson", kind: "events" },
|
|
2708
|
-
...(args.traceArtifactPath
|
|
2709
|
-
? [{ label: "actor trace", path: args.traceArtifactPath, kind: "trace" }]
|
|
2710
|
-
: []),
|
|
2711
|
-
...(args.commsArtifactPath
|
|
2712
|
-
? [{ label: "comms thread", path: args.commsArtifactPath, kind: "log" }]
|
|
2713
|
-
: []),
|
|
2714
|
-
...(args.recording ? [{ label: "desktop recording", path: args.recording.path, kind: "recording" }] : []),
|
|
2715
|
-
...args.screenshots.map((screenshot, index) => ({
|
|
2716
|
-
label: `screenshot ${String(index + 1).padStart(2, "0")} (${screenshotMode})`,
|
|
2717
|
-
path: screenshot,
|
|
2718
|
-
kind: "screenshot"
|
|
2719
|
-
}))
|
|
2720
|
-
]
|
|
2721
|
-
};
|
|
2722
|
-
const events = [
|
|
2723
|
-
{
|
|
2724
|
-
id: "event-000-created",
|
|
2725
|
-
at: args.createdAt,
|
|
2726
|
-
level: "info",
|
|
2727
|
-
type: "cua-lab.run.created",
|
|
2728
|
-
message: `Created computer-use lab run for ${args.labId} (actor ${args.actorId}).`
|
|
2729
|
-
},
|
|
2730
|
-
args.subjectProvenance
|
|
2731
|
-
? {
|
|
2732
|
-
id: "event-001-subject",
|
|
2733
|
-
at: args.createdAt,
|
|
2734
|
-
level: "info",
|
|
2735
|
-
type: "cua-lab.subject.provenance",
|
|
2736
|
-
// HONEST WORDING: claim "cloned/packed and served" only when it actually happened.
|
|
2737
|
-
message: `${subjectProvenanceMessage(args.subjectProvenance, publicAppUrl, args.dryRun, args.session !== undefined)} (subject env names: ${args.subjectProvenance.envNames.length > 0 ? args.subjectProvenance.envNames.join(", ") : "none"}; values never persisted); state: ${describeSubjectState(args.subjectProvenance.state, args.dryRun)}.`,
|
|
2738
|
-
simId: "sim-001",
|
|
2739
|
-
streamId: "stream-001"
|
|
2740
|
-
}
|
|
2741
|
-
: {
|
|
2742
|
-
id: "event-001-subject",
|
|
2743
|
-
at: args.createdAt,
|
|
2744
|
-
level: "info",
|
|
2745
|
-
type: "cua-lab.subject.declared",
|
|
2746
|
-
// Invariant 5: declare what the subject WAS, including the ABSENCE of a pin. A
|
|
2747
|
-
// local-app / in-process subject is an already-running LOCAL dev server the caller
|
|
2748
|
-
// provisioned; it cannot be commit-pinned, so its provenance is honestly UNPINNED and
|
|
2749
|
-
// no E2B desktop was created. A plain app-url entry runs inside the desktop sandbox.
|
|
2750
|
-
message: args.entryKind === "local-app"
|
|
2751
|
-
? `Subject app declared at ${publicAppUrl} (already-running LOCAL dev server driven in-process; NO clone, NO E2B desktop). Provenance: caller-provisioned and UNPINNED — a running dev server cannot be commit-pinned.`
|
|
2752
|
-
: `Subject app declared at ${publicAppUrl} (loopback inside the desktop sandbox).`,
|
|
2753
|
-
simId: "sim-001",
|
|
2754
|
-
streamId: "stream-001"
|
|
2755
|
-
},
|
|
2756
|
-
args.session
|
|
2757
|
-
? {
|
|
2758
|
-
id: "event-002-session",
|
|
2759
|
-
at: args.createdAt,
|
|
2760
|
-
level: args.session.status === "passed" ? "info" : "warn",
|
|
2761
|
-
type: `cua-lab.session.${args.session.completionReason}`,
|
|
2762
|
-
message: `${args.session.status}: ${args.session.reason}`,
|
|
2763
|
-
simId: "sim-001",
|
|
2764
|
-
streamId: "stream-001"
|
|
2765
|
-
}
|
|
2766
|
-
: args.inProgress === true
|
|
2767
|
-
? {
|
|
2768
|
-
id: "event-002-running",
|
|
2769
|
-
at: args.createdAt,
|
|
2770
|
-
level: "info",
|
|
2771
|
-
type: "cua-lab.session.running",
|
|
2772
|
-
message: "Live computer-use session is running; terminal evidence has not been written yet.",
|
|
2773
|
-
simId: "sim-001",
|
|
2774
|
-
streamId: "stream-001"
|
|
2775
|
-
}
|
|
2776
|
-
: args.sessionError
|
|
2777
|
-
? {
|
|
2778
|
-
id: "event-002-session",
|
|
2779
|
-
at: args.createdAt,
|
|
2780
|
-
level: "error",
|
|
2781
|
-
type: "cua-lab.session.error",
|
|
2782
|
-
message: args.sessionError,
|
|
2783
|
-
simId: "sim-001",
|
|
2784
|
-
streamId: "stream-001"
|
|
2785
|
-
}
|
|
2786
|
-
: {
|
|
2787
|
-
id: "event-002-contract",
|
|
2788
|
-
at: args.createdAt,
|
|
2789
|
-
level: "info",
|
|
2790
|
-
type: "cua-lab.contract.ready",
|
|
2791
|
-
message: "Dry-run contract bundle ready; switch scenario.mode to live for a real desktop session.",
|
|
2792
|
-
simId: "sim-001",
|
|
2793
|
-
streamId: "stream-001"
|
|
2794
|
-
}
|
|
2795
|
-
];
|
|
2796
|
-
// Persisted phase trail (real boot timing, not just a coarse provenance sentence): one
|
|
2797
|
-
// RunEvent per COMPLETED phase boundary (started events never persist here; they carry no
|
|
2798
|
-
// durationMs). ok:false phases warn rather than error, since the failing phase's own thrown
|
|
2799
|
-
// error already becomes the terminal cua-lab.session.error event above.
|
|
2800
|
-
let phaseEventSeq = 3;
|
|
2801
|
-
for (const phase of args.phaseEvents ?? []) {
|
|
2802
|
-
events.push({
|
|
2803
|
-
id: `event-${String(phaseEventSeq++).padStart(3, "0")}-phase-${phaseEventIdSuffix(phase.type)}`,
|
|
2804
|
-
at: phase.at,
|
|
2805
|
-
level: phase.ok === false ? "warn" : "info",
|
|
2806
|
-
type: phase.type,
|
|
2807
|
-
message: phase.durationMs === undefined ? phase.message : `${phase.message} (${phase.durationMs}ms)`,
|
|
2808
|
-
simId: "sim-001",
|
|
2809
|
-
streamId: "stream-001"
|
|
2810
|
-
});
|
|
2811
|
-
}
|
|
2812
|
-
for (const warning of desktopGeometry?.warnings ?? []) {
|
|
2813
|
-
events.push({
|
|
2814
|
-
id: `event-${String(phaseEventSeq++).padStart(3, "0")}-geometry-warning`,
|
|
2815
|
-
at: args.createdAt,
|
|
2816
|
-
level: "warn",
|
|
2817
|
-
type: "cua-lab.geometry.warning",
|
|
2818
|
-
message: warning,
|
|
2819
|
-
simId: "sim-001",
|
|
2820
|
-
streamId: "stream-001"
|
|
2821
|
-
});
|
|
2822
|
-
}
|
|
2823
|
-
// A funnel with a denominator of one is still the funnel — and its absence stays honest: no
|
|
2824
|
-
// declared protocol (or a dry run) means no `tasks` field, never an empty one.
|
|
2825
|
-
const singleStudyTasks = args.inProgress !== true && args.session?.trace.taskFunnel !== undefined
|
|
2826
|
-
? aggregateTaskFunnels([args.session.trace.taskFunnel])
|
|
2827
|
-
: undefined;
|
|
2828
|
-
// What happened to the participant, as the LANE judged it — the same rule the fan-out roll-up
|
|
2829
|
-
// applies (participantStatusForOutcome). Before #476 this read the actor's own status, so a
|
|
2830
|
-
// run the lane refused as "not a credible pass" was written up as verdict pass, 1/1 reached
|
|
2831
|
-
// the goal, and every projection of the bundle (Observer tally, `runs`, the status index)
|
|
2832
|
-
// repeated it. Found on a real drawDB run whose participant wrote "Blocked after partial
|
|
2833
|
-
// completion".
|
|
2834
|
-
const participantStatus = args.session === undefined
|
|
2835
|
-
? undefined
|
|
2836
|
-
: participantStatusForCredibility(args.session.status, args.credibility);
|
|
2837
|
-
const credibilityNote = args.session === undefined || participantStatus === args.session.status
|
|
2838
|
-
? undefined
|
|
2839
|
-
: args.credibility?.noEngagement === true
|
|
2840
|
-
? "Not counted as a pass: the participant took no actions and said nothing."
|
|
2841
|
-
: "Not counted as a pass: the participant's final message described a blocker.";
|
|
2842
|
-
const review = withCuaReviewProvenance({
|
|
2843
|
-
schema: REVIEW_SCHEMA,
|
|
2844
|
-
verdict: args.inProgress === true
|
|
2845
|
-
? "contract_proof_only"
|
|
2846
|
-
: participantStatus !== undefined
|
|
2847
|
-
? verdictForStatus(participantStatus)
|
|
2848
|
-
: args.sessionError
|
|
2849
|
-
? "fail"
|
|
2850
|
-
: "contract_proof_only",
|
|
2851
|
-
// One lane is still a study with a denominator of one, and saying so keeps a single-lane
|
|
2852
|
-
// result from being read as though it generalized.
|
|
2853
|
-
...(participantStatus !== undefined && args.inProgress !== true
|
|
2854
|
-
? { participants: tallyParticipantOutcomes([participantStatus], [args.credibility?.reportedFriction === true]) }
|
|
2855
|
-
: {}),
|
|
2856
|
-
...(singleStudyTasks === undefined ? {} : { tasks: singleStudyTasks }),
|
|
2857
|
-
summary: credibilityNote === undefined ? reason : `${credibilityNote} ${reason}`,
|
|
2858
|
-
gaps: args.session || args.sessionError
|
|
2859
|
-
? []
|
|
2860
|
-
: args.inProgress === true
|
|
2861
|
-
? ["Live desktop session is still running."]
|
|
2862
|
-
: ["Live desktop session not yet run (dry-run contract only)."]
|
|
2863
|
-
}, [stream]);
|
|
2864
|
-
return {
|
|
2865
|
-
schema: RUN_BUNDLE_SCHEMA,
|
|
2866
|
-
...(args.realEmail && !args.dryRun ? { publication: { restrictions: ["real-communications"] } } : {}),
|
|
2867
|
-
runId: args.runId,
|
|
2868
|
-
mode: args.dryRun ? "dry-run" : "live",
|
|
2869
|
-
simCount: 1,
|
|
2870
|
-
createdAt: args.createdAt,
|
|
2871
|
-
cwd: PUBLIC_TARGET_CWD,
|
|
2872
|
-
artifactRoot: path.join(".humanish", "runs", args.runId),
|
|
2873
|
-
...(args.lab === undefined ? {} : { lab: args.lab }),
|
|
2874
|
-
source: args.source,
|
|
2875
|
-
persona: {
|
|
2876
|
-
id: args.persona.id,
|
|
2877
|
-
name: `Computer-use operator (${args.persona.id})`,
|
|
2878
|
-
source: `lab:${args.labId}`,
|
|
2879
|
-
sourceDigest: args.persona.promptDigest
|
|
2880
|
-
},
|
|
2881
|
-
scenario: {
|
|
2882
|
-
id: `cua-${args.labId}`,
|
|
2883
|
-
title: args.labTitle ?? `Computer-use lab: ${args.labId}`,
|
|
2884
|
-
goal: redactText(args.mission),
|
|
2885
|
-
source: `lab:${args.labId}`,
|
|
2886
|
-
sourceDigest: args.persona.promptDigest
|
|
2887
|
-
},
|
|
2888
|
-
lifecycle: [
|
|
2889
|
-
{
|
|
2890
|
-
at: args.createdAt,
|
|
2891
|
-
event: "cua-lab.run.created",
|
|
2892
|
-
message: `Created computer-use lab run with one desktop browser lane (actor ${args.actorId}).`
|
|
2893
|
-
}
|
|
2894
|
-
],
|
|
2895
|
-
simulations: [simulation],
|
|
2896
|
-
streams: [stream],
|
|
2897
|
-
events,
|
|
2898
|
-
redaction: {
|
|
2899
|
-
status: "passed",
|
|
2900
|
-
notes: traceScreenshotMode === "raw"
|
|
2901
|
-
? "Typed text recorded as length only and reasoning/messages pass through text redaction. Screenshots are FULL-FIDELITY (raw), retained for local use — NOT redacted for publishing; set policies.redactScreenshots: true to blur a share-as-is bundle."
|
|
2902
|
-
: traceScreenshotMode === "blurred"
|
|
2903
|
-
? "Typed text recorded as length only and reasoning/messages pass through text redaction. Screenshots are blurred at capture (policies.redactScreenshots: true) for a share-as-is bundle."
|
|
2904
|
-
: args.screenshots.length > 0
|
|
2905
|
-
? `Session ended before a trace was recorded; ${args.screenshots.length} already-written frame(s) follow the capture policy (${screenshotMode}). Typed text is recorded as length only and reasoning/messages pass through text redaction.`
|
|
2906
|
-
: "No screenshots captured. Typed text is recorded as length only and reasoning/messages pass through text redaction whenever a session runs."
|
|
2907
|
-
},
|
|
2908
|
-
artifacts: {
|
|
2909
|
-
run: "run.json",
|
|
2910
|
-
reviewJson: "review.json",
|
|
2911
|
-
reviewMarkdown: "review.md",
|
|
2912
|
-
observerData: "observer/observer-data.json",
|
|
2913
|
-
events: "events.ndjson"
|
|
2914
|
-
},
|
|
2915
|
-
review,
|
|
2916
|
-
// What the participant reported, when it reported anything (#392). Dry-run and in-progress
|
|
2917
|
-
// bundles carry none — there is no participant yet to quote.
|
|
2918
|
-
feedbackCandidates: args.dryRun || args.inProgress === true
|
|
2919
|
-
? []
|
|
2920
|
-
: participantFeedbackCandidates({
|
|
2921
|
-
runId: args.runId,
|
|
2922
|
-
scenarioId: `cua-${args.labId}`,
|
|
2923
|
-
adapterId: args.labId,
|
|
2924
|
-
goal: redactText(args.mission),
|
|
2925
|
-
substrate: args.feedbackSubstrate ?? (args.desktopRoute === false ? "local-filesystem" : "e2b-desktop"),
|
|
2926
|
-
lanes: [{
|
|
2927
|
-
laneId: args.laneId ?? "lane-01",
|
|
2928
|
-
streamId: "stream-001",
|
|
2929
|
-
personaId: args.persona.id,
|
|
2930
|
-
...(args.session === undefined ? {} : { session: args.session }),
|
|
2931
|
-
...(args.traceArtifactPath === undefined ? {} : { traceArtifactPath: args.traceArtifactPath }),
|
|
2932
|
-
screenshots: args.screenshots,
|
|
2933
|
-
...(args.commsArtifactPath === undefined ? {} : { commsArtifactPath: args.commsArtifactPath })
|
|
2934
|
-
}]
|
|
2935
|
-
}),
|
|
2936
|
-
// Custom desktop image provenance (omitted on the stock-template default → byte-stable).
|
|
2937
|
-
...(args.desktopTemplate === undefined ? {} : { desktopTemplate: args.desktopTemplate }),
|
|
2938
|
-
...(args.desktopBrowser === undefined ? {} : { desktopBrowser: args.desktopBrowser }),
|
|
2939
|
-
...(args.providerResources === undefined || args.providerResources.length === 0 ? {} : { providerResources: args.providerResources }),
|
|
2940
|
-
// Structured subject provenance (invariant 5): code pin + state story. Uniform and
|
|
2941
|
-
// honest on app-url bundles too — the caller minted the URL, its state is the caller's.
|
|
2942
|
-
// CuaSubjectProvenanceArg's two variants (clone, local-tree) are already RunSubjectProvenance-
|
|
2943
|
-
// shaped, so no reconstruction is needed beyond the app-url fallback.
|
|
2944
|
-
subject: args.subjectProvenance ?? { source: "app-url", state: { provenance: "undeclared" } },
|
|
2945
|
-
...(cost === undefined ? {} : { cost })
|
|
2946
|
-
};
|
|
2947
|
-
}
|
|
2948
|
-
/** Human-readable provenance line for the single-lane subject.provenance event (invariant 5):
|
|
2949
|
-
* claims "cloned/packed and served" ONLY when it actually happened. */
|
|
2950
|
-
function subjectProvenanceMessage(provenance, publicAppUrl, dryRun, hasSession) {
|
|
2951
|
-
if (provenance.source === "clone") {
|
|
2952
|
-
if (dryRun) {
|
|
2953
|
-
return `Subject declared: clone of ${provenance.repo}, to be served at ${publicAppUrl} in-sandbox (dry-run contract; nothing cloned)`;
|
|
2954
|
-
}
|
|
2955
|
-
if (provenance.commit) {
|
|
2956
|
-
return hasSession
|
|
2957
|
-
? `Subject cloned from ${provenance.repo}@${provenance.commit} and served at ${publicAppUrl} in-sandbox`
|
|
2958
|
-
: `Subject cloned from ${provenance.repo}@${provenance.commit}; serving at ${publicAppUrl} did not complete (see session error)`;
|
|
2959
|
-
}
|
|
2960
|
-
return `Subject clone attempted from ${provenance.repo}; commit unresolved (provisioning failed before resolution)`;
|
|
2961
|
-
}
|
|
2962
|
-
if (dryRun) {
|
|
2963
|
-
return `Subject declared: local working tree, to be packed and served at ${publicAppUrl} in-sandbox (dry-run contract; nothing packed)`;
|
|
2964
|
-
}
|
|
2965
|
-
if (provenance.archiveSha256) {
|
|
2966
|
-
const dirtyLabel = provenance.dirty === true ? ", dirty working tree" : provenance.dirty === false ? ", clean working tree" : "";
|
|
2967
|
-
return hasSession
|
|
2968
|
-
? `Subject packed (archiveSha256 ${provenance.archiveSha256}${dirtyLabel}) and served at ${publicAppUrl} in-sandbox`
|
|
2969
|
-
: `Subject packed (archiveSha256 ${provenance.archiveSha256}${dirtyLabel}); serving at ${publicAppUrl} did not complete (see session error)`;
|
|
2970
|
-
}
|
|
2971
|
-
return "Subject local-tree packing attempted; archive digest unresolved (provisioning failed before resolution)";
|
|
2972
|
-
}
|
|
2973
|
-
/**
|
|
2974
|
-
* Project N>1 fan-out lanes into a humanish.run-bundle.v1 (the evidence schema is unchanged; this
|
|
2975
|
-
* is a new producer for the multi-stream shape). One sim + one stream per lane; per-lane
|
|
2976
|
-
* provenance/session events; a recorded `cua-lab.fanout.plan` event (and a `cua-lab.fanout.fail-fast`
|
|
2977
|
-
* event when a harness error skipped queued lanes). N-ary verify/Observer already handle multiple
|
|
2978
|
-
* streams. The N=1 path NEVER reaches here (buildCuaBundle owns it, byte-stable).
|
|
2979
|
-
*/
|
|
2980
|
-
export function buildCuaFanoutBundle(args) {
|
|
2981
|
-
const { specs, outcomes, config } = args;
|
|
2982
|
-
const simulations = [];
|
|
2983
|
-
const streams = [];
|
|
2984
|
-
const events = [];
|
|
2985
|
-
events.push({
|
|
2986
|
-
id: "event-000-created",
|
|
2987
|
-
at: args.createdAt,
|
|
2988
|
-
level: "info",
|
|
2989
|
-
type: "cua-lab.run.created",
|
|
2990
|
-
message: `Created computer-use fan-out run for ${config.id} (actor ${args.descriptor.id}, ${specs.length} lanes, per-lane worlds).`
|
|
2991
|
-
});
|
|
2992
|
-
events.push({
|
|
2993
|
-
id: "event-001-fanout-plan",
|
|
2994
|
-
at: args.createdAt,
|
|
2995
|
-
level: "info",
|
|
2996
|
-
type: "cua-lab.fanout.plan",
|
|
2997
|
-
message: `Fan-out plan: ${args.plan.laneCount} lane(s) (${args.plan.strategy}), concurrency ${args.plan.concurrency}, ${args.plan.waves} wave(s); per-lane session budget ${Math.round(args.plan.perLaneSessionBudgetMs / 1000)}s; worst-case ~${args.plan.worstCaseSandboxMinutes} sandbox-minutes${args.dryRun ? " (dry-run: $0)" : ""}. Lanes: ${args.plan.lanes.map(formatLanePlanEntry).join(", ")}.`
|
|
2998
|
-
});
|
|
2999
|
-
let eventSeq = 2;
|
|
3000
|
-
const nextEventId = (suffix) => `event-${String(eventSeq++).padStart(3, "0")}-${suffix}`;
|
|
3001
|
-
if (args.rerun) {
|
|
3002
|
-
events.push({
|
|
3003
|
-
id: nextEventId("fanout-rerun"),
|
|
3004
|
-
at: args.createdAt,
|
|
3005
|
-
level: "info",
|
|
3006
|
-
type: "cua-lab.fanout.rerun",
|
|
3007
|
-
message: `Rerun selected ${args.rerun.selectedLaneIds.length} lane(s) from ${args.rerun.sourceRunId}: ${args.rerun.previous.map((lane) => `${lane.laneId} was ${lane.status}${lane.completionReason ? `/${lane.completionReason}` : ""}`).join(", ")}. This is a new linked run; the source run verdict is unchanged.`
|
|
3008
|
-
});
|
|
3009
|
-
}
|
|
3010
|
-
specs.forEach((spec, index) => {
|
|
3011
|
-
const outcome = outcomes?.[index];
|
|
3012
|
-
const laneAppUrl = spec.targetUrl ?? args.appUrl;
|
|
3013
|
-
const publicLaneAppUrl = publicSafeAppUrlLabel(laneAppUrl);
|
|
3014
|
-
const subject = args.laneSubjects[index];
|
|
3015
|
-
const session = outcome?.session;
|
|
3016
|
-
const fallbackDeclared = declaredScreenForRender(spec.devicePreset, spec.deviceName, spec.resolution);
|
|
3017
|
-
const desktopGeometry = outcome?.desktopGeometry ?? {
|
|
3018
|
-
screen: {
|
|
3019
|
-
requested: { width: spec.resolution[0], height: spec.resolution[1] },
|
|
3020
|
-
...(fallbackDeclared ? { declared: fallbackDeclared } : {})
|
|
3021
|
-
}
|
|
3022
|
-
};
|
|
3023
|
-
const screenshots = outcome?.screenshots ?? [];
|
|
3024
|
-
const lastScreenshot = screenshots[screenshots.length - 1];
|
|
3025
|
-
const status = args.inProgress === true && outcome === undefined
|
|
3026
|
-
? "running"
|
|
3027
|
-
: outcome?.skippedReason !== undefined
|
|
3028
|
-
? "blocked"
|
|
3029
|
-
: session
|
|
3030
|
-
? session.status
|
|
3031
|
-
: outcome?.sessionError
|
|
3032
|
-
? "failed"
|
|
3033
|
-
: "contract_proof_only";
|
|
3034
|
-
const reason = args.inProgress === true && outcome === undefined
|
|
3035
|
-
? "Live computer-use lane is running; stream auth URL is available only through the attached Observer server."
|
|
3036
|
-
: outcome?.skippedReason
|
|
3037
|
-
?? session?.reason
|
|
3038
|
-
?? outcome?.sessionError
|
|
3039
|
-
?? "Contract bundle only: dry-run produced the evidence shape without launching a desktop or spending provider tokens.";
|
|
3040
|
-
const traceScreenshotMode = session?.trace.redaction.screenshots;
|
|
3041
|
-
const screenshotMode = traceScreenshotMode === "raw" || traceScreenshotMode === "blurred"
|
|
3042
|
-
? traceScreenshotMode
|
|
3043
|
-
: config.policies?.redactScreenshots === true ? "blurred" : "raw";
|
|
3044
|
-
simulations.push({
|
|
3045
|
-
id: spec.simId,
|
|
3046
|
-
index: index + 1,
|
|
3047
|
-
personaId: spec.persona.id,
|
|
3048
|
-
scenarioId: `cua-${config.id}`,
|
|
3049
|
-
status,
|
|
3050
|
-
streamKind: "browser",
|
|
3051
|
-
mode: "browser-sim",
|
|
3052
|
-
progress: args.inProgress === true && outcome === undefined ? 20 : 100,
|
|
3053
|
-
currentStep: reason,
|
|
3054
|
-
summary: session
|
|
3055
|
-
? `Lane ${spec.laneId} (${spec.persona.id}/${spec.deviceName}): computer-use actor (${args.descriptor.id}) drove the subject app; ${session.completionReason}.`
|
|
3056
|
-
: args.inProgress === true && outcome === undefined
|
|
3057
|
-
? `Lane ${spec.laneId} (${spec.persona.id}/${spec.deviceName}): computer-use actor (${args.descriptor.id}) is driving the subject app.`
|
|
3058
|
-
: outcome?.skippedReason !== undefined
|
|
3059
|
-
? `Lane ${spec.laneId} ${outcome.skippedReason}.`
|
|
3060
|
-
: outcome?.sessionError
|
|
3061
|
-
? `Lane ${spec.laneId} failed before a terminal session verdict: ${outcome.sessionError}`
|
|
3062
|
-
: `Contract lane ${spec.laneId} (${spec.persona.id}/${spec.deviceName}) for ${args.descriptor.id} against ${publicLaneAppUrl}.`,
|
|
3063
|
-
streamIds: [spec.streamId],
|
|
3064
|
-
startedAt: args.createdAt,
|
|
3065
|
-
updatedAt: args.createdAt
|
|
3066
|
-
});
|
|
3067
|
-
streams.push({
|
|
3068
|
-
id: spec.streamId,
|
|
3069
|
-
simId: spec.simId,
|
|
3070
|
-
laneId: spec.laneId,
|
|
3071
|
-
...(spec.assignment === undefined ? {} : { assignment: participantAssignment(spec.assignment) }),
|
|
3072
|
-
...(spec.actorType === undefined ? {} : { actorType: spec.actorType }),
|
|
3073
|
-
...(spec.surface === undefined ? {} : { surface: spec.surface }),
|
|
3074
|
-
...(spec.caseGroup === undefined ? {} : { caseGroup: spec.caseGroup }),
|
|
3075
|
-
kind: "browser",
|
|
3076
|
-
label: `CUA lane ${spec.laneId} — ${config.id}`,
|
|
3077
|
-
status,
|
|
3078
|
-
transport: "snapshot",
|
|
3079
|
-
updatedAt: args.createdAt,
|
|
3080
|
-
embed: lastScreenshot
|
|
3081
|
-
? { kind: "screenshot", url: lastScreenshot, title: `CUA desktop ${spec.laneId} (${screenshotMode})` }
|
|
3082
|
-
: { kind: "placeholder", title: `CUA desktop ${spec.laneId}` },
|
|
3083
|
-
...(desktopGeometry.viewport === undefined
|
|
3084
|
-
? {}
|
|
3085
|
-
: {
|
|
3086
|
-
viewport: {
|
|
3087
|
-
width: desktopGeometry.viewport.width,
|
|
3088
|
-
height: desktopGeometry.viewport.height,
|
|
3089
|
-
deviceScaleFactor: desktopGeometry.viewport.deviceScaleFactor,
|
|
3090
|
-
isMobile: spec.devicePreset.isMobile
|
|
3091
|
-
}
|
|
3092
|
-
}),
|
|
3093
|
-
desktopGeometry,
|
|
3094
|
-
...(outcome?.recording === undefined ? {} : { recording: outcome.recording }),
|
|
3095
|
-
ui: {
|
|
3096
|
-
route: publicLaneAppUrl,
|
|
3097
|
-
intent: `Watch lane ${spec.laneId} (${spec.persona.id}/${spec.deviceName}) drive the subject app in its own hosted desktop.`,
|
|
3098
|
-
state: reason,
|
|
3099
|
-
...(session ? { actorStatus: session.status } : {}),
|
|
3100
|
-
...(lastScreenshot ? { screenshotUrl: lastScreenshot } : {})
|
|
3101
|
-
},
|
|
3102
|
-
...(session ? { actor: session.trace } : {}),
|
|
3103
|
-
artifacts: [
|
|
3104
|
-
{ label: "run bundle", path: "run.json", kind: "bundle" },
|
|
3105
|
-
{ label: "review", path: "review.md", kind: "review" },
|
|
3106
|
-
{ label: "events", path: "events.ndjson", kind: "events" },
|
|
3107
|
-
...(session
|
|
3108
|
-
? [{ label: `lane ${spec.laneId} actor trace`, path: spec.traceArtifactPath, kind: "trace" }]
|
|
3109
|
-
: []),
|
|
3110
|
-
...(outcome?.commsArtifactPath
|
|
3111
|
-
? [{ label: `lane ${spec.laneId} comms thread`, path: outcome.commsArtifactPath, kind: "log" }]
|
|
3112
|
-
: []),
|
|
3113
|
-
...(outcome?.recording ? [{ label: "desktop recording", path: outcome.recording.path, kind: "recording" }] : []),
|
|
3114
|
-
...screenshots.map((screenshot, screenshotIndex) => ({
|
|
3115
|
-
label: `lane ${spec.laneId} screenshot ${String(screenshotIndex + 1).padStart(2, "0")} (${screenshotMode})`,
|
|
3116
|
-
path: screenshot,
|
|
3117
|
-
kind: "screenshot"
|
|
3118
|
-
}))
|
|
3119
|
-
]
|
|
3120
|
-
});
|
|
3121
|
-
// Per-lane subject provenance (invariant 5).
|
|
3122
|
-
if (args.cloneRoute && args.publicRepo) {
|
|
3123
|
-
events.push({
|
|
3124
|
-
id: nextEventId(`subject-${spec.laneId}`),
|
|
3125
|
-
at: args.createdAt,
|
|
3126
|
-
level: "info",
|
|
3127
|
-
type: "cua-lab.subject.provenance",
|
|
3128
|
-
message: `Lane ${spec.laneId}: ${args.dryRun
|
|
3129
|
-
? `subject declared — clone of ${args.publicRepo}, served at ${publicLaneAppUrl} in-sandbox (dry-run contract; nothing cloned)`
|
|
3130
|
-
: subject.commit
|
|
3131
|
-
? session
|
|
3132
|
-
? `subject cloned from ${args.publicRepo}@${subject.commit} and served at ${publicLaneAppUrl} in-sandbox`
|
|
3133
|
-
: `subject cloned from ${args.publicRepo}@${subject.commit}; serving did not complete (see session error)`
|
|
3134
|
-
: `subject clone attempted from ${args.publicRepo}; commit unresolved`} (subject env names: ${args.subjectEnvNames.length > 0 ? args.subjectEnvNames.join(", ") : "none"}; values never persisted); state: ${describeSubjectState(subject.state, args.dryRun)}.`,
|
|
3135
|
-
simId: spec.simId,
|
|
3136
|
-
streamId: spec.streamId
|
|
3137
|
-
});
|
|
3138
|
-
}
|
|
3139
|
-
else if (subject.source === "local-tree") {
|
|
3140
|
-
events.push({
|
|
3141
|
-
id: nextEventId(`subject-${spec.laneId}`),
|
|
3142
|
-
at: args.createdAt,
|
|
3143
|
-
level: "info",
|
|
3144
|
-
type: "cua-lab.subject.provenance",
|
|
3145
|
-
message: `Lane ${spec.laneId}: ${args.dryRun
|
|
3146
|
-
? `subject declared: local working tree, to be packed and served at ${publicLaneAppUrl} in-sandbox (dry-run contract; nothing packed)`
|
|
3147
|
-
: subject.archiveSha256
|
|
3148
|
-
? session
|
|
3149
|
-
? `subject packed (archiveSha256 ${subject.archiveSha256}${subject.dirty === true ? ", dirty working tree" : subject.dirty === false ? ", clean working tree" : ""}) and served at ${publicLaneAppUrl} in-sandbox`
|
|
3150
|
-
: `subject packed (archiveSha256 ${subject.archiveSha256}); serving did not complete (see session error)`
|
|
3151
|
-
: "subject local-tree packing attempted; archive digest unresolved"} (subject env names: ${args.subjectEnvNames.length > 0 ? args.subjectEnvNames.join(", ") : "none"}; values never persisted); state: ${describeSubjectState(subject.state, args.dryRun)}.`,
|
|
3152
|
-
simId: spec.simId,
|
|
3153
|
-
streamId: spec.streamId
|
|
3154
|
-
});
|
|
3155
|
-
}
|
|
3156
|
-
else {
|
|
3157
|
-
events.push({
|
|
3158
|
-
id: nextEventId(`subject-${spec.laneId}`),
|
|
3159
|
-
at: args.createdAt,
|
|
3160
|
-
level: "info",
|
|
3161
|
-
type: "cua-lab.subject.declared",
|
|
3162
|
-
message: `Lane ${spec.laneId}: subject app declared at ${publicLaneAppUrl} (loopback inside the lane's own desktop sandbox).`,
|
|
3163
|
-
simId: spec.simId,
|
|
3164
|
-
streamId: spec.streamId
|
|
3165
|
-
});
|
|
3166
|
-
}
|
|
3167
|
-
// Per-lane session event.
|
|
3168
|
-
if (session) {
|
|
3169
|
-
events.push({
|
|
3170
|
-
id: nextEventId(`session-${spec.laneId}`),
|
|
3171
|
-
at: args.createdAt,
|
|
3172
|
-
level: session.status === "passed" ? "info" : "warn",
|
|
3173
|
-
type: `cua-lab.session.${session.completionReason}`,
|
|
3174
|
-
message: `Lane ${spec.laneId}: ${session.status} — ${session.reason}`,
|
|
3175
|
-
simId: spec.simId,
|
|
3176
|
-
streamId: spec.streamId
|
|
3177
|
-
});
|
|
3178
|
-
}
|
|
3179
|
-
else if (args.inProgress === true && outcome === undefined) {
|
|
3180
|
-
events.push({
|
|
3181
|
-
id: nextEventId(`running-${spec.laneId}`),
|
|
3182
|
-
at: args.createdAt,
|
|
3183
|
-
level: "info",
|
|
3184
|
-
type: "cua-lab.session.running",
|
|
3185
|
-
message: `Lane ${spec.laneId}: live computer-use session is running; terminal evidence has not been written yet.`,
|
|
3186
|
-
simId: spec.simId,
|
|
3187
|
-
streamId: spec.streamId
|
|
3188
|
-
});
|
|
3189
|
-
}
|
|
3190
|
-
else if (outcome?.skippedReason !== undefined) {
|
|
3191
|
-
events.push({
|
|
3192
|
-
id: nextEventId(`blocked-${spec.laneId}`),
|
|
3193
|
-
at: args.createdAt,
|
|
3194
|
-
level: "warn",
|
|
3195
|
-
type: "cua-lab.session.blocked",
|
|
3196
|
-
message: `Lane ${spec.laneId} ${outcome.skippedReason}.`,
|
|
3197
|
-
simId: spec.simId,
|
|
3198
|
-
streamId: spec.streamId
|
|
3199
|
-
});
|
|
3200
|
-
}
|
|
3201
|
-
else if (outcome?.sessionError) {
|
|
3202
|
-
events.push({
|
|
3203
|
-
id: nextEventId(`session-error-${spec.laneId}`),
|
|
3204
|
-
at: args.createdAt,
|
|
3205
|
-
level: "error",
|
|
3206
|
-
type: "cua-lab.session.error",
|
|
3207
|
-
message: `Lane ${spec.laneId}: ${outcome.sessionError}`,
|
|
3208
|
-
simId: spec.simId,
|
|
3209
|
-
streamId: spec.streamId
|
|
3210
|
-
});
|
|
3211
|
-
}
|
|
3212
|
-
else {
|
|
3213
|
-
events.push({
|
|
3214
|
-
id: nextEventId(`contract-${spec.laneId}`),
|
|
3215
|
-
at: args.createdAt,
|
|
3216
|
-
level: "info",
|
|
3217
|
-
type: "cua-lab.contract.ready",
|
|
3218
|
-
message: `Lane ${spec.laneId}: dry-run contract lane ready; switch scenario.mode to live for a real desktop session.`,
|
|
3219
|
-
simId: spec.simId,
|
|
3220
|
-
streamId: spec.streamId
|
|
3221
|
-
});
|
|
3222
|
-
}
|
|
3223
|
-
for (const warning of desktopGeometry.warnings ?? []) {
|
|
3224
|
-
events.push({
|
|
3225
|
-
id: nextEventId(`geometry-warning-${spec.laneId}`),
|
|
3226
|
-
at: args.createdAt,
|
|
3227
|
-
level: "warn",
|
|
3228
|
-
type: "cua-lab.geometry.warning",
|
|
3229
|
-
message: warning,
|
|
3230
|
-
simId: spec.simId,
|
|
3231
|
-
streamId: spec.streamId
|
|
3232
|
-
});
|
|
3233
|
-
}
|
|
3234
|
-
// Persisted per-lane phase trail (real boot timing): one RunEvent per COMPLETED phase
|
|
3235
|
-
// boundary this lane recorded (started events never persist here; they carry no durationMs).
|
|
3236
|
-
for (const phase of outcome?.phaseRecords ?? []) {
|
|
3237
|
-
events.push({
|
|
3238
|
-
id: nextEventId(`phase-${spec.laneId}-${phaseEventIdSuffix(phase.type)}`),
|
|
3239
|
-
at: phase.at,
|
|
3240
|
-
level: phase.ok === false ? "warn" : "info",
|
|
3241
|
-
type: phase.type,
|
|
3242
|
-
message: phase.durationMs === undefined
|
|
3243
|
-
? `Lane ${spec.laneId}: ${phase.message}`
|
|
3244
|
-
: `Lane ${spec.laneId}: ${phase.message} (${phase.durationMs}ms)`,
|
|
3245
|
-
simId: spec.simId,
|
|
3246
|
-
streamId: spec.streamId
|
|
3247
|
-
});
|
|
3248
|
-
}
|
|
3249
|
-
});
|
|
3250
|
-
if (args.failFastReason) {
|
|
3251
|
-
events.push({
|
|
3252
|
-
id: nextEventId("fanout-fail-fast"),
|
|
3253
|
-
at: args.createdAt,
|
|
3254
|
-
level: "warn",
|
|
3255
|
-
type: "cua-lab.fanout.fail-fast",
|
|
3256
|
-
message: `Fan-out fail-fast: ${args.failFastReason}. In-flight lanes finished; queued lanes were skipped (blocked) — completed evidence is retained.`
|
|
3257
|
-
});
|
|
3258
|
-
}
|
|
3259
|
-
// Worst-of review verdict across lanes; live fan-out must prove every lane.
|
|
3260
|
-
const verdict = args.inProgress === true
|
|
3261
|
-
? "contract_proof_only"
|
|
3262
|
-
: fanoutReviewVerdict({
|
|
3263
|
-
dryRun: args.dryRun,
|
|
3264
|
-
expectedLaneCount: specs.length,
|
|
3265
|
-
outcomes
|
|
3266
|
-
});
|
|
3267
|
-
const passedLanes = (outcomes ?? []).filter((outcome) => outcome.skippedReason === undefined
|
|
3268
|
-
&& outcome.session !== undefined
|
|
3269
|
-
&& outcome.session.status === "passed"
|
|
3270
|
-
&& outcome.session.completionReason !== "harness_error"
|
|
3271
|
-
&& outcome.sessionError === undefined
|
|
3272
|
-
&& !outcome.noEngagement
|
|
3273
|
-
&& !outcome.selfReportedBlocker).length;
|
|
3274
|
-
// What happened to the PARTICIPANTS, with the denominator attached. The verdict above has to
|
|
3275
|
-
// collapse the run to one word; this does not (docs/principles/three-roles.md).
|
|
3276
|
-
const terminalOutcomes = (outcomes ?? []).filter((outcome) => outcome?.session?.status !== undefined);
|
|
3277
|
-
const participants = terminalOutcomes.length > 0
|
|
3278
|
-
? tallyParticipantOutcomes(
|
|
3279
|
-
// A NO-ENGAGEMENT lane is not a participant who reached the goal. It said "done" having
|
|
3280
|
-
// taken zero actions and said nothing, and `passedLanes` below already refuses to count
|
|
3281
|
-
// it — but `reachedGoal` was reading the trace status directly, so one run could be both
|
|
3282
|
-
// "not a passed lane" AND "1/1 reached the goal". The headline number a researcher reads
|
|
3283
|
-
// first was the dishonest one. Found by a provider bug that ended a study on turn one.
|
|
3284
|
-
terminalOutcomes.map((outcome) => participantStatusForCredibility(outcome.session.status, {
|
|
3285
|
-
noEngagement: outcome.noEngagement === true,
|
|
3286
|
-
selfReportedBlocker: outcome.selfReportedBlocker === true
|
|
3287
|
-
})),
|
|
3288
|
-
// A participant who reached the goal AND told you the road there was broken is the most
|
|
3289
|
-
// useful result a study produces; reporting only the outcome would bury it.
|
|
3290
|
-
terminalOutcomes.map((outcome) => outcome.reportedFriction === true))
|
|
3291
|
-
: undefined;
|
|
3292
|
-
// The study funnel: per-task completion rates across every session that measured one. This is
|
|
3293
|
-
// "where did people get stuck" as data, next to WHO got stuck (participants) above.
|
|
3294
|
-
const participantFunnels = (outcomes ?? [])
|
|
3295
|
-
.map((outcome) => outcome?.session?.trace.taskFunnel)
|
|
3296
|
-
.filter((funnel) => funnel !== undefined);
|
|
3297
|
-
const studyTasks = args.inProgress === true ? undefined : aggregateTaskFunnels(participantFunnels);
|
|
3298
|
-
const participantEndings = terminalOutcomes.map((outcome) => {
|
|
3299
|
-
const ending = actorEnding(outcome.session.trace);
|
|
3300
|
-
return { status: outcome.session.status, ...(ending === undefined ? {} : { label: ending.label }) };
|
|
3301
|
-
});
|
|
3302
|
-
const review = withCuaReviewProvenance({
|
|
3303
|
-
schema: REVIEW_SCHEMA,
|
|
3304
|
-
verdict,
|
|
3305
|
-
...(participants === undefined ? {} : { participants }),
|
|
3306
|
-
...(studyTasks === undefined ? {} : { tasks: studyTasks }),
|
|
3307
|
-
summary: args.inProgress === true
|
|
3308
|
-
? `Live computer-use fan-out is running (${specs.length} per-lane worlds); terminal lane evidence has not been written yet.`
|
|
3309
|
-
: args.dryRun
|
|
3310
|
-
? `${args.rerun ? `Rerun contract from ${args.rerun.sourceRunId}: ` : ""}Dry-run fan-out contract: ${specs.length} per-lane-world lanes composed for ${args.descriptor.id} against ${args.appUrl}; no desktops launched, $0 spend.`
|
|
3311
|
-
: `${args.rerun ? `Rerun from ${args.rerun.sourceRunId}: ` : ""}Computer-use fan-out (${specs.length} per-lane worlds): ${passedLanes}/${specs.length} lane(s) reached a terminal, engaged verdict${participants ? ` — ${formatParticipantOutcomes(participants, participantEndings)}` : ""}${studyTasks ? `; tasks: ${formatStudyTaskFunnel(studyTasks)}` : ""}.`,
|
|
3312
|
-
gaps: args.inProgress === true
|
|
3313
|
-
? ["Live fan-out session is still running."]
|
|
3314
|
-
: args.dryRun
|
|
3315
|
-
? ["Live fan-out session not yet run (dry-run contract only)."]
|
|
3316
|
-
: specs
|
|
3317
|
-
.map((spec, index) => ({ spec, outcome: outcomes?.[index] }))
|
|
3318
|
-
.filter(({ outcome }) => outcome === undefined
|
|
3319
|
-
|| outcome.skippedReason !== undefined
|
|
3320
|
-
|| outcome.sessionError !== undefined
|
|
3321
|
-
|| outcome.noEngagement
|
|
3322
|
-
|| outcome.selfReportedBlocker
|
|
3323
|
-
|| outcome.session === undefined
|
|
3324
|
-
|| outcome.session.status !== "passed")
|
|
3325
|
-
.map(({ spec, outcome }) => `${spec.laneId}: ${outcome?.skippedReason ?? outcome?.sessionError ?? outcome?.session?.reason ?? "did not pass"}`)
|
|
3326
|
-
}, streams);
|
|
3327
|
-
const anyRaw = (outcomes ?? []).some((outcome) => outcome.session?.trace.redaction.screenshots === "raw");
|
|
3328
|
-
const ranLive = (outcomes ?? []).some((outcome) => outcome.session !== undefined || outcome.sessionError !== undefined);
|
|
3329
|
-
const configuredBrowser = config.execution?.desktop?.browser;
|
|
3330
|
-
const desktopTemplate = e2bDesktopTemplate(config);
|
|
3331
|
-
const resolvedBrowsers = (outcomes ?? [])
|
|
3332
|
-
.map((outcome) => outcome.desktopBrowser?.resolved)
|
|
3333
|
-
.filter((value) => value !== undefined);
|
|
3334
|
-
const unanimousResolvedBrowser = resolvedBrowsers.length > 0 && new Set(resolvedBrowsers).size === 1
|
|
3335
|
-
? resolvedBrowsers[0]
|
|
3336
|
-
: undefined;
|
|
3337
|
-
const providerResources = (outcomes ?? []).flatMap((outcome) => providerResourcesForOutcome({
|
|
3338
|
-
outcome,
|
|
3339
|
-
createdAt: args.createdAt,
|
|
3340
|
-
simId: outcome.spec.simId,
|
|
3341
|
-
streamId: outcome.spec.streamId,
|
|
3342
|
-
laneId: outcome.spec.laneId
|
|
3343
|
-
}));
|
|
3344
|
-
// Run-level cost ESTIMATE: one model-token line per lane that ran a session (from its persisted
|
|
3345
|
-
// trace.estimatedCost) + a desktop line per owned allocation, priced at its observed resources.
|
|
3346
|
-
// Per-lane worlds have no shared provisioning to double-count. Omitted on a pure dry-run.
|
|
3347
|
-
const costLanes = specs
|
|
3348
|
-
.map((spec, index) => ({ laneId: spec.laneId, outcome: outcomes?.[index] }))
|
|
3349
|
-
.filter((entry) => entry.outcome?.session !== undefined)
|
|
3350
|
-
.map((entry) => ({ laneId: entry.laneId, trace: entry.outcome.session.trace }));
|
|
3351
|
-
const desktops = (outcomes ?? []).filter(outcome => outcome.sandboxId !== undefined).map(outcome => ({
|
|
3352
|
-
laneId: outcome.spec.laneId, minutes: desktopSpanToMinutes(outcome.desktopDurationMs),
|
|
3353
|
-
observation: outcome.desktopResources, lifetimeComplete: outcome.killed
|
|
3354
|
-
}));
|
|
3355
|
-
const cost = buildCuaCostSummary({ lanes: costLanes, desktops });
|
|
3356
|
-
return {
|
|
3357
|
-
schema: RUN_BUNDLE_SCHEMA,
|
|
3358
|
-
...receivingPublication(args.config, args.dryRun),
|
|
3359
|
-
runId: args.runId,
|
|
3360
|
-
mode: args.dryRun ? "dry-run" : "live",
|
|
3361
|
-
simCount: specs.length,
|
|
3362
|
-
createdAt: args.createdAt,
|
|
3363
|
-
cwd: PUBLIC_TARGET_CWD,
|
|
3364
|
-
artifactRoot: path.join(".humanish", "runs", args.runId),
|
|
3365
|
-
...(args.lab === undefined ? {} : { lab: args.lab }),
|
|
3366
|
-
source: args.source,
|
|
3367
|
-
persona: {
|
|
3368
|
-
id: specs[0].persona.id,
|
|
3369
|
-
name: `Computer-use fan-out (${specs.length} lanes)`,
|
|
3370
|
-
source: `lab:${config.id}`,
|
|
3371
|
-
sourceDigest: specs[0].persona.promptDigest
|
|
3372
|
-
},
|
|
3373
|
-
scenario: {
|
|
3374
|
-
id: `cua-${config.id}`,
|
|
3375
|
-
title: config.title ?? `Computer-use fan-out: ${config.id}`,
|
|
3376
|
-
// Redacted at WRITE time, like every other raw-text surface in the bundle. Lane records are
|
|
3377
|
-
// digest-only by design, but scenario.goal keeps one lane's composed instructions verbatim —
|
|
3378
|
-
// and an adopter whose authored lane text must name a runtime world URL (an inbox on a route
|
|
3379
|
-
// where the harness does not inject one) put an *.e2b.app address in it. That landed raw here
|
|
3380
|
-
// and in observer-data.json, the sensitive-text scanner matched it, and verify failed a bundle
|
|
3381
|
-
// this writer produced. The only adopter-side workaround was scanner evasion (#412).
|
|
3382
|
-
//
|
|
3383
|
-
// The instructions the model actually receives are untouched; only the persisted copy changes.
|
|
3384
|
-
goal: redactText(specs[0].evidenceInstructions ?? specs[0].instructions),
|
|
3385
|
-
source: `lab:${config.id}`,
|
|
3386
|
-
sourceDigest: specs[0].persona.promptDigest
|
|
3387
|
-
},
|
|
3388
|
-
lifecycle: [
|
|
3389
|
-
{
|
|
3390
|
-
at: args.createdAt,
|
|
3391
|
-
event: "cua-lab.run.created",
|
|
3392
|
-
message: `Created computer-use fan-out run with ${specs.length} per-lane desktop browser lanes (actor ${args.descriptor.id}).`
|
|
3393
|
-
}
|
|
3394
|
-
],
|
|
3395
|
-
simulations,
|
|
3396
|
-
streams,
|
|
3397
|
-
events,
|
|
3398
|
-
...(args.rerun === undefined ? {} : { rerun: args.rerun }),
|
|
3399
|
-
redaction: {
|
|
3400
|
-
status: "passed",
|
|
3401
|
-
notes: ranLive
|
|
3402
|
-
? anyRaw
|
|
3403
|
-
? "Typed text recorded as length only and reasoning/messages pass through text redaction. Some lanes captured FULL-FIDELITY (raw) screenshots, retained for local use — NOT redacted for publishing; set policies.redactScreenshots: true to blur a share-as-is bundle."
|
|
3404
|
-
: "Typed text recorded as length only and reasoning/messages pass through text redaction. Screenshots are blurred at capture (policies.redactScreenshots: true) for a share-as-is bundle."
|
|
3405
|
-
: "Dry-run fan-out contract bundle: no desktops launched and no screenshots captured. Typed text is recorded as length only and reasoning/messages pass through text redaction whenever a session runs."
|
|
3406
|
-
},
|
|
3407
|
-
artifacts: {
|
|
3408
|
-
run: "run.json",
|
|
3409
|
-
reviewJson: "review.json",
|
|
3410
|
-
reviewMarkdown: "review.md",
|
|
3411
|
-
observerData: "observer/observer-data.json",
|
|
3412
|
-
events: "events.ndjson"
|
|
3413
|
-
},
|
|
3414
|
-
review,
|
|
3415
|
-
// What the participants reported, when any reported anything (#392). Dry-run and in-progress
|
|
3416
|
-
// bundles carry none — there is no participant yet to quote.
|
|
3417
|
-
feedbackCandidates: args.dryRun || args.inProgress === true
|
|
3418
|
-
? []
|
|
3419
|
-
: participantFeedbackCandidates({
|
|
3420
|
-
runId: args.runId,
|
|
3421
|
-
scenarioId: `cua-${config.id}`,
|
|
3422
|
-
adapterId: config.id,
|
|
3423
|
-
goal: redactText(specs[0].evidenceInstructions ?? specs[0].instructions),
|
|
3424
|
-
substrate: config.execution?.target === "local" ? "local-desktop" : "e2b-desktop",
|
|
3425
|
-
lanes: specs.map((spec, index) => {
|
|
3426
|
-
const outcome = outcomes?.[index];
|
|
3427
|
-
return {
|
|
3428
|
-
laneId: spec.laneId,
|
|
3429
|
-
streamId: spec.streamId,
|
|
3430
|
-
personaId: spec.persona.id,
|
|
3431
|
-
...(outcome?.session === undefined ? {} : { session: outcome.session }),
|
|
3432
|
-
...(outcome?.session === undefined ? {} : { traceArtifactPath: spec.traceArtifactPath }),
|
|
3433
|
-
screenshots: outcome?.screenshots ?? [],
|
|
3434
|
-
...(outcome?.commsArtifactPath === undefined ? {} : { commsArtifactPath: outcome.commsArtifactPath })
|
|
3435
|
-
};
|
|
3436
|
-
})
|
|
3437
|
-
}),
|
|
3438
|
-
// Selected hosted image, including the optional speech default; omitted on the stock desktop.
|
|
3439
|
-
...(desktopTemplate === undefined ? {} : { desktopTemplate }),
|
|
3440
|
-
...(configuredBrowser === undefined
|
|
3441
|
-
? {}
|
|
3442
|
-
: { desktopBrowser: { requested: configuredBrowser, ...(unanimousResolvedBrowser === undefined ? {} : { resolved: unanimousResolvedBrowser }) } }),
|
|
3443
|
-
...(providerResources.length === 0 ? {} : { providerResources }),
|
|
3444
|
-
subject: args.aggregateSubject,
|
|
3445
|
-
...(cost === undefined ? {} : { cost })
|
|
3446
|
-
};
|
|
3447
|
-
}
|
|
3448
|
-
function providerResourcesForOutcome(args) {
|
|
3449
|
-
if (args.outcome?.sandboxId === undefined) {
|
|
3450
|
-
return [];
|
|
3451
|
-
}
|
|
3452
|
-
return [{
|
|
3453
|
-
schema: "humanish.provider-resource.v1",
|
|
3454
|
-
provider: "e2b-desktop",
|
|
3455
|
-
kind: "sandbox",
|
|
3456
|
-
id: args.outcome.sandboxId,
|
|
3457
|
-
owner: "humanish",
|
|
3458
|
-
status: args.outcome.killed ? "killed" : "running",
|
|
3459
|
-
simId: args.simId,
|
|
3460
|
-
streamId: args.streamId,
|
|
3461
|
-
laneId: args.laneId,
|
|
3462
|
-
createdAt: args.createdAt,
|
|
3463
|
-
cleanup: {
|
|
3464
|
-
killed: args.outcome.killed,
|
|
3465
|
-
reason: args.outcome.killed
|
|
3466
|
-
? "killed during normal lane teardown"
|
|
3467
|
-
: "not killed during normal lane teardown; cleanup may reclaim by exact recorded id"
|
|
3468
|
-
}
|
|
3469
|
-
}];
|
|
3470
|
-
}
|
|
3471
|
-
/**
|
|
3472
|
-
* The status a participant is TALLIED under, given what the lane made of the session. A
|
|
3473
|
-
* goal_satisfied claim with zero engagement is a session that ran out before anything happened;
|
|
3474
|
-
* one whose final message describes a blocker is a participant who could not proceed and said so.
|
|
3475
|
-
* Both keep their trace status (the claim is evidence); neither is a participant who reached the
|
|
3476
|
-
* goal. One rule for the single lane and the fan-out roll-up (#476).
|
|
3477
|
-
*/
|
|
3478
|
-
export function participantStatusForCredibility(status, credibility) {
|
|
3479
|
-
if (status !== "passed" || credibility === undefined)
|
|
3480
|
-
return status;
|
|
3481
|
-
if (credibility.noEngagement)
|
|
3482
|
-
return "incomplete";
|
|
3483
|
-
if (credibility.selfReportedBlocker)
|
|
3484
|
-
return "blocked";
|
|
3485
|
-
return status;
|
|
3486
|
-
}
|
|
3487
|
-
function verdictForStatus(status) {
|
|
3488
|
-
switch (status) {
|
|
3489
|
-
case "passed":
|
|
3490
|
-
return "pass";
|
|
3491
|
-
case "failed":
|
|
3492
|
-
return "fail";
|
|
3493
|
-
case "blocked":
|
|
3494
|
-
return "blocked";
|
|
3495
|
-
case "timed_out":
|
|
3496
|
-
return "timed_out";
|
|
3497
|
-
// A participant who abandoned, or a session that ran out before the goal, did not pass — but the
|
|
3498
|
-
// harness did not fail either. The run reports what happened rather than a verdict on the tool.
|
|
3499
|
-
case "abandoned":
|
|
3500
|
-
case "incomplete":
|
|
3501
|
-
return "fail";
|
|
3502
|
-
}
|
|
3503
|
-
}
|
|
3504
|
-
function renderCuaReviewMarkdown(bundle) {
|
|
3505
|
-
const trace = bundle.streams[0]?.actor;
|
|
3506
|
-
const provenance = bundle.events.find((event) => event.type === "cua-lab.subject.provenance");
|
|
3507
|
-
return [
|
|
3508
|
-
`# ${bundle.scenario.title}`,
|
|
3509
|
-
"",
|
|
3510
|
-
`- run: ${bundle.runId}`,
|
|
3511
|
-
`- mode: ${bundle.mode}`,
|
|
3512
|
-
`- run gate: ${bundle.review.verdict}`,
|
|
3513
|
-
`- summary: ${bundle.review.summary}`,
|
|
3514
|
-
...(provenance ? [`- subject: ${provenance.message}`] : []),
|
|
3515
|
-
...(trace
|
|
3516
|
-
? [
|
|
3517
|
-
`- actor: ${trace.provider} (${trace.lane}/${trace.protocol})`,
|
|
3518
|
-
// Honest count: name the trace's actual screenshot mode ("raw" | "blurred"); say
|
|
3519
|
-
// nothing when no frames exist ("n/a") rather than claim a redaction that never ran.
|
|
3520
|
-
`- evidence: ${trace.items.length} trace item(s), ${trace.counts.screenshots ?? 0} ${trace.redaction.screenshots === "raw" || trace.redaction.screenshots === "blurred"
|
|
3521
|
-
? `${trace.redaction.screenshots} screenshot(s)`
|
|
3522
|
-
: "screenshot(s)"}`
|
|
3523
|
-
]
|
|
3524
|
-
: []),
|
|
3525
|
-
...(bundle.review.gaps.length > 0 ? ["", "## Gaps", ...bundle.review.gaps.map((gap) => `- ${gap}`)] : []),
|
|
3526
|
-
""
|
|
3527
|
-
].join("\n");
|
|
3528
|
-
}
|
|
3529
|
-
function makeCuaRunId() {
|
|
3530
|
-
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
3531
|
-
return `cua-${stamp}-${randomBytes(4).toString("hex")}`;
|
|
3532
|
-
}
|
|
3533
|
-
function publicSafeAppUrlLabel(url) {
|
|
3534
|
-
return containsSensitive(url) ? `[target-url:${digestUrl(url)}]` : url;
|
|
3535
|
-
}
|
|
3536
|
-
function digestUrl(url) {
|
|
3537
|
-
return digestText(url, 16);
|
|
3538
|
-
}
|
|
3539
|
-
function readPositiveInt(value, fallback) {
|
|
3540
|
-
if (value === undefined)
|
|
3541
|
-
return fallback;
|
|
3542
|
-
const parsed = Number.parseInt(value, 10);
|
|
3543
|
-
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
3544
|
-
}
|
|
3545
|
-
//# sourceMappingURL=cua-actor-lab.js.map
|