humanish 0.106.1 → 0.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +38 -32
- package/CHANGELOG.md +28 -0
- package/CONTEXT.md +51 -41
- package/README.md +152 -174
- package/SECURITY.md +16 -26
- package/TELEMETRY.md +8 -6
- package/dist/actors/affordance.d.ts +4 -4
- package/dist/actors/affordance.js +12 -12
- package/dist/actors/agent-session.d.ts +2 -2
- package/dist/actors/agent-session.js +7 -7
- package/dist/actors/codex/app-server-actor-trace.d.ts +1 -1
- package/dist/actors/codex/app-server-actor-trace.js +1 -1
- package/dist/actors/codex/app-server-client.js +2 -1
- package/dist/actors/codex/app-server-trace.d.ts +0 -1
- package/dist/actors/codex/app-server-trace.js +1 -3
- package/dist/actors/codex/app-server-ui.js +1 -1
- package/dist/actors/codex/app-server.js +2 -1
- package/dist/actors/codex/codex-admission.d.ts +91 -0
- package/dist/actors/codex/codex-admission.js +150 -0
- package/dist/actors/codex/protocol-compat.d.ts +27 -0
- package/dist/actors/codex/protocol-compat.js +357 -0
- package/dist/actors/codex/protocol-contract.d.ts +61 -0
- package/dist/actors/codex/protocol-contract.js +471 -0
- package/dist/actors/codex/restricted-executable.d.ts +28 -4
- package/dist/actors/codex/restricted-executable.js +104 -29
- package/dist/actors/codex/restricted-launch.d.ts +9 -2
- package/dist/actors/codex/restricted-launch.js +43 -4
- package/dist/actors/codex/restricted-notifications.d.ts +101 -0
- package/dist/actors/codex/restricted-notifications.js +277 -0
- package/dist/actors/codex/restricted-participant.d.ts +30 -0
- package/dist/actors/codex/restricted-participant.js +66 -6
- package/dist/actors/codex/restricted-policy.d.ts +11 -3
- package/dist/actors/codex/restricted-policy.js +2 -2
- package/dist/actors/codex/restricted-session.d.ts +16 -3
- package/dist/actors/codex/restricted-session.js +60 -22
- package/dist/actors/codex/restricted-transport.d.ts +29 -0
- package/dist/actors/codex/restricted-transport.js +139 -27
- package/dist/actors/codex/restricted-turn.d.ts +19 -4
- package/dist/actors/codex/restricted-turn.js +43 -30
- package/dist/actors/computer-use/actor.d.ts +17 -17
- package/dist/actors/computer-use/actor.js +6 -6
- package/dist/actors/computer-use/admission-limit.d.ts +1 -1
- package/dist/actors/computer-use/admission-limit.js +1 -1
- package/dist/actors/computer-use/loop/actions.d.ts +3 -3
- package/dist/actors/computer-use/loop/actions.js +10 -10
- package/dist/actors/computer-use/loop/backstop.d.ts +3 -3
- package/dist/actors/computer-use/loop/backstop.js +15 -13
- package/dist/actors/computer-use/loop/ending.d.ts +7 -7
- package/dist/actors/computer-use/loop/ending.js +9 -9
- package/dist/actors/computer-use/loop/observation.d.ts +2 -2
- package/dist/actors/computer-use/loop/observation.js +9 -9
- package/dist/actors/computer-use/loop/provider-call.d.ts +1 -1
- package/dist/actors/computer-use/loop/provider-call.js +1 -1
- package/dist/actors/computer-use/loop/session.js +3 -3
- package/dist/actors/computer-use/loop/spend.d.ts +2 -2
- package/dist/actors/computer-use/loop/spend.js +4 -4
- package/dist/actors/computer-use/loop/trace.js +11 -10
- package/dist/actors/computer-use/loop/types.d.ts +37 -26
- package/dist/actors/computer-use/loop/usage.js +2 -2
- package/dist/actors/computer-use/loop.js +4 -4
- package/dist/actors/computer-use/openai-context.d.ts +37 -0
- package/dist/actors/computer-use/openai-context.js +214 -0
- package/dist/actors/computer-use/openai-provider.d.ts +12 -9
- package/dist/actors/computer-use/openai-provider.js +47 -36
- package/dist/actors/computer-use/openai-requests.d.ts +36 -10
- package/dist/actors/computer-use/openai-requests.js +82 -15
- package/dist/actors/computer-use/openai-wire.d.ts +19 -7
- package/dist/actors/computer-use/openai-wire.js +36 -34
- package/dist/actors/contract.d.ts +86 -49
- package/dist/actors/contract.js +29 -23
- package/dist/actors/local-agent/claude-session.d.ts +1 -1
- package/dist/actors/local-agent/claude-session.js +10 -9
- package/dist/actors/local-agent/cli.d.ts +8 -6
- package/dist/actors/local-agent/cli.js +24 -28
- package/dist/actors/local-agent/readiness.d.ts +2 -2
- package/dist/actors/local-agent/readiness.js +4 -4
- package/dist/actors/reasoning-effort.js +6 -6
- package/dist/actors/registry.d.ts +10 -10
- package/dist/actors/registry.js +18 -17
- package/dist/actors/scripted-browser/actor.d.ts +8 -8
- package/dist/actors/scripted-browser/actor.js +16 -16
- package/dist/actors/scripted-browser/browser-command.js +1 -1
- package/dist/actors/scripted-browser/journey.js +15 -4
- package/dist/actors/scripted-browser/steps.js +19 -1
- package/dist/actors/scripted-browser/types.d.ts +12 -2
- package/dist/actors/stop-conditions.d.ts +1 -1
- package/dist/actors/stop-conditions.js +1 -3
- package/dist/analysis/automatic-completion.d.ts +13 -8
- package/dist/analysis/automatic-completion.js +14 -8
- package/dist/analysis/automatic-config.d.ts +1 -1
- package/dist/analysis/automatic-config.js +2 -2
- package/dist/analysis/automatic.d.ts +10 -4
- package/dist/analysis/automatic.js +17 -13
- package/dist/analysis/codex-config.d.ts +6 -2
- package/dist/analysis/codex-config.js +22 -14
- package/dist/analysis/diagnostics.d.ts +23 -0
- package/dist/analysis/diagnostics.js +112 -0
- package/dist/analysis/evidence-sources.js +2 -2
- package/dist/analysis/evidence.js +6 -6
- package/dist/analysis/execute.d.ts +7 -1
- package/dist/analysis/execute.js +39 -12
- package/dist/analysis/job.d.ts +8 -5
- package/dist/analysis/job.js +15 -14
- package/dist/analysis/load.d.ts +1 -1
- package/dist/analysis/load.js +5 -5
- package/dist/analysis/provider.d.ts +9 -1
- package/dist/analysis/restricted-codex.d.ts +2 -0
- package/dist/analysis/restricted-codex.js +10 -1
- package/dist/analysis/service.d.ts +2 -0
- package/dist/analysis/service.js +35 -16
- package/dist/analysis/sharing.d.ts +2 -2
- package/dist/analysis/sharing.js +5 -5
- package/dist/analysis/store-executions.d.ts +5 -5
- package/dist/analysis/store-executions.js +12 -12
- package/dist/analysis/store.d.ts +4 -4
- package/dist/analysis/store.js +13 -13
- package/dist/cli/commands/analysis-signals.d.ts +2 -1
- package/dist/cli/commands/analysis-signals.js +4 -1
- package/dist/cli/commands/analyze.js +23 -18
- package/dist/cli/commands/codex.js +9 -9
- package/dist/cli/commands/comms.js +62 -34
- package/dist/cli/commands/feedback.js +20 -21
- package/dist/cli/commands/lab-format.d.ts +5 -4
- package/dist/cli/commands/lab-format.js +35 -23
- package/dist/cli/commands/lab-route-computer-use.d.ts +0 -2
- package/dist/cli/commands/lab-route-computer-use.js +32 -21
- package/dist/cli/commands/lab-route-preview.d.ts +1 -3
- package/dist/cli/commands/lab-route-preview.js +7 -7
- package/dist/cli/commands/lab-route-run.d.ts +6 -4
- package/dist/cli/commands/lab-route-run.js +6 -2
- package/dist/cli/commands/lab-route-scripted.d.ts +0 -2
- package/dist/cli/commands/lab-route-scripted.js +0 -1
- package/dist/cli/commands/lab-route-shared-world.d.ts +0 -2
- package/dist/cli/commands/lab-route-shared-world.js +3 -4
- package/dist/cli/commands/lab-route-terminal.d.ts +0 -2
- package/dist/cli/commands/lab-route-terminal.js +0 -1
- package/dist/cli/commands/lab-run.js +84 -54
- package/dist/cli/commands/lab-scorer.d.ts +2 -4
- package/dist/cli/commands/lab-scorer.js +1 -1
- package/dist/cli/commands/migrate.d.ts +4 -0
- package/dist/cli/commands/migrate.js +65 -0
- package/dist/cli/commands/observe.js +168 -107
- package/dist/cli/commands/run-command.d.ts +10 -0
- package/dist/cli/commands/run-command.js +85 -0
- package/dist/cli/commands/run-signals.d.ts +30 -0
- package/dist/cli/commands/run-signals.js +146 -0
- package/dist/cli/commands/runs.js +137 -136
- package/dist/cli/commands/setup.d.ts +2 -2
- package/dist/cli/commands/setup.js +26 -21
- package/dist/cli/commands/study.d.ts +3 -0
- package/dist/cli/commands/study.js +182 -0
- package/dist/cli/commands/tui.d.ts +4 -4
- package/dist/cli/commands/tui.js +21 -21
- package/dist/cli/commands/watch.js +64 -77
- package/dist/cli/deprecations.d.ts +18 -0
- package/dist/cli/deprecations.js +48 -0
- package/dist/cli/doctor.d.ts +18 -5
- package/dist/cli/doctor.js +111 -48
- package/dist/cli/first-run-path.d.ts +12 -10
- package/dist/cli/first-run-path.js +30 -28
- package/dist/cli/io.d.ts +46 -10
- package/dist/cli/io.js +109 -30
- package/dist/cli/observer-follow.d.ts +2 -2
- package/dist/cli/observer-follow.js +5 -6
- package/dist/cli/orientation.d.ts +6 -3
- package/dist/cli/orientation.js +43 -39
- package/dist/cli/product-sentence.d.ts +6 -0
- package/dist/cli/product-sentence.js +6 -0
- package/dist/cli/program.d.ts +6 -1
- package/dist/cli/program.js +112 -43
- package/dist/cli/telemetry.d.ts +17 -17
- package/dist/cli/telemetry.js +49 -41
- package/dist/cli.js +1 -1
- package/dist/comms/capture-surface.d.ts +14 -13
- package/dist/comms/capture-surface.js +23 -22
- package/dist/comms/catch-host.d.ts +8 -8
- package/dist/comms/catch-host.js +14 -14
- package/dist/comms/connections.js +4 -4
- package/dist/comms/email-catch.d.ts +3 -3
- package/dist/comms/email-catch.js +8 -8
- package/dist/comms/external-evidence.d.ts +27 -0
- package/dist/comms/external-evidence.js +93 -0
- package/dist/comms/extract.js +2 -2
- package/dist/comms/fake-inbox.d.ts +2 -2
- package/dist/comms/fake-inbox.js +2 -2
- package/dist/comms/lease-store.js +7 -7
- package/dist/comms/receiving-render.d.ts +1 -1
- package/dist/comms/receiving-render.js +2 -2
- package/dist/comms/sandbox-catch-script.d.ts +6 -6
- package/dist/comms/sandbox-catch-script.js +6 -6
- package/dist/comms/sandbox-catch.d.ts +36 -37
- package/dist/comms/sandbox-catch.js +34 -35
- package/dist/comms/setup.js +42 -8
- package/dist/comms/thread-evidence.d.ts +1 -1
- package/dist/comms/thread-evidence.js +6 -6
- package/dist/comms/types.d.ts +6 -6
- package/dist/comms/types.js +4 -4
- package/dist/evidence/browser-hygiene.d.ts +1 -1
- package/dist/evidence/browser-hygiene.js +1 -1
- package/dist/evidence/frame-signature.d.ts +1 -1
- package/dist/evidence/frame-signature.js +4 -4
- package/dist/evidence/redaction.d.ts +3 -3
- package/dist/evidence/redaction.js +5 -5
- package/dist/evidence/secret-scrub.d.ts +9 -0
- package/dist/evidence/secret-scrub.js +96 -0
- package/dist/feedback/draft.js +41 -85
- package/dist/feedback/export-bundle.js +5 -5
- package/dist/feedback/export.d.ts +5 -1
- package/dist/feedback/export.js +16 -17
- package/dist/feedback/feedback.d.ts +3 -3
- package/dist/feedback/feedback.js +21 -3
- package/dist/guest/chromium-text.js +2 -2
- package/dist/guest/desktop-native.d.ts +1 -1
- package/dist/guest/runtime.js +1 -1
- package/dist/index.d.ts +14 -15
- package/dist/index.js +10 -11
- package/dist/keys/key-resolution.d.ts +7 -5
- package/dist/keys/key-resolution.js +40 -38
- package/dist/lab/adapter-extension.d.ts +35 -13
- package/dist/lab/adapter-extension.js +50 -134
- package/dist/lab/adapter-scorer-loader.d.ts +32 -12
- package/dist/lab/adapter-scorer-loader.js +56 -32
- package/dist/lab/composition-rules.js +58 -58
- package/dist/lab/config.js +85 -43
- package/dist/lab/deprecated-fields.d.ts +10 -0
- package/dist/lab/deprecated-fields.js +24 -0
- package/dist/lab/device-presets.d.ts +10 -10
- package/dist/lab/device-presets.js +11 -11
- package/dist/lab/discover.d.ts +13 -9
- package/dist/lab/discover.js +118 -60
- package/dist/lab/doctor.d.ts +53 -11
- package/dist/lab/doctor.js +193 -47
- package/dist/lab/init-plan.js +1 -3
- package/dist/lab/init-templates.js +147 -147
- package/dist/lab/init.d.ts +1 -1
- package/dist/lab/init.js +41 -9
- package/dist/lab/keys.d.ts +2 -0
- package/dist/lab/keys.js +43 -9
- package/dist/lab/lab-deps.d.ts +80 -0
- package/dist/lab/lab-deps.js +4 -0
- package/dist/lab/parse/actors.js +17 -16
- package/dist/lab/parse/comms.js +8 -7
- package/dist/lab/parse/execution.js +15 -14
- package/dist/lab/parse/study-v3.d.ts +23 -0
- package/dist/lab/parse/study-v3.js +259 -0
- package/dist/lab/parse/subject-state.d.ts +7 -7
- package/dist/lab/parse/subject-state.js +18 -17
- package/dist/lab/parse/subject.js +30 -29
- package/dist/lab/parse/values.d.ts +1 -2
- package/dist/lab/parse/values.js +3 -6
- package/dist/lab/persona-resolve.d.ts +7 -7
- package/dist/lab/persona-resolve.js +8 -8
- package/dist/lab/persona.js +1 -3
- package/dist/lab/plan-base.d.ts +0 -2
- package/dist/lab/plan-base.js +6 -4
- package/dist/lab/plan-participants.d.ts +7 -7
- package/dist/lab/plan-participants.js +2 -2
- package/dist/lab/plan-types.d.ts +26 -8
- package/dist/lab/plan.d.ts +4 -18
- package/dist/lab/plan.js +24 -79
- package/dist/lab/preflight-probes.js +11 -11
- package/dist/lab/preflight-result.d.ts +2 -1
- package/dist/lab/preflight-result.js +5 -6
- package/dist/lab/preflight.d.ts +7 -8
- package/dist/lab/preflight.js +6 -6
- package/dist/lab/requirements.d.ts +20 -0
- package/dist/lab/requirements.js +54 -0
- package/dist/lab/route-inputs.d.ts +23 -12
- package/dist/lab/route-inputs.js +48 -51
- package/dist/lab/routing.d.ts +42 -41
- package/dist/lab/routing.js +66 -68
- package/dist/lab/run-lab-events.d.ts +3 -11
- package/dist/lab/run-lab-events.js +2 -16
- package/dist/lab/run-lab-homes.d.ts +76 -0
- package/dist/lab/run-lab-homes.js +7 -0
- package/dist/lab/run-lab-options.d.ts +12 -66
- package/dist/lab/run-lab-options.js +58 -285
- package/dist/lab/summary.d.ts +15 -9
- package/dist/lab/summary.js +24 -17
- package/dist/lab/tasks.d.ts +15 -15
- package/dist/lab/tasks.js +14 -14
- package/dist/lab/types.d.ts +201 -195
- package/dist/lab/types.js +2 -0
- package/dist/lab/validation.d.ts +21 -21
- package/dist/lab/validation.js +61 -59
- package/dist/lab/warnings.d.ts +10 -0
- package/dist/lab/warnings.js +70 -41
- package/dist/library-aliases.d.ts +56 -0
- package/dist/library-aliases.js +22 -0
- package/dist/observer/artifact.d.ts +2 -2
- package/dist/observer/artifact.js +9 -9
- package/dist/observer/data.d.ts +6 -6
- package/dist/observer/data.js +7 -6
- package/dist/observer/exposure.d.ts +1 -1
- package/dist/observer/exposure.js +11 -11
- package/dist/observer/http.js +1 -1
- package/dist/observer/library.d.ts +1 -1
- package/dist/observer/library.js +4 -2
- package/dist/observer/listen.js +5 -4
- package/dist/observer/render.d.ts +1 -1
- package/dist/observer/render.js +12 -7
- package/dist/observer/run-routes.d.ts +4 -4
- package/dist/observer/run-routes.js +6 -6
- package/dist/observer/serve.d.ts +17 -0
- package/dist/observer/serve.js +41 -6
- package/dist/observer/tunnel.js +1 -1
- package/dist/observer-app.html +4 -4
- package/dist/routes/computer-use/bundle-parts.d.ts +9 -9
- package/dist/routes/computer-use/bundle-parts.js +24 -17
- package/dist/routes/computer-use/bundle.d.ts +4 -11
- package/dist/routes/computer-use/bundle.js +12 -18
- package/dist/routes/computer-use/diagnostics.d.ts +1 -1
- package/dist/routes/computer-use/diagnostics.js +1 -1
- package/dist/routes/computer-use/e2b-desktop/comms.d.ts +11 -11
- package/dist/routes/computer-use/e2b-desktop/comms.js +24 -27
- package/dist/routes/computer-use/e2b-desktop/desktop.d.ts +2 -2
- package/dist/routes/computer-use/e2b-desktop/desktop.js +8 -11
- package/dist/routes/computer-use/e2b-desktop/fidelity.d.ts +16 -15
- package/dist/routes/computer-use/e2b-desktop/fidelity.js +20 -19
- package/dist/routes/computer-use/e2b-desktop/prepare.d.ts +3 -3
- package/dist/routes/computer-use/e2b-desktop/prepare.js +40 -43
- package/dist/routes/computer-use/e2b-desktop/start.d.ts +1 -1
- package/dist/routes/computer-use/e2b-desktop/start.js +32 -30
- package/dist/routes/computer-use/e2b-desktop/state.d.ts +5 -4
- package/dist/routes/computer-use/e2b-desktop/state.js +5 -3
- package/dist/routes/computer-use/e2b-desktop/teardown.d.ts +1 -1
- package/dist/routes/computer-use/e2b-desktop/teardown.js +33 -20
- package/dist/routes/computer-use/external-comms.d.ts +4 -4
- package/dist/routes/computer-use/external-comms.js +19 -43
- package/dist/routes/computer-use/fanout-bundle.d.ts +3 -3
- package/dist/routes/computer-use/fanout-bundle.js +38 -42
- package/dist/routes/computer-use/{fanout-lanes.js → fanout-records.js} +33 -28
- package/dist/routes/computer-use/in-process-desktop.d.ts +3 -10
- package/dist/routes/computer-use/in-process-desktop.js +3 -9
- package/dist/routes/computer-use/live-flush.d.ts +7 -5
- package/dist/routes/computer-use/live-flush.js +20 -28
- package/dist/routes/computer-use/{run-lanes.d.ts → live-phase.d.ts} +4 -2
- package/dist/routes/computer-use/{run-lanes.js → live-phase.js} +17 -16
- package/dist/routes/computer-use/local-tree-pack.d.ts +3 -3
- package/dist/routes/computer-use/local-tree-pack.js +4 -5
- package/dist/routes/computer-use/local-vm.d.ts +11 -8
- package/dist/routes/computer-use/local-vm.js +40 -67
- package/dist/routes/computer-use/participant-desktop.d.ts +8 -23
- package/dist/routes/computer-use/participant-desktop.js +5 -25
- package/dist/routes/computer-use/{lanes.d.ts → participant-execution.d.ts} +11 -11
- package/dist/routes/computer-use/{lanes.js → participant-execution.js} +39 -26
- package/dist/routes/computer-use/participant-facts.d.ts +7 -1
- package/dist/routes/computer-use/participant-facts.js +12 -1
- package/dist/routes/computer-use/participant-model.d.ts +25 -11
- package/dist/routes/computer-use/participant-model.js +76 -34
- package/dist/routes/computer-use/participant-prompt.d.ts +13 -13
- package/dist/routes/computer-use/participant-prompt.js +8 -8
- package/dist/routes/computer-use/participant-runs.d.ts +11 -11
- package/dist/routes/computer-use/participant-runs.js +18 -18
- package/dist/routes/computer-use/plan.d.ts +21 -29
- package/dist/routes/computer-use/plan.js +70 -78
- package/dist/routes/computer-use/preflight.d.ts +3 -5
- package/dist/routes/computer-use/preflight.js +40 -36
- package/dist/routes/computer-use/rerun-selection.js +11 -7
- package/dist/routes/computer-use/result.d.ts +1 -1
- package/dist/routes/computer-use/result.js +48 -38
- package/dist/routes/computer-use/route.d.ts +5 -5
- package/dist/routes/computer-use/route.js +36 -33
- package/dist/routes/computer-use/self-report.d.ts +8 -8
- package/dist/routes/computer-use/self-report.js +33 -33
- package/dist/routes/computer-use/setup.d.ts +9 -15
- package/dist/routes/computer-use/setup.js +106 -83
- package/dist/routes/computer-use/single-bundle.d.ts +13 -16
- package/dist/routes/computer-use/single-bundle.js +46 -53
- package/dist/routes/computer-use/subject-projection.d.ts +6 -8
- package/dist/routes/computer-use/subject-projection.js +24 -23
- package/dist/routes/computer-use/types.d.ts +195 -238
- package/dist/routes/computer-use/types.js +21 -13
- package/dist/routes/preview.d.ts +8 -2
- package/dist/routes/preview.js +8 -5
- package/dist/routes/scripted/bundle.d.ts +5 -8
- package/dist/routes/scripted/bundle.js +23 -27
- package/dist/routes/scripted/plan.d.ts +6 -8
- package/dist/routes/scripted/plan.js +15 -13
- package/dist/routes/scripted/result.d.ts +4 -1
- package/dist/routes/scripted/result.js +15 -11
- package/dist/routes/scripted/route.js +15 -17
- package/dist/routes/scripted/scenario.d.ts +1 -1
- package/dist/routes/scripted/scenario.js +3 -3
- package/dist/routes/scripted/setup.d.ts +3 -2
- package/dist/routes/scripted/setup.js +27 -26
- package/dist/routes/scripted/subject.d.ts +8 -3
- package/dist/routes/scripted/subject.js +10 -6
- package/dist/routes/scripted/surface-records.js +4 -4
- package/dist/routes/scripted/surface-sessions.d.ts +2 -2
- package/dist/routes/scripted/surface-sessions.js +4 -4
- package/dist/routes/scripted/types.d.ts +21 -40
- package/dist/routes/scripted/types.js +2 -3
- package/dist/routes/shared-world/{seat-records.js → bundle-records.js} +17 -16
- package/dist/routes/shared-world/bundle.d.ts +7 -7
- package/dist/routes/shared-world/bundle.js +60 -62
- package/dist/routes/shared-world/checkpoints.d.ts +5 -5
- package/dist/routes/shared-world/checkpoints.js +5 -5
- package/dist/routes/shared-world/comms.d.ts +8 -7
- package/dist/routes/shared-world/comms.js +38 -51
- package/dist/routes/shared-world/external-public.d.ts +6 -5
- package/dist/routes/shared-world/external-public.js +28 -25
- package/dist/routes/shared-world/handoff.d.ts +10 -9
- package/dist/routes/shared-world/handoff.js +89 -81
- package/dist/routes/shared-world/lobby-code.d.ts +7 -7
- package/dist/routes/shared-world/lobby-code.js +22 -22
- package/dist/routes/shared-world/{seats.d.ts → participant-specs.d.ts} +15 -12
- package/dist/routes/shared-world/{seats.js → participant-specs.js} +65 -63
- package/dist/routes/shared-world/plan.d.ts +2 -6
- package/dist/routes/shared-world/plan.js +8 -9
- package/dist/routes/shared-world/provenance.d.ts +4 -4
- package/dist/routes/shared-world/provenance.js +4 -4
- package/dist/routes/shared-world/provisioned.d.ts +3 -2
- package/dist/routes/shared-world/provisioned.js +55 -48
- package/dist/routes/shared-world/result.js +47 -36
- package/dist/routes/shared-world/route.js +54 -57
- package/dist/routes/shared-world/setup.d.ts +6 -5
- package/dist/routes/shared-world/setup.js +16 -18
- package/dist/routes/shared-world/types.d.ts +70 -141
- package/dist/routes/shared-world/types.js +5 -5
- package/dist/routes/terminal/adapter.d.ts +10 -10
- package/dist/routes/terminal/adapter.js +32 -129
- package/dist/routes/terminal/bundle.d.ts +10 -15
- package/dist/routes/terminal/bundle.js +43 -52
- package/dist/routes/terminal/credentials.d.ts +4 -4
- package/dist/routes/terminal/credentials.js +20 -20
- package/dist/routes/terminal/dry-run.d.ts +1 -1
- package/dist/routes/terminal/dry-run.js +13 -15
- package/dist/routes/terminal/encoding.d.ts +1 -1
- package/dist/routes/terminal/encoding.js +4 -4
- package/dist/routes/terminal/ledger.d.ts +8 -8
- package/dist/routes/terminal/ledger.js +22 -22
- package/dist/routes/terminal/live-finish.d.ts +1 -2
- package/dist/routes/terminal/live-finish.js +24 -23
- package/dist/routes/terminal/live-sandbox.d.ts +3 -2
- package/dist/routes/terminal/live-sandbox.js +31 -32
- package/dist/routes/terminal/participant-text.d.ts +23 -0
- package/dist/routes/terminal/participant-text.js +165 -0
- package/dist/routes/terminal/persona.js +1 -1
- package/dist/routes/terminal/plan.d.ts +2 -6
- package/dist/routes/terminal/plan.js +23 -19
- package/dist/routes/terminal/recorder.d.ts +6 -1
- package/dist/routes/terminal/recorder.js +12 -4
- package/dist/routes/terminal/result.js +12 -12
- package/dist/routes/terminal/route.js +30 -31
- package/dist/routes/terminal/runtime.d.ts +3 -2
- package/dist/routes/terminal/runtime.js +3 -3
- package/dist/routes/terminal/sandbox.d.ts +4 -4
- package/dist/routes/terminal/sandbox.js +10 -10
- package/dist/routes/terminal/session.js +22 -19
- package/dist/routes/terminal/token-usage.d.ts +1 -1
- package/dist/routes/terminal/token-usage.js +10 -11
- package/dist/routes/terminal/trace.d.ts +7 -10
- package/dist/routes/terminal/trace.js +11 -66
- package/dist/routes/terminal/types.d.ts +104 -114
- package/dist/routes/terminal/types.js +12 -7
- package/dist/run/active-runs.d.ts +13 -0
- package/dist/run/active-runs.js +12 -0
- package/dist/run/bundle-shape.d.ts +2 -1
- package/dist/run/bundle-shape.js +7 -7
- package/dist/run/bundle.d.ts +99 -92
- package/dist/run/bundle.js +10 -8
- package/dist/run/cost-summary.js +6 -5
- package/dist/run/costs.d.ts +6 -6
- package/dist/run/costs.js +17 -11
- package/dist/run/detail.d.ts +9 -9
- package/dist/run/detail.js +10 -10
- package/dist/run/dry-run-inputs.js +1 -1
- package/dist/run/dry-run.js +23 -30
- package/dist/run/{study-files.d.ts → evidence-files.d.ts} +4 -4
- package/dist/run/{study-files.js → evidence-files.js} +5 -5
- package/dist/run/feedback-shape.js +1 -1
- package/dist/run/judge.d.ts +34 -16
- package/dist/run/judge.js +41 -14
- package/dist/run/locate.d.ts +2 -0
- package/dist/run/locate.js +3 -1
- package/dist/run/outcomes.d.ts +3 -3
- package/dist/run/outcomes.js +3 -3
- package/dist/run/participant-records.d.ts +2 -2
- package/dist/run/participant-records.js +2 -2
- package/dist/run/paths.d.ts +1 -1
- package/dist/run/paths.js +2 -2
- package/dist/run/pricing.d.ts +30 -15
- package/dist/run/pricing.js +49 -30
- package/dist/run/projection.d.ts +27 -27
- package/dist/run/projection.js +23 -23
- package/dist/run/reclaim.d.ts +6 -0
- package/dist/run/reclaim.js +15 -5
- package/dist/run/results.d.ts +5 -5
- package/dist/run/run-index.d.ts +9 -7
- package/dist/run/run-index.js +24 -25
- package/dist/run/run-not-found.d.ts +5 -0
- package/dist/run/run-not-found.js +15 -0
- package/dist/run/run.d.ts +7 -4
- package/dist/run/run.js +10 -6
- package/dist/run/sandbox-receipts.d.ts +5 -5
- package/dist/run/sandbox-receipts.js +6 -6
- package/dist/run/shared-world-evidence.d.ts +67 -66
- package/dist/run/shared-world-evidence.js +4 -4
- package/dist/run/shared-world-shape.d.ts +3 -3
- package/dist/run/shared-world-shape.js +16 -16
- package/dist/run/stats.d.ts +17 -11
- package/dist/run/stats.js +124 -66
- package/dist/run/status.d.ts +21 -32
- package/dist/run/status.js +34 -33
- package/dist/run/stored-runs.js +2 -1
- package/dist/run/stream-shape.js +1 -1
- package/dist/run/streams.d.ts +29 -22
- package/dist/run/study-provenance.d.ts +47 -0
- package/dist/run/study-provenance.js +63 -0
- package/dist/run/study-result.d.ts +16 -0
- package/dist/run/study-result.js +9 -0
- package/dist/run/terminal-contract.d.ts +5 -5
- package/dist/run/terminal-contract.js +4 -4
- package/dist/run/text.d.ts +2 -0
- package/dist/run/text.js +4 -0
- package/dist/run/transient-comms-secrets.d.ts +5 -0
- package/dist/run/transient-comms-secrets.js +12 -0
- package/dist/run-lab.d.ts +40 -38
- package/dist/run-lab.js +42 -40
- package/dist/study/convert.d.ts +33 -0
- package/dist/study/convert.js +432 -0
- package/dist/study/field-paths.d.ts +12 -0
- package/dist/study/field-paths.js +126 -0
- package/dist/study/files.d.ts +26 -0
- package/dist/study/files.js +43 -0
- package/dist/study/migrate.d.ts +48 -0
- package/dist/study/migrate.js +408 -0
- package/dist/study/owned-files.d.ts +38 -0
- package/dist/study/owned-files.js +182 -0
- package/dist/subject/clone.d.ts +1 -1
- package/dist/subject/desktop-cli.d.ts +2 -2
- package/dist/subject/desktop-cli.js +3 -3
- package/dist/subject/local-tree-archive.d.ts +1 -1
- package/dist/subject/local-tree-archive.js +5 -5
- package/dist/subject/local-tree.d.ts +3 -3
- package/dist/subject/local-tree.js +1 -1
- package/dist/subject/node-bootstrap.d.ts +1 -1
- package/dist/subject/node-bootstrap.js +4 -4
- package/dist/subject/runtime.js +10 -11
- package/dist/subject/serve.d.ts +1 -1
- package/dist/subject/serve.js +4 -4
- package/dist/subject/state.d.ts +1 -1
- package/dist/subject/steps.d.ts +8 -8
- package/dist/subject/steps.js +7 -7
- package/dist/substrates/command-failure.d.ts +3 -3
- package/dist/substrates/command-failure.js +3 -3
- package/dist/substrates/detached.d.ts +6 -6
- package/dist/substrates/detached.js +7 -7
- package/dist/substrates/e2b/cdp-probe.d.ts +11 -11
- package/dist/substrates/e2b/cdp-probe.js +15 -18
- package/dist/substrates/e2b/desktop-browser.d.ts +5 -5
- package/dist/substrates/e2b/desktop-browser.js +12 -12
- package/dist/substrates/e2b/desktop-cdp.d.ts +15 -15
- package/dist/substrates/e2b/desktop-cdp.js +11 -11
- package/dist/substrates/e2b/desktop-executor.d.ts +1 -1
- package/dist/substrates/e2b/desktop-executor.js +1 -1
- package/dist/substrates/e2b/desktop-geometry.d.ts +6 -6
- package/dist/substrates/e2b/desktop-geometry.js +9 -9
- package/dist/substrates/e2b/desktop-media.d.ts +1 -1
- package/dist/substrates/e2b/desktop-media.js +3 -3
- package/dist/substrates/e2b/desktop-screenshot-cleanup.d.ts +3 -3
- package/dist/substrates/e2b/desktop-screenshot-cleanup.js +3 -3
- package/dist/substrates/e2b/sandbox.d.ts +1 -1
- package/dist/substrates/e2b/sandbox.js +2 -2
- package/dist/substrates/e2b/sdk.d.ts +11 -11
- package/dist/substrates/e2b/sdk.js +8 -8
- package/dist/substrates/local/runtime-host.d.ts +2 -0
- package/dist/substrates/local/runtime-host.js +6 -0
- package/dist/tui/actions.d.ts +8 -6
- package/dist/tui/actions.js +31 -22
- package/dist/tui/contract.d.ts +16 -16
- package/dist/tui/contract.js +10 -10
- package/dist/tui/launch.d.ts +4 -4
- package/dist/tui/launch.js +18 -19
- package/dist/tui/project.js +5 -5
- package/dist/tui-app.js +83 -83
- package/dist/verify/actor.d.ts +14 -14
- package/dist/verify/actor.js +45 -45
- package/dist/verify/artifacts.d.ts +3 -3
- package/dist/verify/artifacts.js +6 -6
- package/dist/verify/context-growth.d.ts +9 -0
- package/dist/verify/context-growth.js +62 -0
- package/dist/verify/costs.d.ts +7 -7
- package/dist/verify/costs.js +9 -9
- package/dist/verify/liveness.d.ts +1 -1
- package/dist/verify/liveness.js +4 -1
- package/dist/verify/rerun.d.ts +1 -1
- package/dist/verify/rerun.js +1 -1
- package/dist/verify/shared-world-concurrent.d.ts +2 -2
- package/dist/verify/shared-world-concurrent.js +79 -76
- package/dist/verify/shared-world.d.ts +2 -2
- package/dist/verify/shared-world.js +18 -18
- package/dist/verify/subject.d.ts +4 -4
- package/dist/verify/subject.js +18 -18
- package/dist/verify/verify.js +98 -49
- package/docs/README.md +26 -33
- package/docs/architecture/actor-contract.md +105 -98
- package/docs/architecture/desktop-sessions.md +14 -14
- package/docs/architecture/external-public-shared-world.md +66 -59
- package/docs/architecture/github-feedback-loop.md +1 -4
- package/docs/architecture/guest-desktop.md +2 -2
- package/docs/architecture/observer.md +37 -37
- package/docs/architecture/restricted-codex-analysis.md +70 -30
- package/docs/architecture/serve.md +34 -37
- package/docs/architecture/state-driven-executor.md +55 -59
- package/docs/architecture/task-protocol-support.md +7 -9
- package/docs/architecture/{terminal-product-lane.md → terminal-product-route.md} +83 -115
- package/docs/contracts/README.md +24 -0
- package/docs/contracts/adapter-admission.md +3 -3
- package/docs/contracts/adapter-fixtures.md +2 -5
- package/docs/contracts/core.md +1 -3
- package/docs/contracts/errors.md +62 -0
- package/docs/contracts/feedback.md +8 -9
- package/docs/contracts/policy.md +7 -12
- package/docs/contracts/run-bundle.md +54 -51
- package/docs/contracts/schemas.md +505 -390
- package/docs/contracts/study-analysis.md +26 -2
- package/docs/contracts/study-costs.md +11 -7
- package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +1 -1
- package/docs/decisions/0002-observer-is-one-self-contained-file.md +2 -2
- package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +3 -3
- package/docs/decisions/0004-participant-desktops-stay-in-the-route.md +2 -1
- package/docs/principles/actor-fidelity.md +4 -4
- package/docs/principles/engineering.md +3 -3
- package/docs/principles/invariants-and-defaults.md +38 -38
- package/docs/principles/self-driving-harness.md +1 -5
- package/docs/principles/three-roles.md +1 -1
- package/docs/product/automatic-analysis.md +8 -7
- package/examples/participant/README.md +5 -5
- package/examples/participant/run.mjs +11 -10
- package/examples/scorer/README.md +3 -3
- package/examples/scorer/run.mjs +11 -12
- package/package.json +16 -17
- package/skills/humanish/SKILL.md +63 -60
- package/skills/humanish/agents/openai.yaml +1 -1
- package/AGENTS.md +0 -99
- package/CONTRIBUTING.md +0 -148
- package/dist/actors/codex/qualified-versions.d.ts +0 -45
- package/dist/actors/codex/qualified-versions.js +0 -87
- package/dist/cli/commands/lab.d.ts +0 -3
- package/dist/cli/commands/lab.js +0 -193
- package/dist/deprecated.d.ts +0 -90
- package/dist/deprecated.js +0 -106
- package/dist/lab/bag-overrides.d.ts +0 -18
- package/dist/lab/bag-overrides.js +0 -121
- package/dist/observer/static.d.ts +0 -39
- package/dist/observer/static.js +0 -369
- package/dist/routes/computer-use/legacy-lane-spec.d.ts +0 -62
- package/dist/routes/computer-use/legacy-lane-spec.js +0 -39
- package/docs/architecture/comms-inbox.md +0 -122
- package/docs/architecture/desktop-recording.md +0 -80
- package/docs/architecture/local-browser-runtime.md +0 -178
- package/docs/architecture/observer-review.md +0 -144
- package/docs/architecture/participant-media.md +0 -90
- package/docs/architecture/project-layout.md +0 -62
- package/docs/architecture/real-email-receiving.md +0 -136
- package/docs/assets/humanish-drawdb-hero.png +0 -0
- package/docs/assets/humanish-observer-hero.png +0 -0
- package/docs/goals/current.md +0 -207
- package/docs/product/lobby-trivia-3player-external-public.md +0 -77
- package/docs/product/open-source-install-experience.md +0 -240
- package/docs/ramp/README.md +0 -294
- package/docs/release/open-source-readiness.md +0 -222
- package/docs/release/public-readiness-standard.md +0 -210
- package/docs/roadmap/world-class-open-source-v0.md +0 -311
- /package/dist/routes/computer-use/{fanout-lanes.d.ts → fanout-records.d.ts} +0 -0
- /package/dist/routes/shared-world/{seat-records.d.ts → bundle-records.d.ts} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReceivingSurfaceFile } from "./receiving-types.js";
|
|
2
2
|
import type { CommsMessage } from "./types.js";
|
|
3
3
|
/** [fromOrigin, toOrigin] pairs: rewrite a link/href whose origin is the app's in-sandbox origin into a
|
|
4
|
-
*
|
|
4
|
+
* participant-reachable origin, so the persona's browser can actually follow the verify link when it clicks. */
|
|
5
5
|
export type OriginMap = Array<[string, string]>;
|
|
6
6
|
export interface InboxRenderOptions {
|
|
7
7
|
originMap?: OriginMap;
|
|
@@ -13,17 +13,18 @@ export interface InboxRenderOptions {
|
|
|
13
13
|
export declare function inboxRecipientScope(address: string): string;
|
|
14
14
|
export declare function recipientInboxUrl(inboxUrl: string, address: string): string;
|
|
15
15
|
/** Rewrite a URL's origin per the map (origins are exact strings the harness minted). Leaves
|
|
16
|
-
* non-matching URLs
|
|
16
|
+
* non-matching URLs (including sibling origins sharing a prefix) untouched. */
|
|
17
17
|
export declare function rewriteOrigin(url: string, originMap?: OriginMap): string;
|
|
18
18
|
/**
|
|
19
|
-
* Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes
|
|
19
|
+
* Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes its own
|
|
20
20
|
* origin into the verify links it emails (usually its loopback serve origin); the persona reaches the
|
|
21
|
-
* app at a possibly-different origin
|
|
22
|
-
* harness-minted getHost URL on the shared-world route (where the rewrite is
|
|
23
|
-
* origin plus its loopback aliases at the serve port (127.0.0.1 / localhost /
|
|
24
|
-
* stamps `localhost` still rewrites, and
|
|
25
|
-
* an app was configured with
|
|
26
|
-
* only (no trailing slash, no path), so replaceOriginBoundary matches
|
|
21
|
+
* app at a possibly-different origin: the same loopback on the computer-use route (identity, a
|
|
22
|
+
* no-op), the harness-minted getHost URL on the shared-world route (where the rewrite is required).
|
|
23
|
+
* Emits the serve origin plus its loopback aliases at the serve port (127.0.0.1 / localhost /
|
|
24
|
+
* 0.0.0.0) so an app that stamps `localhost` still rewrites, and (because the harness cannot infer
|
|
25
|
+
* an absolute public base URL an app was configured with) an operator-declared `linkOrigin` escape
|
|
26
|
+
* hatch, matched first. Origins only (no trailing slash, no path), so replaceOriginBoundary matches
|
|
27
|
+
* on a URL boundary.
|
|
27
28
|
*/
|
|
28
29
|
export declare function buildOriginMap(args: {
|
|
29
30
|
internalServeUrl?: string | undefined;
|
|
@@ -32,11 +33,11 @@ export declare function buildOriginMap(args: {
|
|
|
32
33
|
}): OriginMap;
|
|
33
34
|
/** Best-guess primary call-to-action: the first verify/confirm-looking link, else the first link. */
|
|
34
35
|
export declare function pickVerifyUrl(links: string[]): string | undefined;
|
|
35
|
-
/** The inbox
|
|
36
|
+
/** The inbox list page (semantic table; each row links to its message). */
|
|
36
37
|
export declare function renderInboxList(messages: CommsMessage[], options?: InboxRenderOptions): string;
|
|
37
|
-
/** One message,
|
|
38
|
+
/** One message, default view: the app's real captured email in a minimal high-contrast shell. */
|
|
38
39
|
export declare function renderInboxMessage(message: CommsMessage, options?: InboxRenderOptions): string;
|
|
39
|
-
/** One message,
|
|
40
|
+
/** One message, synthesized view: a guaranteed-legible reading pane (big verify button + big OTP). The
|
|
40
41
|
* reliability fallback when the app's real email is a vision minefield; opt-in, never the default. */
|
|
41
42
|
export declare function renderInboxMessageSynth(message: CommsMessage, options?: InboxRenderOptions): string;
|
|
42
43
|
/** JSON projection of one message (the programmatic/CLI actor surface + the reliability backstop). Raw
|
|
@@ -46,7 +47,7 @@ export declare function inboxMessageJson(message: CommsMessage, options?: InboxR
|
|
|
46
47
|
* Build every served file for the inbox surface from the normalized messages. The host writes each to
|
|
47
48
|
* `<servedDir>/<path>`; the in-sandbox catch serves `<servedDir>/<pathname>`, falling back to
|
|
48
49
|
* `<pathname>/index` (so a URL like `/inbox` maps to the `inbox/index` file while `/inbox/{id}` stays a
|
|
49
|
-
* directory)
|
|
50
|
+
* directory), standard web-server index semantics that avoid a file-vs-directory path collision. The
|
|
50
51
|
* URLs the persona actually sees stay clean (`/inbox`, `/inbox/{id}`, `/inbox/{id}/synth`). Content type
|
|
51
52
|
* is inferred from the `/api/` prefix. Message ids are `comms-NNNN` (never `index`/`latest`), so leaf
|
|
52
53
|
* filenames never collide with an id.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The inbox surface for captured mail
|
|
1
|
+
// The inbox surface for captured mail: the pages a computer-use participant opens to read a
|
|
2
2
|
// captured verification email and follow its link. The pages are minimal semantic HTML (native
|
|
3
3
|
// links and tables, black on white, large targets), because vision participants read a simple
|
|
4
4
|
// inbox more reliably than a webmail client. The default view is the app's real captured email,
|
|
@@ -27,7 +27,7 @@ function inboxPath(options) {
|
|
|
27
27
|
: `/inbox/for/${inboxRecipientScope(options.recipient)}`;
|
|
28
28
|
}
|
|
29
29
|
const VERIFY_HINT = /verify|confirm|activate|validate|magic|token|account|sign[\s-]?up/i;
|
|
30
|
-
/** Replace `from` with `to` everywhere it ends on an
|
|
30
|
+
/** Replace `from` with `to` everywhere it ends on an origin boundary: end-of-string or one of the URL
|
|
31
31
|
* delimiters `/ ? # " ' <space> > \`. A bare prefix replace would mangle a sibling origin that shares
|
|
32
32
|
* `from` as a numeric prefix (`:30000` vs `:3000`) or a suffix-domain (`:3000.evil`); the boundary
|
|
33
33
|
* check prevents that. Works both for a single URL and inside a blob of email HTML. */
|
|
@@ -47,7 +47,7 @@ function replaceOriginBoundary(text, from, to) {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
/** Rewrite a URL's origin per the map (origins are exact strings the harness minted). Leaves
|
|
50
|
-
* non-matching URLs
|
|
50
|
+
* non-matching URLs (including sibling origins sharing a prefix) untouched. */
|
|
51
51
|
export function rewriteOrigin(url, originMap = []) {
|
|
52
52
|
let out = url;
|
|
53
53
|
for (const [from, to] of originMap)
|
|
@@ -75,14 +75,15 @@ function safePort(url) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes
|
|
78
|
+
* Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes its own
|
|
79
79
|
* origin into the verify links it emails (usually its loopback serve origin); the persona reaches the
|
|
80
|
-
* app at a possibly-different origin
|
|
81
|
-
* harness-minted getHost URL on the shared-world route (where the rewrite is
|
|
82
|
-
* origin plus its loopback aliases at the serve port (127.0.0.1 / localhost /
|
|
83
|
-
* stamps `localhost` still rewrites, and
|
|
84
|
-
* an app was configured with
|
|
85
|
-
* only (no trailing slash, no path), so replaceOriginBoundary matches
|
|
80
|
+
* app at a possibly-different origin: the same loopback on the computer-use route (identity, a
|
|
81
|
+
* no-op), the harness-minted getHost URL on the shared-world route (where the rewrite is required).
|
|
82
|
+
* Emits the serve origin plus its loopback aliases at the serve port (127.0.0.1 / localhost /
|
|
83
|
+
* 0.0.0.0) so an app that stamps `localhost` still rewrites, and (because the harness cannot infer
|
|
84
|
+
* an absolute public base URL an app was configured with) an operator-declared `linkOrigin` escape
|
|
85
|
+
* hatch, matched first. Origins only (no trailing slash, no path), so replaceOriginBoundary matches
|
|
86
|
+
* on a URL boundary.
|
|
86
87
|
*/
|
|
87
88
|
export function buildOriginMap(args) {
|
|
88
89
|
const to = safeOrigin(args.reachableBaseUrl);
|
|
@@ -121,7 +122,7 @@ function esc(value) {
|
|
|
121
122
|
.replace(/>/g, ">")
|
|
122
123
|
.replace(/"/g, """);
|
|
123
124
|
}
|
|
124
|
-
/** Defense-in-depth stripping before rendering the app's real email HTML. The
|
|
125
|
+
/** Defense-in-depth stripping before rendering the app's real email HTML. The main protection is
|
|
125
126
|
* the page CSP (`script-src 'none'`, set both as a page() meta and as a catch response header), which is
|
|
126
127
|
* browser-enforced and neuters inline handlers, `javascript:` URLs, and injected <script> regardless of
|
|
127
128
|
* what this misses. This strip additionally removes elements CSP does not cover (a redirecting
|
|
@@ -133,9 +134,9 @@ function neutralizeEmailHtml(html, originMap) {
|
|
|
133
134
|
.replace(/<(script|style|iframe|object|embed|svg|math|template)\b[\s\S]*?<\/\1\s*>/gi, "")
|
|
134
135
|
// stray or void dangerous tags, incl. unclosed openers and redirect/reroot tags
|
|
135
136
|
.replace(/<\/?(script|style|iframe|object|embed|svg|math|base|meta|link|form)\b[^>]*>/gi, "")
|
|
136
|
-
// inline event handlers, whether preceded by whitespace
|
|
137
|
+
// inline event handlers, whether preceded by whitespace or a "/" attribute separator
|
|
137
138
|
.replace(/[\s/]on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, " ")
|
|
138
|
-
// javascript:/vbscript: in nav/resource attrs
|
|
139
|
+
// javascript:/vbscript: in nav/resource attrs, quoted or unquoted (data: is left for inline images)
|
|
139
140
|
.replace(/(href|src|xlink:href|formaction|action)\s*=\s*(?:"(?:javascript|vbscript):[^"]*"|'(?:javascript|vbscript):[^']*'|(?:javascript|vbscript):[^\s>]*)/gi, '$1="#"');
|
|
140
141
|
// Rewrite the app's origin inside the (now-neutralized) HTML so its own links resolve to a reachable host.
|
|
141
142
|
for (const [from, to] of originMap)
|
|
@@ -213,7 +214,7 @@ function renderEmailImages(html, message, originMap) {
|
|
|
213
214
|
}
|
|
214
215
|
}
|
|
215
216
|
if (!src)
|
|
216
|
-
return `<span class="email-image-unavailable" role="img" aria-label="${esc(alt || "Email image")}">${esc(alt ? `${alt}
|
|
217
|
+
return `<span class="email-image-unavailable" role="img" aria-label="${esc(alt || "Email image")}">${esc(alt ? `${alt}: ${unavailable}` : unavailable)}</span>`;
|
|
217
218
|
const sizing = ["width", "height"]
|
|
218
219
|
.map((name) => {
|
|
219
220
|
const value = attributes.get(name);
|
|
@@ -241,11 +242,11 @@ const PAGE_CSS = "body{font:16px/1.55 system-ui,-apple-system,Segoe UI,Roboto,sa
|
|
|
241
242
|
"@media(max-width:560px){.inbox-list thead{display:none}.inbox-list,.inbox-list tbody,.inbox-list tr,.inbox-list td{display:block}" +
|
|
242
243
|
".inbox-list tr{padding:12px 0;border-bottom:1px solid #ddd}.inbox-list td{padding:3px 0;border:0}" +
|
|
243
244
|
".inbox-list td:first-child a{display:block;min-height:32px;font-weight:600}.inbox-list td[data-label]::before{content:attr(data-label) ': ';font-size:13px;color:#666}}";
|
|
244
|
-
/** The surface CSP
|
|
245
|
+
/** The surface CSP: the browser-enforced main protection against the app-authored email running
|
|
245
246
|
* script or hijacking navigation on the surface page. `script-src 'none'` blocks inline handlers,
|
|
246
247
|
* `javascript:` URLs, and any injected <script>; `object-src`/`frame-src 'none'` block plugins/frames;
|
|
247
248
|
* `base-uri 'none'` blocks <base> reroot. Images/styles stay permissive so the real email still renders.
|
|
248
|
-
* Set
|
|
249
|
+
* Set both as a page() meta (covers direct render use) and as a catch response header (covers serving). */
|
|
249
250
|
const INBOX_SURFACE_CSP = "default-src 'self'; script-src 'none'; object-src 'none'; base-uri 'none'; frame-src 'none'; img-src * data:; style-src 'unsafe-inline'; font-src * data:";
|
|
250
251
|
function page(title, inner) {
|
|
251
252
|
return ('<!doctype html><html lang="en"><head><meta charset="utf-8">' +
|
|
@@ -273,7 +274,7 @@ function fmtTime(ms) {
|
|
|
273
274
|
function toValues(message) {
|
|
274
275
|
return message.to.map((address) => address.value);
|
|
275
276
|
}
|
|
276
|
-
/** The inbox
|
|
277
|
+
/** The inbox list page (semantic table; each row links to its message). */
|
|
277
278
|
export function renderInboxList(messages, options = {}) {
|
|
278
279
|
const base = inboxPath(options);
|
|
279
280
|
const rows = messages
|
|
@@ -295,8 +296,8 @@ export function renderInboxList(messages, options = {}) {
|
|
|
295
296
|
"</td></tr>")
|
|
296
297
|
.join("");
|
|
297
298
|
const body = (options.recipient
|
|
298
|
-
? '<div class="bar">Inbox for ' + esc(options.recipient) + "
|
|
299
|
-
: '<div class="bar">Shared operator inbox
|
|
299
|
+
? '<div class="bar">Inbox for ' + esc(options.recipient) + ": "
|
|
300
|
+
: '<div class="bar">Shared operator inbox (all captured recipients): ') +
|
|
300
301
|
messages.length +
|
|
301
302
|
" message" +
|
|
302
303
|
(messages.length === 1 ? "" : "s") +
|
|
@@ -307,7 +308,7 @@ export function renderInboxList(messages, options = {}) {
|
|
|
307
308
|
"</tbody></table></main>";
|
|
308
309
|
return page("Inbox", body);
|
|
309
310
|
}
|
|
310
|
-
/** One message,
|
|
311
|
+
/** One message, default view: the app's real captured email in a minimal high-contrast shell. */
|
|
311
312
|
export function renderInboxMessage(message, options = {}) {
|
|
312
313
|
const originMap = options.originMap ?? [];
|
|
313
314
|
const base = inboxPath(options);
|
|
@@ -336,7 +337,7 @@ export function renderInboxMessage(message, options = {}) {
|
|
|
336
337
|
"</main>";
|
|
337
338
|
return page(message.subject ?? "Message", body);
|
|
338
339
|
}
|
|
339
|
-
/** One message,
|
|
340
|
+
/** One message, synthesized view: a guaranteed-legible reading pane (big verify button + big OTP). The
|
|
340
341
|
* reliability fallback when the app's real email is a vision minefield; opt-in, never the default. */
|
|
341
342
|
export function renderInboxMessageSynth(message, options = {}) {
|
|
342
343
|
const originMap = options.originMap ?? [];
|
|
@@ -418,7 +419,7 @@ function inboxListJson(messages, options = {}) {
|
|
|
418
419
|
* Build every served file for the inbox surface from the normalized messages. The host writes each to
|
|
419
420
|
* `<servedDir>/<path>`; the in-sandbox catch serves `<servedDir>/<pathname>`, falling back to
|
|
420
421
|
* `<pathname>/index` (so a URL like `/inbox` maps to the `inbox/index` file while `/inbox/{id}` stays a
|
|
421
|
-
* directory)
|
|
422
|
+
* directory), standard web-server index semantics that avoid a file-vs-directory path collision. The
|
|
422
423
|
* URLs the persona actually sees stay clean (`/inbox`, `/inbox/{id}`, `/inbox/{id}/synth`). Content type
|
|
423
424
|
* is inferred from the `/api/` prefix. Message ids are `comms-NNNN` (never `index`/`latest`), so leaf
|
|
424
425
|
* filenames never collide with an id.
|
|
@@ -9,14 +9,14 @@ export interface CommsCatchHostOptions {
|
|
|
9
9
|
dir: string;
|
|
10
10
|
/** Bearer token required on GET /deliveries. Strongly recommended when the host is reachable. */
|
|
11
11
|
token?: string;
|
|
12
|
-
/**
|
|
13
|
-
* (or
|
|
12
|
+
/** Second port for the read-only inbox listener bound to 0.0.0.0, so a persona on another machine
|
|
13
|
+
* (or on a participant's desktop) can reach the inbox. Omit to stay loopback-only. */
|
|
14
14
|
inboxPort?: number;
|
|
15
15
|
/** Optional loopback SMTP listener sharing the HTTP capture and inbox pipeline. */
|
|
16
16
|
smtpPort?: number;
|
|
17
|
-
/** Restrict the rendered inbox to these addresses. Omit to render whatever the app actually mailed
|
|
18
|
-
*
|
|
19
|
-
* name one should not get a healthy catch that renders an empty inbox forever
|
|
17
|
+
/** Restrict the rendered inbox to these addresses. Omit to render whatever the app actually mailed;
|
|
18
|
+
* a standalone catch has no lab roster to read recipients from, and an operator who forgets to
|
|
19
|
+
* name one should not get a healthy catch that renders an empty inbox forever. */
|
|
20
20
|
recipients?: string[];
|
|
21
21
|
/** Inbox re-render cadence in ms (test seam). */
|
|
22
22
|
renderIntervalMs?: number;
|
|
@@ -24,10 +24,10 @@ export interface CommsCatchHostOptions {
|
|
|
24
24
|
/**
|
|
25
25
|
* Render the persona-facing inbox surface from the catch's own deliveries file into `surfaceDir`.
|
|
26
26
|
*
|
|
27
|
-
* This is the half that was missing on an adopter-hosted plane
|
|
28
|
-
* /api/inbox as
|
|
27
|
+
* This is the half that was missing on an adopter-hosted plane. The catch serves /inbox and
|
|
28
|
+
* /api/inbox as static files; in-sandbox, humanish-as-host renders those files on a cadence. On a
|
|
29
29
|
* plane humanish does not provision there is no such host, so nothing wrote them and every persona
|
|
30
|
-
* that reached /inbox got `message not found` against a catch whose /health was green
|
|
30
|
+
* that reached /inbox got `message not found` against a catch whose /health was green, so the funnel
|
|
31
31
|
* dead-ended at a technically-healthy service.
|
|
32
32
|
*
|
|
33
33
|
* Rebuilding from scratch each pass is deliberate and matches refreshInboxSurface: a fresh channel per
|
package/dist/comms/catch-host.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
// `humanish comms catch
|
|
1
|
+
// `humanish comms catch`: run the email catch on the operator's own host.
|
|
2
2
|
//
|
|
3
|
-
// Why this exists. The in-sandbox catch only works when humanish provisions the subject itself
|
|
3
|
+
// Why this exists. The in-sandbox catch only works when humanish provisions the subject itself:
|
|
4
4
|
// it clones the app into a sandbox, injects the email-API base URL at boot, and hosts the catch
|
|
5
|
-
// alongside it. An adopter whose study runs against their
|
|
5
|
+
// alongside it. An adopter whose study runs against their own deployed environment (an app-url or
|
|
6
6
|
// operator-provisioned plane) hands humanish a URL instead, so humanish never boots the app and
|
|
7
7
|
// has nowhere to put a catch. Before this, a `comms:` block on that route was warned inert and the
|
|
8
8
|
// personas simply stalled at the verification screen.
|
|
9
9
|
//
|
|
10
10
|
// The fix is to let the adopter host the same catch and declare it (`comms.email.external`).
|
|
11
|
-
// Shipping it as a
|
|
11
|
+
// Shipping it as a command rather than a spec matters: the adopter runs the identical
|
|
12
12
|
// implementation humanish deploys in-sandbox, so the capture shape, the inbox surface, and the
|
|
13
13
|
// drain contract cannot drift between the two planes. Point the app's email-API base URL at this
|
|
14
14
|
// server and point the lab's `catchBaseUrl` at it too.
|
|
15
15
|
//
|
|
16
|
-
// Runtime: the catch is a python3 stdlib server with no dependencies (that was the 0.29.0 lesson
|
|
16
|
+
// Runtime: the catch is a python3 stdlib server with no dependencies (that was the 0.29.0 lesson:
|
|
17
17
|
// a co-located catcher must use a runtime the environment guarantees). This command writes the
|
|
18
18
|
// same script and runs it in the foreground until interrupted.
|
|
19
19
|
import { spawn } from "node:child_process";
|
|
@@ -27,10 +27,10 @@ const DEFAULT_RENDER_INTERVAL_MS = 3_000;
|
|
|
27
27
|
/**
|
|
28
28
|
* Render the persona-facing inbox surface from the catch's own deliveries file into `surfaceDir`.
|
|
29
29
|
*
|
|
30
|
-
* This is the half that was missing on an adopter-hosted plane
|
|
31
|
-
* /api/inbox as
|
|
30
|
+
* This is the half that was missing on an adopter-hosted plane. The catch serves /inbox and
|
|
31
|
+
* /api/inbox as static files; in-sandbox, humanish-as-host renders those files on a cadence. On a
|
|
32
32
|
* plane humanish does not provision there is no such host, so nothing wrote them and every persona
|
|
33
|
-
* that reached /inbox got `message not found` against a catch whose /health was green
|
|
33
|
+
* that reached /inbox got `message not found` against a catch whose /health was green, so the funnel
|
|
34
34
|
* dead-ended at a technically-healthy service.
|
|
35
35
|
*
|
|
36
36
|
* Rebuilding from scratch each pass is deliberate and matches refreshInboxSurface: a fresh channel per
|
|
@@ -43,7 +43,7 @@ export async function renderInboxSurfaceLocally(args) {
|
|
|
43
43
|
text = await readFile(args.deliveriesPath, "utf8");
|
|
44
44
|
}
|
|
45
45
|
catch {
|
|
46
|
-
text = ""; // no mail captured yet
|
|
46
|
+
text = ""; // no mail captured yet; still render, so /inbox answers "No messages yet."
|
|
47
47
|
}
|
|
48
48
|
const sends = parseDeliveriesNdjson(text);
|
|
49
49
|
const addresses = args.recipients && args.recipients.length > 0
|
|
@@ -55,7 +55,7 @@ export async function renderInboxSurfaceLocally(args) {
|
|
|
55
55
|
}));
|
|
56
56
|
const messages = await inboxMessagesFrom(sends, recipients);
|
|
57
57
|
const files = buildInboxSurface(messages, { recipients: addresses });
|
|
58
|
-
// A reused catch directory can change recipients or truncate its delivery log. Remove
|
|
58
|
+
// A reused catch directory can change recipients or truncate its delivery log. Remove only files
|
|
59
59
|
// recorded as generated routes, so old recipient/message URLs cannot survive that change.
|
|
60
60
|
const manifestPath = path.join(args.surfaceDir, ".inbox-files.json");
|
|
61
61
|
let previous = [];
|
|
@@ -105,8 +105,8 @@ export async function runCommsCatchHost(options, io) {
|
|
|
105
105
|
const surfaceDir = path.join(dir, "surface");
|
|
106
106
|
await mkdir(surfaceDir, { recursive: true });
|
|
107
107
|
await writeFile(scriptPath, SANDBOX_CATCH_SCRIPT, "utf8");
|
|
108
|
-
// Render the
|
|
109
|
-
// yet." page from the first request instead of a bare `message not found
|
|
108
|
+
// Render the empty inbox before the server is announced, so /inbox resolves to the "No messages
|
|
109
|
+
// yet." page from the first request instead of a bare `message not found`, because a persona reads
|
|
110
110
|
// that 404 as a broken product and reports a blocker that is really just an empty mailbox.
|
|
111
111
|
await renderInboxSurfaceLocally({
|
|
112
112
|
deliveriesPath,
|
|
@@ -131,7 +131,7 @@ export async function runCommsCatchHost(options, io) {
|
|
|
131
131
|
...(inboxPort === undefined
|
|
132
132
|
? []
|
|
133
133
|
: [
|
|
134
|
-
` read-only inbox listener on http://0.0.0.0:${inboxPort} (GET only;
|
|
134
|
+
` read-only inbox listener on http://0.0.0.0:${inboxPort} (GET only; this is the address to give personas)`,
|
|
135
135
|
]),
|
|
136
136
|
` POST /emails <- point your app's email-API base URL here`,
|
|
137
137
|
` GET /inbox <- shared operator inbox${inboxPort === undefined ? " (loopback only without --inbox-port)" : ""}`,
|
|
@@ -149,7 +149,7 @@ export async function runCommsCatchHost(options, io) {
|
|
|
149
149
|
? [` authTokenEnv: HUMANISH_COMMS_TOKEN # value read at runtime, never persisted`]
|
|
150
150
|
: []),
|
|
151
151
|
``,
|
|
152
|
-
`Captured mail is written to ${deliveriesPath}. Raw bodies stay on
|
|
152
|
+
`Captured mail is written to ${deliveriesPath}. Raw bodies stay on this host: the run bundle`,
|
|
153
153
|
`only ever receives digests (from/to/subject/link) and an OTP count.`,
|
|
154
154
|
``,
|
|
155
155
|
].join("\n") + "\n");
|
|
@@ -4,10 +4,10 @@ import path from "node:path";
|
|
|
4
4
|
import { parse, stringify } from "yaml";
|
|
5
5
|
import { COMMS_PROVIDERS, DEFAULT_COMMS_PROVIDER, isReceivingProviderId, } from "./providers.js";
|
|
6
6
|
import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory, prepareManagedHumanishOutputDirectory, readContainedRegularFile, writeContainedOutputFile, } from "../run/contained-output.js";
|
|
7
|
+
import { isRecord } from "../run/type-guards.js";
|
|
7
8
|
const COMMS_CONNECTIONS_SCHEMA = "humanish.comms-connections.v1";
|
|
8
9
|
export const COMMS_CONFIG_PATH = ".humanish/local/comms.yaml";
|
|
9
10
|
export { COMMS_PROVIDERS };
|
|
10
|
-
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
11
|
const validName = (name) => /^[a-z][a-z0-9-]{0,47}$/.test(name) && name !== "constructor" && name !== "prototype";
|
|
12
12
|
const empty = () => ({ schema: COMMS_CONNECTIONS_SCHEMA, connections: {} });
|
|
13
13
|
async function exists(file) {
|
|
@@ -22,16 +22,16 @@ async function exists(file) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
function parseConnections(raw) {
|
|
25
|
-
if (!
|
|
25
|
+
if (!isRecord(raw) ||
|
|
26
26
|
raw.schema !== COMMS_CONNECTIONS_SCHEMA ||
|
|
27
|
-
!
|
|
27
|
+
!isRecord(raw.connections) ||
|
|
28
28
|
Object.keys(raw).some((key) => key !== "schema" && key !== "connections") ||
|
|
29
29
|
Object.keys(raw.connections).length > 32)
|
|
30
30
|
throw new Error("Invalid connection configuration.");
|
|
31
31
|
const connections = {};
|
|
32
32
|
for (const [name, value] of Object.entries(raw.connections)) {
|
|
33
33
|
if (!validName(name) ||
|
|
34
|
-
!
|
|
34
|
+
!isRecord(value) ||
|
|
35
35
|
!isReceivingProviderId(value.provider) ||
|
|
36
36
|
typeof value.apiKeyEnv !== "string" ||
|
|
37
37
|
!/^[A-Z][A-Z0-9_]{0,99}$/.test(value.apiKeyEnv) ||
|
|
@@ -10,10 +10,10 @@ interface NormalizedSend {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* A provider wire-shape adapter. Vendor-neutral seam: the server owns the HTTP + routing + bus
|
|
13
|
-
* delivery; a profile only knows how
|
|
13
|
+
* delivery; a profile only knows how one provider (or a custom app) shapes its send request.
|
|
14
14
|
*/
|
|
15
15
|
export interface EmailSendProfile {
|
|
16
|
-
/** Descriptive name (e.g. "generic", "sendgrid"). Not a dependency
|
|
16
|
+
/** Descriptive name (e.g. "generic", "sendgrid"). Not a dependency, just a label. */
|
|
17
17
|
name: string;
|
|
18
18
|
/** Request paths (method POST) this profile accepts a send on. */
|
|
19
19
|
sendPaths: string[];
|
|
@@ -31,7 +31,7 @@ export interface EmailCatchServer {
|
|
|
31
31
|
close(): Promise<void>;
|
|
32
32
|
}
|
|
33
33
|
export interface EmailCatchOptions {
|
|
34
|
-
/** Bind host. Default 127.0.0.1 (loopback
|
|
34
|
+
/** Bind host. Default 127.0.0.1 (loopback only). */
|
|
35
35
|
host?: string;
|
|
36
36
|
/** Port. Default 0 (ephemeral). */
|
|
37
37
|
port?: number;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// A
|
|
2
|
-
// email provider is redirected into the fake bus with
|
|
3
|
-
// API base URL at this server. The catch does not depend on, or name itself after, any one vendor
|
|
1
|
+
// A vendor-neutral loopback catch server for email-send APIs. An app hardwired to a hosted
|
|
2
|
+
// email provider is redirected into the fake bus with one env var and no code change: point the app's
|
|
3
|
+
// API base URL at this server. The catch does not depend on, or name itself after, any one vendor:
|
|
4
4
|
// it normalizes each provider's distinct wire shape (Resend's flat body, SendGrid's nested
|
|
5
5
|
// personalizations, Postmark's TitleCase, a custom app's own JSON) to one shape via pluggable
|
|
6
|
-
//
|
|
6
|
+
// profiles, and routes the result into a CommsChannel. Resend-compatible and SendGrid-compatible out
|
|
7
7
|
// of the box; extend with a custom profile for anything else. (There is no standardized email-send
|
|
8
8
|
// request shape across vendors, so normalization is by design, not a shortcut.)
|
|
9
9
|
//
|
|
@@ -34,8 +34,8 @@ function toAddresses(value) {
|
|
|
34
34
|
return [];
|
|
35
35
|
}
|
|
36
36
|
// ---------------------------------------------------------------- built-in profiles
|
|
37
|
-
/** The flat-JSON shape: `{ from, to, subject, html, text }`. Matches Resend (POST /emails)
|
|
38
|
-
* custom app that sends the common shape
|
|
37
|
+
/** The flat-JSON shape: `{ from, to, subject, html, text }`. Matches Resend (POST /emails) and a
|
|
38
|
+
* custom app that sends the common shape and Postmark's TitleCase keys (From/To/HtmlBody/…). */
|
|
39
39
|
const genericEmailProfile = {
|
|
40
40
|
name: "generic",
|
|
41
41
|
sendPaths: ["/emails", "/emails/batch", "/email", "/email/batch", "/send"],
|
|
@@ -125,7 +125,7 @@ function readBody(req, limit) {
|
|
|
125
125
|
req.on("data", (chunk) => {
|
|
126
126
|
size += chunk.length;
|
|
127
127
|
if (size > limit) {
|
|
128
|
-
// Reject
|
|
128
|
+
// Reject without destroying the socket here: the handler responds 413 cleanly first (a
|
|
129
129
|
// write-after-destroy would otherwise reset the connection), then tears the request down.
|
|
130
130
|
reject(new BodyTooLargeError("request body too large"));
|
|
131
131
|
return;
|
|
@@ -193,7 +193,7 @@ export async function startEmailCatchServer(channel, options = {}) {
|
|
|
193
193
|
const sends = profile.parse(path, parsed);
|
|
194
194
|
if (sends.length === 0) {
|
|
195
195
|
// A well-formed request that names no deliverable recipient (empty body, or a shape the profile
|
|
196
|
-
// could not resolve to a send)
|
|
196
|
+
// could not resolve to a send): return a bad-request rather than a fabricated success id.
|
|
197
197
|
respondJson(res, 422, { error: "no deliverable message in request" });
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LabCommsEmail, LabCommsExternal } from "../lab/types.js";
|
|
2
|
+
import type { PreparedRunArtifactPaths } from "../run/paths.js";
|
|
3
|
+
/**
|
|
4
|
+
* Why a catch token cannot be used, or undefined when it can. An unset or empty token is none. A
|
|
5
|
+
* token that is not well-formed Unicode cannot be sent as a header or encoded as a URL, so it is
|
|
6
|
+
* refused with the short ones.
|
|
7
|
+
*/
|
|
8
|
+
export declare function catchTokenRefusal(token: string | undefined): string | undefined;
|
|
9
|
+
/** The catch token the run sends: the value of `authTokenEnv`, when the lab names one. */
|
|
10
|
+
export declare function catchTokenOf(external: LabCommsExternal, env: Record<string, string | undefined>): string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Drains the catch, routes its sends to the declared recipients' inboxes and writes the thread to
|
|
13
|
+
* `comms/thread.json`. Returns that path when a message matched, and the warnings to add to the
|
|
14
|
+
* run, each scrubbed of the run's known secret values and the catch token, then redacted. It never
|
|
15
|
+
* throws: the run continues without comms evidence.
|
|
16
|
+
*/
|
|
17
|
+
export declare function collectExternalCommsEvidence(args: {
|
|
18
|
+
external: LabCommsExternal;
|
|
19
|
+
email: LabCommsEmail;
|
|
20
|
+
env: Record<string, string | undefined>;
|
|
21
|
+
runPaths: PreparedRunArtifactPaths;
|
|
22
|
+
/** The run's known secret values. The route may add to it until the drain runs. */
|
|
23
|
+
knownSecretValues: readonly string[];
|
|
24
|
+
}): Promise<{
|
|
25
|
+
path?: string;
|
|
26
|
+
warnings: string[];
|
|
27
|
+
}>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// An adopter-hosted catch's thread, collected into a run. The computer-use and shared-world routes
|
|
2
|
+
// both call this, so every warning it returns is scrubbed the same way on both: an error or the
|
|
3
|
+
// catch URL can carry the catch's bearer token or a provisioned value, and neither has a secret's
|
|
4
|
+
// shape for pattern redaction to find.
|
|
5
|
+
import { redactText, toErrorMessage } from "../evidence/redaction.js";
|
|
6
|
+
import { scrubSecretValues } from "../evidence/secret-scrub.js";
|
|
7
|
+
import { addressedRecipients } from "../lab/parse/comms.js";
|
|
8
|
+
import { writeContainedOutputFile } from "../run/contained-output.js";
|
|
9
|
+
import { FakeInbox } from "./fake-inbox.js";
|
|
10
|
+
import { collectExternalCommsThread } from "./sandbox-catch.js";
|
|
11
|
+
/**
|
|
12
|
+
* The shortest catch bearer token humanish uses. The token guards GET /deliveries on a host the run
|
|
13
|
+
* reaches over the network, so it must not be guessable: 16 random base64 characters are 96 bits.
|
|
14
|
+
* The run also scrubs the token from every warning as a literal, and a short token would scrub
|
|
15
|
+
* ordinary words.
|
|
16
|
+
*/
|
|
17
|
+
const MIN_CATCH_TOKEN_LENGTH = 16;
|
|
18
|
+
/** String.prototype.isWellFormed (ES2024, Node 20+); the ES2023 lib types do not declare it. */
|
|
19
|
+
function isWellFormed(value) {
|
|
20
|
+
return value.isWellFormed();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Why a catch token cannot be used, or undefined when it can. An unset or empty token is none. A
|
|
24
|
+
* token that is not well-formed Unicode cannot be sent as a header or encoded as a URL, so it is
|
|
25
|
+
* refused with the short ones.
|
|
26
|
+
*/
|
|
27
|
+
export function catchTokenRefusal(token) {
|
|
28
|
+
if (token === undefined || token.length === 0)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (!isWellFormed(token))
|
|
31
|
+
return "The comms catch token is not well-formed Unicode; use printable ASCII, such as the output of `openssl rand -hex 16`, on the catch and in the run.";
|
|
32
|
+
if (token.length >= MIN_CATCH_TOKEN_LENGTH)
|
|
33
|
+
return undefined;
|
|
34
|
+
return `The comms catch token is ${token.length} characters; it must be at least ${MIN_CATCH_TOKEN_LENGTH}, such as the output of \`openssl rand -hex 16\`, on the catch and in the run.`;
|
|
35
|
+
}
|
|
36
|
+
/** The catch token the run sends: the value of `authTokenEnv`, when the lab names one. */
|
|
37
|
+
export function catchTokenOf(external, env) {
|
|
38
|
+
return external.authTokenEnv === undefined ? undefined : env[external.authTokenEnv];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Drains the catch, routes its sends to the declared recipients' inboxes and writes the thread to
|
|
42
|
+
* `comms/thread.json`. Returns that path when a message matched, and the warnings to add to the
|
|
43
|
+
* run, each scrubbed of the run's known secret values and the catch token, then redacted. It never
|
|
44
|
+
* throws: the run continues without comms evidence.
|
|
45
|
+
*/
|
|
46
|
+
export async function collectExternalCommsEvidence(args) {
|
|
47
|
+
const { external, email, env, runPaths, knownSecretValues } = args;
|
|
48
|
+
const authToken = catchTokenOf(external, env);
|
|
49
|
+
// Pattern redaction alone until the literal scrub exists, so a warning built in the catch below
|
|
50
|
+
// is never unredacted. The scrub is built inside the try, where a failure becomes that warning.
|
|
51
|
+
let scrubbed = (warnings) => warnings.map(redactText);
|
|
52
|
+
try {
|
|
53
|
+
const scrub = scrubSecretValues([
|
|
54
|
+
...knownSecretValues,
|
|
55
|
+
...(authToken === undefined ? [] : [authToken]),
|
|
56
|
+
]);
|
|
57
|
+
scrubbed = (warnings) => warnings.map((warning) => redactText(scrub(warning)));
|
|
58
|
+
const channel = new FakeInbox();
|
|
59
|
+
const inboxes = [];
|
|
60
|
+
for (const recipient of addressedRecipients(email)) {
|
|
61
|
+
inboxes.push(await channel.provisionAddress(recipient.participantId, recipient.address));
|
|
62
|
+
}
|
|
63
|
+
const collected = await collectExternalCommsThread({
|
|
64
|
+
external: { ...external, ...(authToken === undefined ? {} : { authToken }) },
|
|
65
|
+
channel,
|
|
66
|
+
inboxes,
|
|
67
|
+
});
|
|
68
|
+
if (collected.artifact) {
|
|
69
|
+
const path = "comms/thread.json";
|
|
70
|
+
await writeContainedOutputFile(runPaths, path, `${JSON.stringify(collected.artifact, null, 2)}\n`, "utf8");
|
|
71
|
+
return { path, warnings: [] };
|
|
72
|
+
}
|
|
73
|
+
if (collected.captured > 0) {
|
|
74
|
+
return {
|
|
75
|
+
warnings: scrubbed([
|
|
76
|
+
`The email catch captured ${collected.captured} email send(s), but none matched a declared recipient inbox, so no email evidence was written. Set comms.email.recipients[].address to the address the app sends to.`,
|
|
77
|
+
]),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
warnings: scrubbed([
|
|
82
|
+
`The email catch captured no email sends: the app never delivered mail through the catch at ${external.catchBaseUrl}. Verify the app's email API base URL points at it and that the flow reached an email step.`,
|
|
83
|
+
]),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
warnings: scrubbed([
|
|
89
|
+
`Comms evidence collection failed against the adopter-hosted catch (run continues): ${String(toErrorMessage(error))}`,
|
|
90
|
+
]),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/comms/extract.js
CHANGED
|
@@ -48,8 +48,8 @@ export function extractOtpCodes(body) {
|
|
|
48
48
|
list.push(c);
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
-
// The alphanumeric alternative requires at least one
|
|
52
|
-
//
|
|
51
|
+
// The alphanumeric alternative requires at least one digit (lookahead) so a labeled prose word like
|
|
52
|
+
// `your code is INVALID` isn't captured as a code; pure-digit codes (4–8) match directly.
|
|
53
53
|
const labeledRe = /(?:one[-\s]?time\s+(?:pass)?code|verification\s+code|security\s+code|access\s+code|login\s+code|confirmation\s+code|passcode|\bOTP\b|\bPIN\b|\bcode\b)\D{0,15}\b([0-9]{4,8}|(?=[A-Za-z0-9]*[0-9])[A-Z0-9]{6,8})\b/gi;
|
|
54
54
|
let m;
|
|
55
55
|
while ((m = labeledRe.exec(text)) !== null)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CommsAddress, CommsChannel, CommsChannelKind, CommsMessage, InboundRaw, OutboundMessage } from "./types.js";
|
|
2
2
|
export interface FakeInboxOptions {
|
|
3
|
-
/** "email" (default) or "sms"
|
|
3
|
+
/** "email" (default) or "sms": the address shape + surface differ; machinery is identical. */
|
|
4
4
|
channel?: CommsChannelKind;
|
|
5
5
|
/** Email domain for minted addresses. Default example.test, an RFC 6761 reserved, unroutable
|
|
6
6
|
* test domain. */
|
|
@@ -21,7 +21,7 @@ export declare class FakeInbox implements CommsChannel {
|
|
|
21
21
|
constructor(options?: FakeInboxOptions);
|
|
22
22
|
provision(participantId: string): Promise<CommsAddress>;
|
|
23
23
|
/**
|
|
24
|
-
* Provision an inbox for `participantId` at an
|
|
24
|
+
* Provision an inbox for `participantId` at an explicit address (a lab-declared recipient), so
|
|
25
25
|
* the app-under-test's send to that literal address resolves in `deliverRaw` (which drops
|
|
26
26
|
* recipients with no provisioned inbox). Declaring the same address intentionally shares its
|
|
27
27
|
* inbox; if `participantId` already held a different auto-generated address, the declared
|
package/dist/comms/fake-inbox.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The in-process email/SMS bus
|
|
1
|
+
// The in-process email/SMS bus: a working, in-memory implementation of the CommsChannel
|
|
2
2
|
// port. Deterministic, offline and free: a message the app under test sends (through an ingress
|
|
3
3
|
// such as the email catch) is routed to the addressed inbox and read back through the same port.
|
|
4
4
|
// Nothing leaves the process. See types.ts for the port and its public-safety notes.
|
|
@@ -60,7 +60,7 @@ export class FakeInbox {
|
|
|
60
60
|
return address;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* Provision an inbox for `participantId` at an
|
|
63
|
+
* Provision an inbox for `participantId` at an explicit address (a lab-declared recipient), so
|
|
64
64
|
* the app-under-test's send to that literal address resolves in `deliverRaw` (which drops
|
|
65
65
|
* recipients with no provisioned inbox). Declaring the same address intentionally shares its
|
|
66
66
|
* inbox; if `participantId` already held a different auto-generated address, the declared
|