From 24beb005755465f71a19ec92a85da0958d1b9740 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Tue, 29 Sep 2026 08:56:21 -0500 Subject: [PATCH] feat(runner): add rich ACP transport and durable interaction foundation (#14430) Add shared rich ACP transport, durable questions and permissions, verified provider packaging, and bounded activity and plan presentation. Keep Cursor, Copilot, and Pi pending their separate provider qualification. Persist interaction settlement before publication, fence failed writes until fresh recovery, and preserve owned-process cleanup. Incorporate reviewed mainline integration with extended harness coverage. Co-Authored-By: Paperclip --- .env.runner-e2e.example | 4 + .../runner-rich-acp-capabilities.md | 592 ++++++++++++ doc/evals.md | 6 + doc/plans/2026-09-28-runner-rich-acp.md | 30 + docker/daytona-runner/Dockerfile | 9 +- docker/daytona-runner/README.md | 37 + packages/adapter-utils/src/index.ts | 1 + .../src/paperclip-runner-permissions.ts | 11 +- .../src/sandbox-managed-runtime.test.ts | 5 +- packages/paperclip-runner/README.md | 37 +- .../devtools/issue-thread/src/ThreadItems.tsx | 1 + packages/paperclip-runner/package.json | 2 +- .../paperclip-runner/protocol/manifest.json | 10 +- .../provider-schemas/acpx-sidecar.schema.json | 104 ++- .../protocol/schemas/event-v2.schema.json | 3 +- .../schemas/provider-descriptor.schema.json | 16 +- .../schemas/provider-event.schema.json | 2 +- .../protocol/schemas/question-set.schema.json | 2 +- .../protocol/schemas/request.schema.json | 175 ++++ .../runner-core/src/acpx_event_payload.rs | 208 ++++- .../runner-core/src/acpx_provider_backend.rs | 861 ++++++++++++++++-- .../runner-core/src/acpx_provider_session.rs | 222 ++++- .../runner-core/src/acpx_provider_state.rs | 87 +- .../runner-core/src/acpx_sidecar_transport.rs | 37 +- .../runner-core/src/bin/fake-acpx-sidecar.rs | 25 +- .../src/bin/fake-codex-app-server.rs | 13 +- .../crates/runner-core/src/codex_provider.rs | 50 +- .../crates/runner-core/src/durable/mod.rs | 4 +- .../crates/runner-core/src/durable/runner.rs | 119 +++ .../crates/runner-core/src/durable/state.rs | 149 ++- .../src/generated_acpx_sidecar_contract.rs | 4 + .../runner-core/src/provider_backend.rs | 8 + .../crates/runner-core/src/provider_events.rs | 145 ++- .../tests/acpx_event_projection.rs | 51 +- .../tests/acpx_provider_checkpoint.rs | 1 + .../runner-core/tests/acpx_provider_events.rs | 125 +++ .../tests/acpx_provider_resolutions.rs | 40 + .../tests/acpx_provider_session.rs | 9 +- .../runner-core/tests/acpx_provider_state.rs | 69 ++ .../tests/acpx_provider_suspend.rs | 1 + .../runner-core/tests/acpx_provider_turns.rs | 89 +- .../runner-core/tests/acpx_rich_events.rs | 181 ++++ .../tests/acpx_sidecar_transport.rs | 39 +- .../runner-core/tests/codex_provider.rs | 39 +- .../tests/native_provider_backend.rs | 618 ++++++++++++- .../scripts/build-provider-pack.mjs | 24 +- .../scripts/candidate-provider-pack.mjs | 22 + .../scripts/candidate-provider-pack.test.mjs | 14 + .../src/backends/codex-acpx-native-backend.ts | 13 +- .../src/backends/codex-native-backend.ts | 8 +- .../backends/harness-driver-backend.test.ts | 18 + .../src/backends/harness-driver-backend.ts | 10 +- .../backends/native-backend-factory.test.ts | 26 +- .../src/backends/native-backend-factory.ts | 5 - .../src/cli/acpx-runtime-sidecar.test.ts | 47 +- .../src/cli/acpx-runtime-sidecar.ts | 197 +++- .../src/cli/eval-session-contract.test.ts | 179 +++- .../src/cli/eval-session-contract.ts | 57 +- .../src/cli/eval-session-entrypoint.test.ts | 31 + .../paperclip-runner/src/cli/eval-session.ts | 106 ++- .../src/contracts/harness-driver.ts | 5 +- .../src/contracts/native-execution.test.ts | 11 +- .../src/contracts/native-execution.ts | 12 +- .../src/contracts/native-session-backend.ts | 1 + .../src/contracts/question-set.ts | 4 +- .../paperclip-runner/src/contracts/types.ts | 7 + .../durable-prp-control-plane.test.ts | 50 +- .../durable-prp-control-plane.ts | 6 +- .../acpx/acp-permission-adapter.test.ts | 45 + .../drivers/acpx/acp-permission-adapter.ts | 63 ++ .../src/drivers/acpx/capability-profiles.ts | 67 ++ .../drivers/acpx/codex-acpx-driver.test.ts | 124 ++- .../src/drivers/acpx/codex-acpx-driver.ts | 221 ++++- .../acpx/codex-runtime-adapter.test.ts | 131 ++- .../src/drivers/acpx/codex-runtime-adapter.ts | 158 +++- .../src/drivers/acpx/driver-profile.test.ts | 10 +- .../src/drivers/acpx/driver-profile.ts | 32 +- .../src/drivers/acpx/environment.test.ts | 64 +- .../src/drivers/acpx/environment.ts | 78 +- .../acpx/generated-sidecar-contract.ts | 2 + .../drivers/acpx/installation-integrity.ts | 39 + .../native-distribution-integrity.test.ts | 233 +++++ .../acpx/native-distribution-integrity.ts | 200 ++++ .../drivers/acpx/profile-extensions.test.ts | 58 ++ .../src/drivers/acpx/profile-extensions.ts | 139 +++ .../src/drivers/acpx/profile-installation.ts | 21 + .../drivers/acpx/provider-assets-root.test.ts | 47 + .../src/drivers/acpx/provider-assets-root.ts | 64 ++ .../src/drivers/acpx/qualified-profiles.ts | 28 +- .../drivers/acpx/recovery-identity.test.ts | 15 + .../src/drivers/acpx/recovery-identity.ts | 7 + .../drivers/acpx/response-delivery.test.ts | 29 + .../src/drivers/acpx/response-delivery.ts | 17 + .../src/drivers/acpx/runtime-host.test.ts | 64 +- .../src/drivers/acpx/runtime-host.ts | 112 ++- .../src/drivers/acpx/runtime-sandbox.test.ts | 53 +- .../src/drivers/acpx/runtime-sandbox.ts | 76 ++ .../src/drivers/acpx/runtime-skill-lease.ts | 22 + .../src/drivers/acpx/sidecar-protocol.ts | 1 + .../src/drivers/acpx/turn-controls.test.ts | 26 + .../src/drivers/acpx/turn-controls.ts | 42 + .../src/drivers/acpx/usage-accounting.test.ts | 44 +- .../src/drivers/acpx/usage-accounting.ts | 39 +- .../src/drivers/codex/app-server-transport.ts | 3 + .../codex-app-server-driver.test-support.ts | 10 +- .../codex/codex-app-server-driver.test.ts | 32 + .../drivers/codex/codex-harness-session.ts | 18 +- .../drivers/codex/codex-question-adapter.ts | 14 +- .../codex/codex-session-server-requests.ts | 3 +- .../codex/codex-thread-normalization.ts | 4 +- .../src/issue-thread/issue-thread.test.ts | 5 + .../src/issue-thread/live-projection.ts | 3 +- .../src/issue-thread/public-view.ts | 13 +- .../src/live/live-session.test.ts | 149 ++- .../paperclip-runner/src/live/live-session.ts | 148 ++- .../src/live/runnerd-codex-transport.test.ts | 234 ++++- .../src/live/runnerd-codex-transport.ts | 127 ++- .../src/protocol/generated/schema-bundle.ts | 264 +++++- .../generated/standalone-validators.ts | 2 +- .../src/protocol/permission-request.test.ts | 47 + .../src/protocol/provider-descriptor.test.ts | 24 + .../src/reducer/session-reducer.test.ts | 16 + .../acpx-pi-receipt-package-contract.test.mjs | 90 ++ ...esponse-delivery-package-contract.test.mjs | 100 ++ ...-rich-extensions-package-contract.test.mjs | 119 +++ .../test/acpx-sidecar-contract.test.mjs | 29 + packages/shared/src/validators/issue.ts | 2 +- patches/acpx@0.13.1.patch | 450 ++++++++- scripts/check-node-version-policy.mjs | 3 +- scripts/node-image-policy.mjs | 7 + scripts/standard-image-contract.test.mjs | 18 + server/src/__tests__/adapter-registry.test.ts | 15 + .../heartbeat-cost-accounting.test.ts | 21 + .../__tests__/issue-feedback-routes.test.ts | 112 ++- server/src/adapters/registry.ts | 15 +- .../heartbeat-runner-provider-config.test.ts | 2 +- server/src/services/heartbeat.ts | 12 +- .../native-runtime/acpx-qualification.test.ts | 43 + .../native-runtime/acpx-qualification.ts | 32 + .../native-permission-projection.test.ts | 61 ++ .../native-question-bridge.test.ts | 97 ++ .../native-runtime/native-question-bridge.ts | 35 +- .../native-session-executor.test.ts | 53 +- .../native-runtime/native-session-executor.ts | 82 +- .../native-runtime/provider-profile.ts | 25 +- .../workspace-git-snapshot-streaming.test.ts | 13 +- tests/runner-e2e/FIXTURES.md | 8 + tests/runner-e2e/README.md | 45 + tests/runner-e2e/SECURITY.md | 14 + tests/runner-e2e/catalog.test.ts | 6 +- tests/runner-e2e/catalog.ts | 55 +- tests/runner-e2e/daytona-image-content.ts | 23 +- tests/runner-e2e/daytona-image.test.ts | 11 +- tests/runner-e2e/extended-harnesses.test.ts | 49 + tests/runner-e2e/harness-env.ts | 21 +- tests/runner-e2e/launch.ts | 1 + tests/runner-e2e/live-fixtures.test.ts | 18 + tests/runner-e2e/live-fixtures.ts | 7 +- tests/runner-e2e/redaction.ts | 1 + tests/runner-e2e/types.ts | 4 + .../codex-local/config-fields.test.tsx | 12 +- ui/src/adapters/codex-local/config-fields.tsx | 20 +- ui/src/adapters/config-sections.test.tsx | 17 +- .../adapters/paperclip-runner/index.test.ts | 31 + ui/src/adapters/paperclip-runner/index.ts | 9 +- ui/src/components/MarkdownBody.test.tsx | 27 + ui/src/components/MarkdownBody.tsx | 15 +- ui/src/components/TaskChatThread.test.tsx | 69 ++ ui/src/components/task-chat/QuestionForm.tsx | 12 +- .../TaskChatProtocolActivityRow.test.tsx | 21 +- .../task-chat/TaskChatProtocolActivityRow.tsx | 8 +- .../task-chat/TaskChatProtocolCard.test.tsx | 82 +- .../task-chat/transcript-adapter.ts | 9 + .../transcript/RunTranscriptView.tsx | 1 + .../transcript/native-run-events.ts | 7 +- .../rich-acp-browser-proof.darwin-arm64.json | 147 +++ .../stories/rich-acp-interactions.stories.tsx | 321 +++++++ 177 files changed, 10463 insertions(+), 625 deletions(-) create mode 100644 doc/architecture/runner-rich-acp-capabilities.md create mode 100644 doc/plans/2026-09-28-runner-rich-acp.md create mode 100644 packages/paperclip-runner/runner/crates/runner-core/tests/acpx_rich_events.rs create mode 100644 packages/paperclip-runner/scripts/candidate-provider-pack.mjs create mode 100644 packages/paperclip-runner/scripts/candidate-provider-pack.test.mjs create mode 100644 packages/paperclip-runner/src/cli/eval-session-entrypoint.test.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/acp-permission-adapter.test.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/acp-permission-adapter.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/capability-profiles.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.test.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/profile-extensions.test.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/profile-extensions.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/profile-installation.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/provider-assets-root.test.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/provider-assets-root.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/response-delivery.test.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/response-delivery.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/runtime-skill-lease.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/turn-controls.test.ts create mode 100644 packages/paperclip-runner/src/drivers/acpx/turn-controls.ts create mode 100644 packages/paperclip-runner/src/protocol/permission-request.test.ts create mode 100644 packages/paperclip-runner/test/acpx-pi-receipt-package-contract.test.mjs create mode 100644 packages/paperclip-runner/test/acpx-response-delivery-package-contract.test.mjs create mode 100644 packages/paperclip-runner/test/acpx-rich-extensions-package-contract.test.mjs create mode 100644 scripts/node-image-policy.mjs create mode 100644 server/src/services/native-runtime/acpx-qualification.test.ts create mode 100644 server/src/services/native-runtime/acpx-qualification.ts create mode 100644 server/src/services/native-runtime/native-permission-projection.test.ts create mode 100644 tests/runner-e2e/extended-harnesses.test.ts create mode 100644 ui/storybook/fixtures/evidence/rich-acp-browser-proof.darwin-arm64.json create mode 100644 ui/storybook/stories/rich-acp-interactions.stories.tsx diff --git a/.env.runner-e2e.example b/.env.runner-e2e.example index 1e208f879f..c1e2377a39 100644 --- a/.env.runner-e2e.example +++ b/.env.runner-e2e.example @@ -6,6 +6,10 @@ XAI_API_KEY= OPENROUTER_API_KEY= DAYTONA_API_KEY= +# Explicit-only extended-harnesses candidates. +CURSOR_AUTH_TOKEN= +COPILOT_GITHUB_TOKEN= + # Required only for Daytona cells. Use an immutable, anonymously pullable # digest from the Runner Full-Stack E2E image job or a locally published image. PAPERCLIP_E2E_DAYTONA_IMAGE=ghcr.io/paperclipai/paperclip-daytona-runner@sha256:REPLACE_ME diff --git a/doc/architecture/runner-rich-acp-capabilities.md b/doc/architecture/runner-rich-acp-capabilities.md new file mode 100644 index 0000000000..54467eaa03 --- /dev/null +++ b/doc/architecture/runner-rich-acp-capabilities.md @@ -0,0 +1,592 @@ +# Rich ACP integration and qualification report + +Updated: 2026-09-29. Base: `c65fc9e3c81c41aafe421aa90a00514b84343285`. +Mainline integration: `3ca196b0a642aa21b8feba1fcd89edb53d1c622e`. +Status: implementation is split into a shared foundation and three provider PRs. +Provider review and qualification remain separate from foundation acceptance. +All three new profiles remain **pending qualification**. Cursor passed all five +local semantic Product E2E cases. Copilot passed completion, file validation, +plan approval and controller restart; its question case failed the required final +marker. Pi passed completion, file validation and semantic question continuation, +plus real native denial, active steering and queued follow-up probes on v2/v3/v4. +Pi completion/file/question passes retain their v2/v3 identities. The latest +Pi candidate is profile v5 with a patched dependency closure; its paid native +control probe passed, but its Product qualification remains blocked. Copilot +passed real native denial and detached-command settlement probes. Failed attempts remain in the evidence; +no Daytona resources have been started. This report does not certify a provider +from its ACP listing or a partial run. + +The [harness priorities report](https://pages.paperclip.ing/2026-09-25-harness-priorities/) +recommends Cursor and Copilot, followed by Pi, using the existing qualified ACPX +path. Codex app-server is the richness benchmark. The legacy Cursor and Pi +adapters are outside this change. + +## Branches and evidence ownership + +| Branch | Deliverable | +| --- | --- | +| [`codex/runner-rich-acp` / #14430](https://github.com/paperclipai/paperclip/pull/14430) | Shared ACPX extension boundary, durable permissions, canonical display events, provider pack infrastructure, configuration and UI | +| [`codex/runner-cursor-acp` / #14435](https://github.com/paperclipai/paperclip/pull/14435) | Cursor native distribution, questions/plans, child activity, policy admission, wire fixtures | +| [`codex/runner-copilot-acp` / #14434](https://github.com/paperclipai/paperclip/pull/14434) | Copilot native distribution, event inventory/projections, permission and settlement probes | +| [`codex/runner-pi-acp` / #14436](https://github.com/paperclipai/paperclip/pull/14436) | Patched wrapper, owned extension, MCP/tools, permissions/input, portable dependency closure | +| [`codex/rich-acp-extended-harness-evals` / evals #29](https://github.com/paperclipai/paperclip-evals/pull/29) | Explicit 21-cell Runner Eval campaign, semantic assertions, provenance and fail-closed budget accounting | + +The provider branches were implemented in parallel from the foundation. Final +shared registration and packaging conflicts are resolved in dependency order: +foundation → Cursor → Copilot → Pi. They remain four separate worktrees and PR +review units; the later PR bases include their prerequisite providers. Foundation +acceptance requires Apex review and CI to pass. Provider PRs remain unmerged +pending qualification. Source reports on the provider branches are +`doc/architecture/runner-cursor-capabilities.md`, +`doc/architecture/runner-copilot-capabilities.md`, and +`doc/architecture/runner-pi-capabilities.md`. Those reports retain versioned +source references, per-field dispositions, fixture paths, and narrower claims. +The Copilot inventory enumerates all 150 pinned native event types. Read the +[Cursor inventory](https://github.com/paperclipai/paperclip/blob/codex/runner-cursor-acp/doc/architecture/runner-cursor-capabilities.md), +[Copilot inventory](https://github.com/paperclipai/paperclip/blob/codex/runner-copilot-acp/doc/architecture/runner-copilot-capabilities.md), +and [Pi inventory](https://github.com/paperclipai/paperclip/blob/codex/runner-pi-acp/doc/architecture/runner-pi-capabilities.md) +for the complete per-provider source audit. + +[Retained browser evidence](../../ui/storybook/fixtures/evidence/rich-acp-browser-proof.darwin-arm64.json) +records the production renderer's full native plan, accept/reject/cancel, +single/multiple selection, typed input and activity-details checks. Its Cursor +transport is a canonical fixture, not a paid provider session. The browser +renders a 99,724-character plan and verifies its final paragraph before approval. +The JSON records screenshot hashes; screenshots remain outside the source tree. + +## Capability matrix + +“Candidate” means implemented or observed in deterministic tests. It does not +mean the required paid local and Daytona product cases have passed. “Not exposed” +means the pinned interface was inspected; “unverified” is a separate finding. +Codex's row is the existing app-server integration, not the Codex ACP bridge. + +The provider reports above are the method/event and field inventories. In the +table below, a method that is absent from a pinned implementation is distinct +from an exposed method with no Paperclip control. Shared surfaces and their +deterministic evidence are mapped separately after the comparison. + +| Capability | Codex app-server benchmark | Cursor ACP candidate | Copilot ACP candidate | Pi ACP candidate | +| --- | --- | --- | --- | --- | +| Exact model | Selected and reported model | Explicit ID required; exact echo, paid semantic protocol and five local semantic product cases passed | Explicit ID required; exact `gpt-5.6-luna` echo, paid semantic protocol and local completion/file/plan/restart passed | Exact `openrouter/deepseek/deepseek-v4-flash-0731`; model echo and v2/v3 paid Product completion/file/question cases passed; v5 native controls passed | +| Text and tools | Typed thread/turn/item events | Standard ACP updates; child activity kept separate | Standard ACP plus opt-in native session events | Wrapper text/tool updates and owned tool gates | +| Active steering | Dedicated `turn/steer` | Concurrent prompt replaces/cancels, so it is not steering | Concurrent prompt replaces/cancels, so it is not steering | Owned `pi/steer` requires handshake, exact active turn and acknowledgment; real active-turn probe passed | +| Queued follow-up | Product continuation controls | Controller can schedule a later prompt; native queue not established | Native pending-message activity exists; no qualified ACP queue responder | Owned `pi/follow_up`, separately named and ordered; real queued marker followed the steered current response | +| Cancellation | Typed interrupt and process lifecycle | ACP cancel; paid command cleanup pending | ACP cancel; bounded live detached-command settlement and cleanup passed | Native abort; wrapper waits for `agent_settled` and treats provider errors as failure | +| Session continuity | Read/load/history/fork and durable identity | Session load/list observed; paid semantic warm continuation passed; native history replay unverified and fork absent in tested methods | Session load plus native history events; semantic pending-question controller recovery passed | Private Pi JSONL mapping/load; native RPC `fork`, `clone`, `get_fork_messages` are not mapped through this ACP wrapper; unresolved UI promises cannot survive provider death | +| Questions | Typed input requests and response correlation | `cursor/ask_question`, option identity and multiple selection preserved | Native ask-user capability exists, but pinned ACP does not wire its responder; do not display a false answerable form | `select`, `confirm`, `input`, `editor` through typed form elicitation | +| Permissions | Durable typed approvals | Standard ACP permission options; denied shell write had no observed side effects; separately labeled exact-correlation assessment | Standard ACP; real native reject_once prevented marker creation; session decision scope inspected | Native pre-tool gate; allow once, exact-operation session grant, deny; paths rechecked after wait | +| Plans | Typed plan and collaboration mode | `cursor/create_plan` includes full plan and revision-bound accept/reject/cancel; todo activity separate | Native plan events displayed; native plan-decision callback not exposed in ACP | No native structured plan event; authenticated Paperclip planning tools available | +| Authenticated tools | Runner bridge and governed operations | ACP HTTP MCP binding; paid context/history reads passed | ACP HTTP MCP binding; paid context read passed | Owned extension registers exact bound MCP tools; four authenticated semantic reads succeeded in a paid partial run; no ambient servers | +| Delegation | Typed agent roles and lifecycle | Opt-in subagent lifecycle, nested ownership and bounded child activity; never parent transcript flattening | Native delegation/session events projected with role/model/agent provenance | No built-in ACP delegation protocol; arbitrary extensions are excluded | +| Files/diffs | Typed file changes and artifact references | Standard tool changes plus validated image references | File/workspace events retained; provider session files are not silently treated as task files | Wrapper-retained read/write/edit diffs; common typed/UI projection remains partial; semantic artifact tools | +| Images/artifacts | Typed references and registered work products | Existing contained files only, provenance, `registered:false` | Contained task references only; external/session-store paths become descriptive notices | Image/resource tool blocks preserved by wrapper; dedicated artifact channel absent | +| Image/attachment input | Typed input conversion | ACP advertises images, but the runner turn converter currently forwards text only | ACP advertises images/embedded context, but the runner turn converter currently forwards text only | Native Pi/ACP image input exists, but the runner turn converter currently forwards text only | +| Usage | Per-request receipt and model context | Pinned ACP omitted receipts on denied and successful turns; account UI confirms included usage separately | ACP/native tokens retained; account UI confirms included credits separately, without a per-run USD receipt | Assistant-message and compaction token receipts; dollar cost is a catalog pricing estimate, never authoritative billing | +| Config/model changes | Typed configurable controls | Known modes/model interfaces researched; runtime policy cannot be changed by a display event | Model/reasoning/mode options exist; runtime policy remains authoritative | Exact pinned candidate model; arbitrary slash commands/config/extensions disabled | +| Reconnect/restart | Durable controller replay and qualified provider restoration | Semantic pending question survived a real controller restart; exact native callback restoration remains unverified | Semantic pending question survived controller restart with the same interaction and provider session; native callback restoration remains unverified | Wrapper explicitly advertises live-process-only pending-input recovery | + +| Shared capability | User-visible surface | Inspectable implementation / deterministic evidence | +| --- | --- | --- | +| Text, tools and child activity | Task transcript; bounded activity details preserve provider/session/tool attribution | `src/drivers/acpx/codex-runtime-adapter.test.ts`, `runner/crates/runner-core/tests/acpx_rich_events.rs`, `ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx` | +| Native questions and permissions | Existing question/confirmation cards; only offered decisions can be submitted | `src/protocol/permission-request.test.ts`, `src/drivers/acpx/acp-permission-adapter.test.ts`, `server/src/services/native-runtime/native-question-bridge.test.ts`, `ui/src/components/task-chat/TaskChatProtocolCard.test.tsx` | +| Native plan decisions | Full plan description followed by a revision-bound decision; no implicit plan acceptance | Cursor provider fixtures; `ui/storybook/fixtures/evidence/rich-acp-browser-proof.darwin-arm64.json` records the real renderer with a canonical fixture | +| Active steering and queued follow-up | Existing active-turn control where a bound method is negotiated; no native Pi queue selector yet | `src/drivers/acpx/turn-controls.test.ts`; provider reports state which wire method is absent or not surfaced | +| Files, diffs and images | Workspace file/artifact cards plus contained provider reference notices; raw provider diffs are still partial | `src/drivers/acpx/profile-extensions.test.ts`; provider field audits; Product `file-edit-validate` uses an independent exact-byte oracle | +| Usage and model identity | Exact configured model, per-provider token/accounting fields, explicit incomplete cost coverage | `src/drivers/acpx/usage-accounting.test.ts`, `src/cli/eval-session-contract.test.ts`, `server/src/services/native-runtime/native-session-executor.test.ts` | +| Durable input, reconnect and provider death | Pending interaction cards survive controller recovery; unsafe replacement expires unresolved requests; delivered settlement survives a crash before journaling | `src/control-plane/durable-prp-control-plane.test.ts`, `runner/crates/runner-core/tests/acpx_provider_resolutions.rs`, `runner/crates/runner-core/tests/native_provider_backend.rs`, `src/live/runnerd-codex-transport.test.ts` | +| Session list/fork, generic configuration and commands | No added operator surface; exact owned session recovery and configured model remain available | Provider inventories identify native-only, ACP-exposed, confirmed-absent and unverified methods with follow-ups | + +Paths starting with `src/` or `runner/` in this evidence table are relative to +`packages/paperclip-runner/`; other paths are repository-relative. Deterministic +fixtures establish contract behavior, not successful paid provider execution. + +## Shared event and interaction contract + +The active connection, wire session, normalized turn and original request identity +bind every extension callback. The allowlist is per provider. An omitted wire +session can only acquire the verified active connection's session; an explicit +mismatch is rejected. Retired streams and inactive turns cannot emit new activity. + +Requests enter durable runtime state before the UI presents them. The response +must match an outstanding request and an offered action or valid typed answer. +The direct driver and sidecar await a receipt for the exact JSON-RPC pipe write before the runtime settles its durable record. +The successful resolution enters the retained event outbox in the same atomic +state save that removes the pending request. A restart before journal delivery +retains that resolution for normal event replay and acknowledgment. A restart +before this state save instead expires the unresolved request; neither path sends +the provider response again. +If persistence fails, the current executor stops accepting commands and exposing +or acknowledging retained events. Cleanup still terminates its owned provider, +but leaves the uncertain snapshot untouched. A fresh executor reads the complete +atomic snapshot that survived; it cannot publish an in-memory resolution that +conflicts with a later recovery expiry. +Standard ACP does not acknowledge application of a permission reply; a lost +transport acknowledgment is not proof of exactly-once external effects. A +replacement provider process cannot inherit an old approval promise. A bounded durable ledger expires pending requests after provider loss or unsafe restart, including requests whose creation events were already acknowledged. No tool +mutation or approval is automatically replayed into a replacement. + +Full plan documents have a bounded 100,000-character description and a 196 KiB +question-set envelope. Oversized plans fail rather than approve an unseen suffix. +Display redaction remains visible. Decision descriptions render image references as +inert text and Mermaid diagrams as source, so reviewing a plan does not fetch +provider-selected media. Automatic issue-reference linking is disabled for these +descriptions, so a long provider plan cannot start an issue-detail query for every +identifier. The rich event channel has exact canonical +schemas and a bounded envelope. It cannot create terminal outcomes, dispatch a +semantic tool, register an artifact, synchronize a durable plan, or supply source +authority. Notices retain useful bounded fields and provenance in expandable UI +details. Provider references remain unregistered until a control-plane operation +registers them. + +Permission labels are derived from offered option kinds. An unknown or duplicate +option is rejected. A provider's “always” decision is not relabeled “this session” +unless the pinned implementation proves that scope. Restrictive execution policy +is separate from automatic approval and company governance. Fresh permission +configuration defaults to full auto; it never overrides read-only task policy. + +## Distribution and isolation + +Cursor pins `2026.09.26-dd393fe`; Copilot pins `1.0.88`; Pi pins wrapper `0.0.33`, +runtime `0.84.2`, portable Node and its full npm lock. Native distribution hashes +cover macOS ARM64, macOS x64 and Linux x64. Source-owned closure pins remain +separate from profile declaration digests. Native admission reads held files, +verifies every admitted byte, creates a private immutable snapshot and retains +the existing process guardian. A manifest cannot supply its own trusted pin. + +Candidate credentials are only read from explicitly bound run environments: +Cursor `CURSOR_API_KEY`/`CURSOR_AUTH_TOKEN`, Copilot `COPILOT_GITHUB_TOKEN`, Pi +`OPENROUTER_API_KEY`. Ambient GitHub login variables, provider configuration, +extensions and MCP discovery do not establish authority. Homes/config/cache are +private. Updates are disabled. Pi launches only its owned extension and assigned +skills, with a startup sentinel before a prompt can run. + +Build candidate packs explicitly with `--candidate-providers=` on that +provider's branch. Local and remote pack verification includes the complete +candidate asset tree. The corresponding Daytona build argument is documented in +`docker/daytona-runner/README.md`. Candidate packaging never promotes a profile. + +ACP is not an OS sandbox. Cursor still reads project MCP/hooks beyond its +`--disable-project-configs` flag; the adapter rejects known ambient execution +configuration. Mutations during a run and remote team hooks still require +qualification. Pi's tool policy supplements the execution boundary; arbitrary +shell commands and filesystem races require the host boundary. These are +qualification gates, not claims that a JavaScript path check confines a shell. + +## Explicit gaps and follow-ups + +| Priority | Exposed but unused, partial, or unverified | Reason and next proof | +| --- | --- | --- | +| P0 | Remaining paid product cases on local and Daytona | Credentials are explicitly bound. Cursor's five local semantic cases passed. Copilot completion/file/plan/restart cases and Pi completion/file/question passed. Copilot question continuation reached the correct UI and warm session but failed its final marker. Copilot question qualification, remaining Pi cases and all remote cases remain open. The 30-cell Product E2E and private 21-cell Runner Eval extended suites are explicit-only. Daytona billing access still requires user email verification. | +| P0 | Pi fresh-profile Product verification blocked by host resources | The next paid plan case failed during embedded PostgreSQL initialization before any provider prompt. A disposable reproduction found 16 free SysV semaphores when Postgres required a set of 17. Resume the unchanged canonical case only after task-owned cleanup or a supported isolated test host restores capacity; do not remove unrelated IPC objects or substitute an unqualified database fixture. | +| P0 | Copilot native ask-user and plan-decision callbacks | Pinned ACP does not install native responders. Prove no blocking request is exposed, or add a qualified responder/wrapper; never swallow the request. | +| P0 | Broader Copilot denial and background settlement qualification | Two bounded real-service probes passed on pinned 1.0.88: native reject_once prevented a requested file write, and an observed detach:true command completed before end_turn. These establish the selected cases only; retain broader local/Daytona qualification before promotion. | +| P0 | Cursor native question availability | A real default-mode prompt with no semantic MCP tools reported that native AskQuestion was unavailable. No native request arrived. The pinned client implements the RPC and mode controls, but inspected local guards do not explain the negotiated tool availability. Inspect the actual catalog/flags before another live probe; do not infer permanent harness absence or native success from semantic question tests. | +| P0 | Typed Cursor entitlement failure | Exact models and successful inference are observed on the selected paid accounts. Cursor's first-account entitlement denial became ordinary text and normal completion. Preserve this failure and qualify typed failure handling; never infer a successful task from terminal status alone. | +| P0 | Cursor project and remote hooks; native shell boundaries | Configuration flags do not cover every native source. Demonstrate policy cannot be bypassed before production qualification. | +| P1 | Native Pi queue selection in the product UI | The runner API exposes negotiated `follow_up` separately from active steering. The current composer has no native queue selector; add one without confusing it with controller-scheduled later turns. | +| P1 | Pi `queue_update` contents and delivery state | RPC acknowledgment proves acceptance, not model consumption. Add bounded queued/delivered events and durable message correlation with explicit retention rules for user content; preserve this distinction on reconnect. The selected live marker-order probe proves its own consumed messages only. | +| P1 | Pi structured retry and compaction progress | Current text projection drops some `attempt`, `maxAttempts`, `delayMs`, `errorMessage`, `success`, `finalError`, `reason` and `willRetry` fields. Add bounded redacted provider notices with source-event provenance and outcome regressions; keep terminal failure authority separate. | +| P1 | Native Cursor/Copilot active steering and queues | ACP prompt replacement is not steering; native SDK capabilities may be richer. Require a dedicated bound method plus acknowledgment before advertising. | +| P1 | Child tool media/diff/raw payloads | Bounded delegation summaries preserve lifecycle and identity. Large nested payloads need a child-owned canonical item model; current omission is a visible notice and provider report entry. | +| P1 | Standard ACP parent tool `content` diffs/images, secondary locations and raw input bodies | The existing common normalizer projects bounded output text, input-presence, and the first safe relative location. Raw argument bodies can contain secrets; richer content needs bounded typed blocks and a separately validated workspace binding for each file. Provider-specific image/file notices do not close this standard-tool gap. | +| P1 | User attachments and image prompting | `AcpxRuntimeTurnInput` and the common runtime adapter currently forward text only, despite underlying image-input support. Implement validated attachment-to-ACP content conversion and model-specific capability admission, then qualify real local/Daytona image prompts. This is an implementation gap as well as a live-verification gap. | +| P1 | Copilot session-store files and export/artifact URIs | Provider paths are not task-workspace paths. Add a separately authorized export flow with validated bytes and provenance; do not resolve arbitrary URLs or auto-register. | +| P1 | Pi native fork/history/export interfaces | Pinned Pi 0.84.2 native RPC exposes `fork(entryId)`, `clone`, `get_fork_messages` and `export_html`. The wrapper does not map them to runner controls. Add durable branch lineage for fork/clone and an authorized, contained artifact flow for HTML export before exposing them; do not label these native capabilities absent. | +| P1 | Complete usage/billing provenance | Missing cache fields remain unknown. Pi price estimates are displayed separately. Budget qualification requires actual spend coverage, not an estimate presented as a bill. | +| P1 | Fork/history/model/mode controls not exposed by Paperclip | Research documents the native and ACP methods separately. Add governance-aware controls and durable lineage before enabling them. | +| P1 | Exact pending-request restoration after process death | Session transcript restoration does not restore callbacks. Expire unresolved requests unless a provider proves exact restoration. | +| P1 | Persistent agent-directory access through ACPX | Mainline `3ca196b0a` supplies `AGENT_HOME`, but its existing ACPX environment allowlist does not forward it. Pi also confines native writes to the task workspace, while the local persistent agent directory lives outside it. The disabled candidates do not claim this new capability. Bind and validate the company/agent/run-owned directory explicitly through launch and native-tool policy, including warm-run rebinding and cleanup-before-collection tests; do not widen ambient environment or filesystem access. | +| P2 | Remaining Copilot native diagnostic/config/account events | The provider inventory records every event and field, its projection or reason for omission. Preserve bounded useful context; avoid credentials, raw environment or unbounded blobs. | +| P2 | Pi native extension surfaces and unsupported slash commands | Arbitrary extensions/templates/themes may execute ambient code. Only reviewed runner-owned capabilities are admitted; structured native plan/goals are not fabricated. Native fork/clone/export are separate unmapped capabilities above. | +| P2 | Pi status/widget/title/editor and session/configuration notifications | `setStatus`, `setWidget`, `setTitle` and `set_editor_text` have no UI projection and are unused by the owned extension. Native session-name and thinking-level events also lack a dedicated projection. Add reviewed bounded notice schemas and governed configuration controls before exposing these fields; `notify` and interactive input already have separate bridges. | +| P2 | Pi invocation and history provenance metadata | The v4 wrapper retains `nativeToolCallId`, `modelIteration`, `historyMessageIndex` and `identityScope` in private ACP-wire metadata. Closed common tool and permission projections omit them from the UI. Normalized IDs still correlate live tool, MCP and permission events. Add bounded, redacted display-only provenance fields and parity tests before surfacing the native metadata. | +| P2 | Conditional native-plan follow-up fields | Cursor's optional rejection-reason field also appears for accept/cancel. The current question renderer has no conditional fields; add conditional presentation without changing the revision-bound decision receipt. | +| P2 | Cursor command exit code projection | The paid file case preserved native `exitCode: 0` inside output text, while the canonical command field remained null. Normalize a typed, correlated exit code without parsing arbitrary prose; current independent file assertions do not prove this field. | + +## Qualification ledger + +Combined ceiling: **$100**, including retries and infrastructure. Cursor allocation: +$25; Copilot: $25; Pi: $25; coordinated diagnosis reserve: $25. +Initial reservations are $2 per provider. Measured cumulative OpenRouter key-usage +delta through Pi's native denial, both file attempts, question, three plan attempts and native controls: **$0.028858372**, including failures. +The first model-backed Pi attempt cost **$0.005748807**. Cursor's first account was +not entitled; its dashboard was unchanged at the displayed precision, with no +per-request receipt. Copilot's first session-start failure left its dashboard at +0/1,500 included AI credits and $0 incremental charges. After its protocol and +Product question cases, GitHub displayed 3/1,500 included credits with additional +usage disabled and a $0 budget. It still displayed 3 immediately after the native +denial and detached-command probes, then 5 after the two-turn plan batch. These +are delayed aggregate snapshots; exact allocation among calls is unverified. +After its denial probe, the selected Cursor account displayed 482.5K included +tokens across twelve qualification requests and zero on-demand tokens. The restart +rows showed 23.8K and 41.7K included tokens; denial used 24.4K. Displayed credit precision +and delayed accounting do not establish +a per-run zero receipt. These measurements are +partial, not a final all-provider total. No Daytona leases have been started. +Registry downloads, fake-model fixtures and metadata-only authenticated discovery +are separate from model inference. + +The first Pi canonical `get-task-context` attempt used source `788105248a3ba594b30b5bcec3fa266d8a51d8d4` +and provider-pack digest `sha256:4de47c31a8131424495366741bd491ff5fa10723ab9a3918c07a3e42982dbe3c`. +It observed successful `get_task_context`, `get_task_history`, `list_documents` +and `read_document` calls, then hit its 120-second turn deadline without a +terminal receipt. This is a retained failed attempt, not semantic qualification. +Earlier launch/interpreter failures are retained separately. The initial Cursor +failure exposed a native wrapper gap: a typed entitlement error becomes an +ordinary message and normal completion. The runner does not infer authorization +from that message or fabricate a zero-cost usage receipt. + +The selected paid Cursor account passed canonical `get-task-context` at source +`01959b8a602683f13706807983f02c3cba9d36a0`, pack +`sha256:f0b622e9151c1c0886e6220c48ea71993b65887baa88b1600b27462074748ddb`, +using exact `gpt-5.6-luna[context=272k,reasoning=medium,fast=false]`. +Its context/history reads and all four semantic checks passed in 29.291 seconds. +The account usage row attributes 56K tokens to this run, included in its existing +Pro+ subscription; incremental cash is zero. ACP supplied no token or USD receipt. +A conservative list-price bound of $0.07 is an estimate, not an invoice. + +Copilot's first real completed attempt used source `92fcaf0c`, the same immutable +runnerd SHA-256 `986060810ba7377c6ddd64a5d89e322d0a434e1a9c80400e6d4acf5934bfc421`, +and exact `gpt-5.6-luna`. One context read and all four canonical semantic checks +passed. The original post-run package/provenance failure is retained; offline +scoring recovered the same artifact with zero additional provider calls. Its +24,258 input, 11,781 cached-input and 441 output tokens yield a $0.00326022 catalog +estimate. GitHub still displayed 0/1,500 included credits and additional billing +disabled with a $0 budget after the run. UI delay/rounding leaves the exact credit +delta unverified; this is not a provider USD receipt. Neither protocol case proves +Product E2E, restrictive permissions, restart recovery, or Daytona qualification. + +Separate paid Product E2E evidence now records: + +| Provider / local case | Exact source revision | Observed result | +| --- | --- | --- | +| Cursor / completion | `edf538e61e712dddb6b4d59045c3dcfd445686c7` | 6/6 assertions; committed finalization, one completion marker, cleanup passed | +| Cursor / file edit and validation | `fe132224c2b30a8d9ce7b46cea38b8760af233fc` | 7/7 assertions; independent final file bytes, visible downloadable workspace artifact, cleanup passed | +| Copilot / completion | `bcc9c638a25b91b84065f12633f083bd4f7a689f` | 6/6 assertions and cleanup passed; original accounting projection failed independently | +| Cursor / question continuation | `a7e01a0cec397dd5048f5d5b5825658dc6e91450` | UI answer Cobalt, continuation and 6/6 terminal assertions passed; two expected provider runs, no retry | +| Cursor / semantic plan approval | `a7e01a0cec397dd5048f5d5b5825658dc6e91450` | Displayed plan revision matched the confirmation target; accept/continue and 6/6 terminal assertions passed | +| Copilot / file edit and validation | `ee9536001fbe733b2386dd3379730a4e0be59488` | 7/7 assertions and cleanup passed; independent bytes validated; GitHub biller and unpriced receipt verified | +| Copilot / question continuation | `ee9536001fbe733b2386dd3379730a4e0be59488` | Question/answer and warm session reuse worked; 4/6 terminal assertions passed because the provider returned a literal placeholder instead of the required marker; failed attempt retained | +| Cursor / controller restart | `d35b83074a018537f5475568d7410e3b1d676789` | 6/6 assertions in 57.865 seconds; pending semantic question survived server restart, Cobalt answer continued to Done, cleanup passed; earlier failed attempts retained | +| Pi / completion | `dd78df1ef8b279c30c710c9b7a7f9fda22e321d6` | 6/6 assertions and cleanup passed in 32.668 seconds; one authenticated `paperclip_finish`, `turn.completed` and `run.terminal`; exclusive-key delta $0.000654767 | +| Copilot / semantic plan approval | `c06fc5fccc88f5816450434493451b9d2d339125` | 6/6 assertions in 45.625 seconds; accepted decision bound to displayed revision 1, exact marker and Done; both paused/completed receipts are GitHub/unpriced; cleanup passed | +| Copilot / controller restart | `19ca0f558` (final runtime remains `8aa867b64d5fc2fd62cff110bd000addf5dc54de`) | 6/6 assertions in 50.875 seconds; same pending interaction survived restart and same persisted provider session continued; exact marker once, Done and cleanup passed | +| Pi / file edit and validation | Runtime `dd78df1ef8b279c30c710c9b7a7f9fda22e321d6` | Failed at the 120-second active deadline after five rejected `paperclip_finish` calls; cleanup passed. The extension discarded validation details. Final bytes and completion arguments cannot be reconstructed from the retained projection. | +| Pi / file edit and validation, v3 | Pack source `7710736ca3924c655c5b0efd172cfd3c0173766a`; execution fix `06cf356a8bc94f709fcd15606fe05e17933fe3b2` | 7/7 assertions in 49.427 seconds, exact file bytes and cleanup passed. One rejected completion exposed the missing registered deliverable; Pi corrected it, registered the artifact and finished. Exclusive-key delta $0.003324737. | +| Pi / semantic question, v3 | Pack source `7710736ca3924c655c5b0efd172cfd3c0173766a` | 6/6 assertions in 158.388 seconds across two bounded turns; exact question/option IDs, Cobalt answer, warm continuation, final marker and cleanup passed. First waiting run has no usage receipt; exclusive-key delta covers both runs ($0.001413697). | +| Pi / semantic plan, v3 first attempt | Pack source `7710736ca3924c655c5b0efd172cfd3c0173766a` | Failed the native write-boundary UI assertion in 119.553 seconds. The saved two-step plan, matching revision and confirmation controls were visible, but all retained DOM snapshots showed the fallback Plan card. Rust lost the MCP display name/namespace used for placement. Cleanup passed; exclusive-key delta $0.003090504. Matcher remains unchanged. | +| Pi / semantic plan, v3 second attempt | Combined pack source `7ab463697037c9456a4ad2b83ea0e9c28b353f8a`; daemon source `e35b11db21b8da8527fa0b6c6f5fe186bdced111` | Displayed Plan placement and revision-bound acceptance passed. The resumed turn exceeded the unchanged 120-second active deadline; total case duration 260.246 seconds, cleanup passed. Pi reused native `call_0` for different tool executions, so the bridge correctly rejected conflicting identities. No terminal usage receipt; exclusive-key delta $0.002440082. | +| Pi / semantic plan, v4 first attempt | Frozen combined runtime `f556110d588a9de9fefe676a9a62bf09e98afb85`; fresh pack `sha256:4df7e9fa164929c7ae7d8e8711f0bf7d587d9fa6a246d0a8340f318f57168855` | Infrastructure failure during embedded PostgreSQL initialization in 6.679 seconds; no provider process or prompt. Disposable reproduction confirms host SysV semaphore exhaustion (16 free, 17 required). Cleanup passed, exclusive-key delta $0, and all original graders/deadlines remain unchanged. | + +Pi's immediately preceding `hello05` consumed $0.000351509 and failed evidence +packaging after a confirmed 888-second host Maintenance Sleep. It reached +authenticated prompt acceptance but retained no semantic-tool or terminal event. +An incomplete Playwright archive could not be inspected (`unzip` exit 9), so the +existing scanner failed closed with its `secret_leak` classification; no credential +match was observed. That canonical failure remains unchanged. The deliberate +`hello06` repeat used the same runtime source, a task-owned idle-sleep hold, and +both wall-clock and monotonic outer deadlines. Canonical Pi cost remains unpriced; +the exclusive-key billing delta is separate evidence, not a fabricated receipt. + +Separate real native probes used final immutable packs without a Product database: + +| Provider / probe | Runtime source | Observed result | +| --- | --- | --- | +| Cursor / native AskQuestion | `25fb1b5b317e52a8ad50208d7681a1ee34bd939c` | Failed: zero native input events, normal terminal and cleanup; the provider reported that the tool was unavailable. This does not qualify the native question bridge. | +| Copilot / denied write | `8aa867b64d5fc2fd62cff110bd000addf5dc54de` | Passed: actual native permission request ID 0, original `reject_once`, forbidden marker absent in all 79 observations through process cleanup. | +| Copilot / detached command | `8aa867b64d5fc2fd62cff110bd000addf5dc54de` | Passed: actual `mode:async` and `detach:true`, original `allow_once`; command completion at 10.083 seconds and marker at 10.098 preceded `end_turn` at 10.675; five-second late-effect check and process cleanup passed. | +| Pi / denied write | `dd78df1ef8b279c30c710c9b7a7f9fda22e321d6` | Passed in 20.438 seconds: actual native permission ID 0 persisted before original reject_once reply, pipe delivery acknowledged, correlated tool failed, prompt settled end_turn, forbidden file absent and cleanup passed. | +| Cursor / denied shell write | `25fb1b5b317e52a8ad50208d7681a1ee34bd939c` | Original grader failed because permission omitted rawInput. Separate offline assessment passed from the preceding exact-command tool_call bound to the same toolCallId, original reject-once reply and 98 independent absent-file observations through terminal and cleanup; no repeat prompt. | +| Pi / active steering and queued follow-up, v3 | Pack source `7710736ca3924c655c5b0efd172cfd3c0173766a` | Passed in 21.342 seconds. Both active-session operations acknowledged while a persisted native write permission waited; original reject_once denied the tool, visible STEERED_CURRENT preceded QUEUED_NEXT, forbidden file stayed absent, end_turn settled, stale steer was rejected and cleanup passed. | +| Pi / active steering and queued follow-up, v4 | Frozen combined runtime `f556110d588a9de9fefe676a9a62bf09e98afb85`; verified pack `sha256:4df7e9fa164929c7ae7d8e8711f0bf7d587d9fa6a246d0a8340f318f57168855` | Passed in 20.255 seconds: normalized invocation identity matched the denied tool update; both controls acknowledged before the original denial; exact STEERED_CURRENT then QUEUED_NEXT, stale steering rejected, end_turn and cleanup passed, forbidden file absent. Settled exclusive-key delta $0.000546502; separate Pi estimate $0.000803936. | +| Pi / active steering and queued follow-up, v5 | Runtime `aec26ad83f1d082f0d0a5eaffbd615a9e2e26155`; pack `sha256:cf7d0998bbc2bed7b893c726c2b6455ba8a683d9cff7d92afedbff2d5900d500` | Passed in 19.199 seconds: one real normalized write invocation, original reject_once persisted before reply, both controls acknowledged before denial, exact STEERED_CURRENT then QUEUED_NEXT, stale steering rejected, end_turn, forbidden file absent and cleanup passed. Settled exclusive-key delta $0.000151739; its separate Pi estimate is $0.000611156. | + +These narrow native probes do not replace durable Product interaction, restart, +or Daytona coverage. Their private wire evidence remains separate from sanitized +public summaries. A private durable retention manifest also records hashes for the +logs, screenshots, wire evidence and failed attempts; no credentials are published. The semantic Cursor question and plan cases above exercise +Paperclip tools, not the native `cursor/ask_question` or `cursor/create_plan` RPCs. +The failed Pi file case exposed two wrapper losses. Profile version 3 at +`06cf356a8bc94f709fcd15606fe05e17933fe3b2` preserves bounded, redacted MCP validation +errors and bounded Bash arguments/output; oversized values have an explicit +omission marker. Version 1 and 2 warm snapshots are rejected. Installed-wrapper +regressions pass. A fresh v3 pack then passed the paid file case: the model received +the actual missing-deliverable receipt error, registered its deliverable and +completed. The original v2 failure remains retained. +Canonical command exit codes and typed generic diff/media projection remain partial. + +The Pi denial/file batch cost $0.010995043 by exclusive-key delta. Delayed billing +prevents exact per-call attribution, so the aggregate remains separate from Pi's +catalog estimates and its missing terminal receipt on the file case. + +The Cursor file case proves the workspace artifact surface, not complete native +file/diff projection. The Copilot result incorrectly projected missing native cost +as USD zero and attributed its biller to OpenAI. The original result is retained; +the shared fix identifies GitHub, Cursor and OpenRouter correctly and keeps absent +candidate USD receipts unpriced. The separate Copilot file case verifies that fix. +The question case exposed a second ledger edge: zero normalized token counters +were treated as a reported cost despite no cost field. Ledger classification now +requires an explicit finite nonnegative cost; an explicit zero remains reported. +Protocol evals now fail their cost gate when spend is unknown, preserving completed +behavior and semantic evidence in a separate accounting-failure result. The +maintained campaign stops subsequent cells on unknown accounting and never turns +an unavailable receipt into a zero-dollar measurement. + +Initial Product attempts exposed local PostgreSQL postinstall hydration and a +server candidate-admission gap before any model prompt. Both failed attempts are +retained. The package's own hydration repairs local installation; exact host +qualification now applies consistently at agent creation, runtime selection, +native input and process construction. Agent configuration cannot grant itself +qualification authority. The obsolete unconditional Pi executor rejection is +replaced by the same closed host authorization. Candidate active turns are bounded +to 120 seconds and automatic infrastructure retries remain disabled. + +Pi's next local startup attempt timed out before any prompt, with zero exclusive +key usage delta. A credential-free reproduction isolated a 37.423-second immutable +copy of its 13,827-file, 234,019,683-byte distribution, after 5.168 seconds of +verification, against the 30-second session-open deadline. Bounded parallel copying +reduced that same copy to 5.851 seconds without changing a timeout. Ten tests cover +the eight-file / 32 MiB batch bounds, unchanged per-file integrity checks, stable +digest order, mutation rejection, and draining pending copies before cleanup. +The later `hello06` paid Product case above proves successful inference and +settlement after this startup fix. Its final screenshot shows one readable answer, +a visible Done status and a usable composer, with no duplicate response or error. + +The maintained Product E2E `extended-harnesses` suite covers local and Daytona +completion, question/answer, semantic plan approval, pending-input restart and +file edit/validation. It has no automatic retries and does not enable candidates +outside exact operator-authorized provider/model pairs. The private Runner Eval +campaign is complementary: seven semantic protocol cases per provider. Neither +suite's membership is a qualification claim. + +Before each paid batch, record source SHA, executable and closure/profile digests, +exact model, OS/architecture or Daytona image, selected cases, prior spend, +maximum batch spend and authoritative billing coverage. Stop before the shared +ceiling. Missing spend coverage blocks a run rather than treating unknown cost as +zero. Retain screenshots and wire evidence without credentials. Never convert a +provider to supported solely because a test suite or packaging check passed. + +Verification commands and final results are recorded with the prerequisite and +provider PRs. The full handoff requires runner checks, token gates, recursive +typecheck, `pnpm test:run`, and `pnpm build`. Until that evidence is recorded, this +report is an implementation report rather than a PR-ready certification. + +At foundation `5aeebb20c`, recursive typecheck, build, token gates, full Rust runner +checks, conformance/replay parity and API-authority checks passed. Approval +verification includes 18 real database integration tests, 15 projector cases, +87 transcript/UI cases, 82 route/websocket cases and eight provider receipt cases. +The local full root test attempt initially failed because embedded Postgres's +install-time library links were missing. Its official package postinstall restored +them; all 38 affected suites (743 tests) then passed. The workspace streaming stress test initially exceeded macOS path limits +(`ENAMETOOLONG`); the later portable fixture correction is recorded below. The full runner TypeScript repeat passed 2,164 tests in 156 files, with ten +skipped tests. The full UI and CLI suites passed 6,772 and 502 tests. Remaining +source workspace checks passed 2,882 tests; two macOS path-alias fixture failures +were corrected with an explicit injection assertion (all 89 sandbox tests pass), +and a database timeout passed in an isolated repeat. Failed attempts and the +latest CI state remain recorded in the PR. The decision-media review fix passes +87 focused tests, UI typecheck, token gates and the UI build. + +At execution source `f063fbf2b`, recursive typecheck, full build, token gates, +13 accounting tests, 10 immutable-distribution tests and seven image-contract tests +pass. Both full UI/CLI shard partitions pass (6,848 UI and 502 CLI tests). +The remaining local test partitions run sequentially after paid Product cells to +avoid the machine's observed PostgreSQL semaphore exhaustion. Earlier failures, +including the unchanged macOS path-length stress case, remain retained. Foundation +CI at this source passed typecheck, build, Rust, both runner test lanes, all twelve +general server shards and all eight browser shards. Six other jobs received a +coordinated runner-shutdown signal; their cancellation is not a passing result. +Greptile reviewed this source at 5/5. Later documentation/build-pin updates still +require their own final check status. + +Initial macOS ARM64 candidate packs were independently built and launched through +the generic installation registry: Cursor source `1055c13f8`, Copilot `5d8829add`, +Pi `f58cfa1cb`. Review fixes that change execution bytes require fresh packs and +launch proofs; the latest source SHA, manifest/profile/closure digests and +sanitized wire evidence are retained in each provider PR. Earlier proofs retain +their original source identity. These probes send no model prompt. + +Final native Linux packaging probes also run with no network, no credentials and +no model prompt. Cursor source `25fb1b5b317e52a8ad50208d7681a1ee34bd939c` initialized +from image `sha256:5fa7951d1d6dd99305555fe00a5baf2bf5b834d16f021053737301975b93986f`; +its native EOF does not settle within five seconds, so explicit process-group +cleanup is required and verified. Copilot source +`8aa867b64d5fc2fd62cff110bd000addf5dc54de` initialized as version 1.0.88 from image +`sha256:5457769683fd310223d3b0d4f1ed9a6cf341bdb16514746b3aaeabca2e888fee` and exited +zero on EOF. Its metadata-only fixture received zero requests. The provider PRs +retain complete pack/executable digests and sanitized initialization responses. +Pi v3 source `06cf356a8bc94f709fcd15606fe05e17933fe3b2` also initializes and +exits zero from image `sha256:c5fa7976bba92a186a2f70dc8b8ddc58ab86606b80a819c89bf550d2d057c832`, +with profile digest `sha256:72cb225288376f733b9ed3afa5e13565eb4152f0de509bc1181382fa44bee472`. +The earlier Pi v2 proof remains historical. +These are packaging proofs, not paid Daytona or model-availability evidence. + +At `e8dee462e2fd09cf858be0a02368805346ec4f3b`, versioned profile contract tests +pass 36 cases and full runner TypeScript checking passes. Nine additional source +workspace suites pass 2,757 tests with 19 skips. An isolated repeat of the unchanged +HTTP redaction suite passes all 59 tests after its earlier concurrent-run timeout. +The feedback-route mock fix retains both company-boundary assertions and passes +two independent fresh-environment repeats (4/4 each). Current source and any +remaining partition failures are recorded in the final prerequisite PR evidence. + +The portable real-Git stress fixture passes on macOS in 234.43 seconds, retaining +40,000 files, four independently checked 34,988,890-byte filename lanes, real +staging/deletion and cleanup. Its timeout is 300 seconds on macOS and remains +180 seconds elsewhere. Both earlier failures are retained. All seven remaining +serialized root partitions pass; database-project and previously skipped-file +remediation later passed all ten partitions, including 1,042 chat integration tests. + +The combined provider stack passes 616 ACP/profile/backend tests with eight +skips, 34 packaging and actual-wrapper tests, and a TypeScript build. The +Copilot registry regression now verifies dispatch to separate native factories; +the pack test no longer assumes another implemented provider is absent. +The complete database project passes 161 tests in 44 files after serialization; +all ten remediation partitions completed without failure, including the full +1,042-test chat integration lane. + +The first Pi plan attempt exposed a shared Rust display-mapping gap: prefixed MCP +tool names were retained as builtin names, preventing the saved Plan from anchoring +to its write event. All four retained browser snapshots showed the fallback Plan +card inside a settled turn, so the existing Product matcher remains unchanged. +Foundation `5aa02662b34fc70f5fe9ffd5ac8f7ae81b5fe3bd` parses both ACP MCP name forms, +preserves bounded/redacted namespace and name, and leaves semantic authorization, +operation and read-only classification unchanged. The original Rust regression +fails before the fix; 55 Rust, 28 TypeScript provider and 158 UI tests pass afterward. +The UI regression distinguishes the proper write-boundary card from the original +fallback. A rebuilt daemon is required for the retained paid retry. + +The rebuilt daemon passed the Plan placement and revision-acceptance boundary in +the second paid attempt. Its continuation exposed a separate Pi identity defect: +the owned extension reused native `call_0` as the MCP request ID for different +executions. A rejected finish was followed by a corrected finish and context read, +both correctly rejected by shared duplicate-call protection. Further paid cases +stopped. Pi v4 at `f556110d588a9de9fefe676a9a62bf09e98afb85` binds live identities +to a private launch namespace and a lifetime model-iteration ordinal. Exact +request retries retain their identity; conflicting or duplicate native invocations +within one iteration fail closed. History uses a separate display-only identity. +Bridge idempotence and durable call tombstones stay intact. Raw native IDs remain +private-wire provenance, with the UI omission recorded above. + +The corrective-call regression failed before the fix and passes against the real +authenticated loopback tool bridge afterward. The pinned native SDK test covers +six iterations across two warm prompts and four executions that reuse `call_0`; +its native turn index resets, while the owned ordinal remains monotonic. This +check uses an in-memory model stream and no inference. Independent review and +profile-digest verification pass. At that v4 checkpoint, the profile digest was: +`sha256:2324d9b47650c12b16f8e2c44dc33637d52f1b22ba8e914623eac4049e7e1991`. +Earlier v1-v3 snapshots could not establish that runtime identity. The extended +Runner Eval configuration then pinned v4 and rejected those historical identities; +all 132 eval tests and seven-cell Pi campaign validation passed without provider +calls at that checkpoint. The later v5 identity is recorded below. + +Recursive typecheck and the full build both pass at shared repair source +`5aa02662b34fc70f5fe9ffd5ac8f7ae81b5fe3bd` and again at version-admission source +`1545c7692`. The latter adds closed profile-version-4 admission, with 22 contract +tests and full runner TypeScript checks passing. Exact profile digest matching +remains mandatory. Recursive typecheck and the full repository build also pass +on the frozen combined provider source `f556110d588a9de9fefe676a9a62bf09e98afb85`, +including Pi v4. The combined macOS ARM64 provider pack +at `7ab463697037c9456a4ad2b83ea0e9c28b353f8a` independently verifies all three +complete candidate trees (446 Cursor files, one Copilot executable and 13,827 Pi +files), manifest digest +`sha256:0800f10114e3d8e2e981ea27f0ab47f1da9349dbcc22bf55a76ebbc4af00601d`. +All three initialize through the verified registry without credentials or model +prompts. The combined Linux image at source +`e35b11db21b8da8527fa0b6c6f5fe186bdced111` also passes all three initialization +probes under network-none and a read-only root. Image digest: +`sha256:17c228d3b9744d6bf375ead57918c755d0cc44fc3db44d238cc898317cea25ce`; +pack digest: `sha256:4e0ddb39a34e7f55d7520a8a2d9074009589f3f95f4c10bb837e54ec58d70ba1`. +These combined proofs contain Pi v3 and retain that historical identity when the +invocation-identity repair produces a new profile. They are not paid Daytona proof. +All candidates remain pending; verification and packaging do not grant support. + +The fresh v4 macOS ARM64 pack at the frozen combined source has digest +`sha256:4df7e9fa164929c7ae7d8e8711f0bf7d587d9fa6a246d0a8340f318f57168855`. +All 13,827 Pi runtime files independently match the pinned closure. Native +initialization under denied networking exits cleanly; the complete Runner path +returns the expected typed missing-key rejection without inference. The rebuilt +daemon has SHA-256 `373848d2d7287b2c47b2cee422cb7bd25073a594a620a9ad574f2d3d51cd1670`. +The next canonical paid plan attempt stopped in PostgreSQL initialization before +provider startup. Its unchanged billing and exact disposable initdb failure are +retained. The diagnostic created no server, removed its temporary directory, and +left IPC totals unchanged. Unrelated database processes and IPC objects were not +modified. Canonical Product E2E does not currently admit an external database +substitute; introducing one requires its own ownership, isolation and cleanup +proof rather than bypassing the existing guard. + +The v4 native probe above does not need a database and passed independently of +the host PostgreSQL blocker. A read-only IPC attribution audit found no semaphore +set with sufficient task provenance for safe cleanup; no unrelated set was +removed. The paid Product and Daytona qualification gaps remain open. + +Dependency review subsequently identified Pi's pinned `undici@8.9.0` in +[GHSA-3wwx-pv8p-q78v](https://github.com/advisories/GHSA-3wwx-pv8p-q78v), +indexed in GitHub's advisory database on September 28. Pi's owned Node 24.19.0 +also embeds affected Undici 7.29.0. The repair requires both the exact npm override +and the provider-local patched Node distribution; it cannot be represented by a +waived check or a claim that updating npm replaces Node's builtin implementation. +Shared contract source `e6163e16a` admits closed profile version 5, with 23 contract +cases and full runner TypeScript checking passing; unknown versions remain +rejected and exact command digests remain mandatory. + +The combined v4 Linux image at frozen source `f556110d588a9de9fefe676a9a62bf09e98afb85` +was built and all three providers passed credential-free initialization under +network-none with a read-only root. Image digest: +`sha256:4c4e2fb7eb8ef3681b14c3e21dd28eace5ef920d6d08d572fb9c9c7e9a5f290a`; +pack digest: `sha256:8f263ed5c309b47cdc74a37d8217bb49e57129fca393498f54f6dd461657a9b9`. +The daemon SHA-256 is `5914ed1ad0235aaee9af8731af2cbf28d7b16b0fda231a13963f1d86c3220daa`. +Cursor still needs explicit cleanup after EOF; Copilot and Pi exit zero. No model +prompt or credential was supplied. This remains a historical packaging proof +when the dependency repair changes Pi's profile and executable closure. + +Recursive `pnpm -r typecheck` and `pnpm build` also pass at shared version-5 +admission source `e6163e16a`; no database or model request is used by those checks. + +Pi v5 repair source `aec26ad83f1d082f0d0a5eaffbd615a9e2e26155` pins private +Node 24.21.0 (bundled Undici 7.29.1) and nested npm Undici 8.10.2. Because npm +retains the vulnerable shrinkwrapped package despite overrides, materialization +verifies the exact published old tuple, replaces only that package from a bounded, +integrity-checked fixed archive, and then verifies the complete trusted closure. +The upstream Pi package and shrinkwrap bytes remain unchanged; the exception is +explicit and closed. Profile digest: +`sha256:020d96ccbd3c45c3f62680814776394ed5a56d9572a1a4dccda56a74d16c7803`. +Old versions 1–4 are rejected. Forty-three focused TypeScript tests, 19 package +checks, eight materializer checks and 13 Rust tests pass. Independent review verified all 13,827 installed +files, both patched dependency copies, eight materializer and twelve profile/ +recovery cases. Recursive typecheck and the full build pass at the same source. + +Shared Docker pin source `3a5637732` uses the official Linux x64 +`node:24.21.0-bookworm@sha256:5a750d3be5e5c80275f8c9a5367c3aed99c2875656590c8d0701c7ee687f5f0a` +for the outer provider pack. A network-disabled launch independently reports +Node 24.21.0 and Undici 7.29.1, matching the +[official Node release](https://nodejs.org/en/blog/release/v24.21.0). All seven +image-contract tests pass. The first local test invocation failed because the +root has no `tsx` executable; the retained retry used the maintained Vitest +configuration. No provider call was made by either command. Pack authority +includes the outer Node bytes and is bound into the persisted launch profile; +changes invalidate incompatible warm recovery independently of Pi's profile bump. + +The fresh v5 macOS ARM64 combined pack has manifest digest +`sha256:cf7d0998bbc2bed7b893c726c2b6455ba8a683d9cff7d92afedbff2d5900d500` +and source `aec26ad83f1d082f0d0a5eaffbd615a9e2e26155`. Independent initialization +through the verified registry passes for Cursor, Copilot and Pi with no provider +credentials or prompts. The outer interpreter reports Node 24.21.0 and Undici +7.29.1. Cursor still requires explicit process-group cleanup after EOF; Copilot +and Pi exit zero. All owned processes and private supervisor directories are +cleaned. Cursor/Copilot profile and closure identities match their prior pack. +The proof index has SHA-256 +`a07394e3215eb0a153c4a9443762a5d84ddfe0e70b92e1d47ae8d574239e104c`. + +The private Runner Eval campaign now pins v5 at eval source +`18f06b9391838d637c1588f372c7f1397ec6f9a7`. All 133 Python tests and the seven-cell +Pi validation pass without provider calls. Explicit tests reject each historical +v1–v4 version, digest and paired identity. Cases, model, scoring, budgets and +deadlines remain unchanged. + +Current Linux image identities and exact-head CI/review status are recorded in +the [Pi PR verification](https://github.com/paperclipai/paperclip/pull/14436) and +[foundation PR verification](https://github.com/paperclipai/paperclip/pull/14430). +Their immutable source and dependency identities remain distinct from the +historical packaging proofs above. Image initialization does not certify paid +Daytona execution. The unresolved local database and remote account-verification +blockers prevent support promotion. diff --git a/doc/evals.md b/doc/evals.md index 40d298b5db..061918c608 100644 --- a/doc/evals.md +++ b/doc/evals.md @@ -284,3 +284,9 @@ downloaded proof on local native/legacy Codex and native Daytona. See the The explicit-only Product E2E `api-response-reading` suite verifies retrieval of large saved API responses on local and Daytona native Codex runs. See the [Runner E2E guide](../tests/runner-e2e/README.md#bounded-api-response-reading). + +The explicit-only Product E2E `extended-harnesses` suite covers pending Cursor, +Copilot and Pi ACP profiles on local and Daytona. See the +[fixture admission, credentials and budget contract](../tests/runner-e2e/README.md#extended-acp-harnesses-explicit-only). +The private Runner Evals campaign of the same name provides complementary +semantic protocol cases; catalog membership is not live qualification. diff --git a/doc/plans/2026-09-28-runner-rich-acp.md b/doc/plans/2026-09-28-runner-rich-acp.md new file mode 100644 index 0000000000..994afef0d3 --- /dev/null +++ b/doc/plans/2026-09-28-runner-rich-acp.md @@ -0,0 +1,30 @@ +# Cursor, Copilot and Pi through rich ACP + +Approved implementation plan, 2026-09-28. Shared base: +`c65fc9e3c81c41aafe421aa90a00514b84343285`. + +Keep the existing ACPX runner transport and Codex app-server richness benchmark. +Implement a foundation prerequisite, then three separate provider branches and +PRs. Do not merge automatically. Preserve legacy Cursor/Pi adapters. + +1. Investigate each pinned harness, its native API, ACP adapter, observed wire, + runner normalization and UI surface. Inventory every meaningful event field. +2. Extend shared profile declarations, durable permissions/input, extension + dispatch, steering/queue distinctions, artifact provenance and bounded display + activity. Preserve existing replay and exact warm-session identities. +3. Pin complete provider distributions for macOS ARM64/x64 and Linux x64. Use + verified launches, isolated credentials/configuration and build-time assets. +4. Implement Cursor native ACP and its question/plan/subagent extensions; + Copilot ACP authentication/events/permission and settlement handling; Pi + wrapper patch and owned extension with authenticated tools and input policy. +5. Run deterministic, recovery, isolation and richness conformance. Qualify each + provider locally and in Daytona with product E2E evidence before advertising + support. Use a combined $100 ceiling: $25 each plus $25 diagnosis reserve. +6. Run required full checks, prepare one foundation and three dependent PRs, + retain evidence and deliver the comparative capability report. List every + unused/partial capability with reason and prioritized follow-up. + +Implementation and qualification status is maintained in +[the capability report](../architecture/runner-rich-acp-capabilities.md). +Missing credentials or spend verification leave qualification pending; they do +not authorize a weaker supported profile or an implicit model fallback. diff --git a/docker/daytona-runner/Dockerfile b/docker/daytona-runner/Dockerfile index 75e33e9033..5a1da31b1f 100644 --- a/docker/daytona-runner/Dockerfile +++ b/docker/daytona-runner/Dockerfile @@ -9,10 +9,10 @@ WORKDIR /workspace/packages/paperclip-runner/runner COPY packages/paperclip-runner/runner/Cargo.toml packages/paperclip-runner/runner/Cargo.lock ./ COPY packages/paperclip-runner/runner/crates ./crates COPY packages/paperclip-runner/protocol ../protocol -RUN cargo build --locked --release -p paperclip-runner-core --bin paperclip-runnerd \ +RUN cargo build --locked --release -j 2 -p paperclip-runner-core --bin paperclip-runnerd \ && strip /workspace/packages/paperclip-runner/runner/target/release/paperclip-runnerd -FROM node:24-bookworm@sha256:9137a20e25879e0b557227b57e3ee4e9af4bde29eb3db66134cd1723e84f830b AS provider-pack-build +FROM node:24.21.0-bookworm@sha256:5a750d3be5e5c80275f8c9a5367c3aed99c2875656590c8d0701c7ee687f5f0a AS provider-pack-build RUN corepack enable && corepack prepare pnpm@9.15.4 --activate WORKDIR /workspace COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc tsconfig.base.json ./ @@ -28,15 +28,18 @@ COPY cli/package.json ./cli/package.json # The complete resolved lock (including transitive integrity hashes) is reviewed. # Reject registry-time drift BEFORE installing packages or running lifecycle code. # Refresh this digest together with source/provider dependency changes. -ARG PAPERCLIP_RUNNER_LOCK_SHA256=0c06ab38c1d4b6b58e1c9f5ee85eb79f97cecd83e751b5d27c3b262faf5ab265 +ARG PAPERCLIP_RUNNER_LOCK_SHA256=2c46a68811ba1d504a41b6f4d3f642bc93f4a1c615097754c9c6486881dba8e6 RUN printf '%s pnpm-lock.yaml\n' "${PAPERCLIP_RUNNER_LOCK_SHA256}" > /tmp/provider-lock.sha256 \ && sha256sum -c /tmp/provider-lock.sha256 \ && pnpm install --frozen-lockfile --filter '@paperclipai/paperclip-runner...' ARG PAPERCLIP_RUNNER_SOURCE_REVISION +# Optional qualification assets; this never promotes candidate profiles. +ARG PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS= RUN test -n "${PAPERCLIP_RUNNER_SOURCE_REVISION}" RUN pnpm --filter @paperclipai/paperclip-runner build:typescript \ && PAPERCLIP_RUNNER_SOURCE_REVISION="${PAPERCLIP_RUNNER_SOURCE_REVISION}" \ node packages/paperclip-runner/scripts/build-provider-pack.mjs /provider-pack \ + "--candidate-providers=${PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS}" \ && chmod -R a+rX /provider-pack # Fleet sandbox base image. Keep this section aligned with diff --git a/docker/daytona-runner/README.md b/docker/daytona-runner/README.md index 54b8911a67..cd54962638 100644 --- a/docker/daytona-runner/README.md +++ b/docker/daytona-runner/README.md @@ -94,6 +94,12 @@ full Git SHA as `PAPERCLIP_RUNNER_SOURCE_REVISION`. Do not bake provider credentials, Paperclip bootstrap tickets, or Daytona preview tokens into this image. They remain per-run secret material. +The provider-pack build pins the official Linux x64 Node 24.21.0 image by +manifest digest. Its bundled Undici is 7.29.1, which fixes +[GHSA-3wwx-pv8p-q78v](https://github.com/advisories/GHSA-3wwx-pv8p-q78v). +Pi separately verifies its private Node executable and nested npm dependency; +changing the outer interpreter does not replace either provider-owned pin. + Provider CLI updates are manifest-only changes: repository CI owns the root lockfile. Resolve the complete workspace manifest graph in the build context before invoking Docker, matching CI when a source commit precedes the lockfile @@ -106,6 +112,37 @@ verifies the downloaded artifact, then passes that artifact's SHA-256 as the `PAPERCLIP_RUNNER_LOCK_SHA256` build argument. The Dockerfile checks the resolved lock against this value before installation. The fixed Dockerfile default is for standalone builds; it must not replace a campaign's verified lock digest. +Refresh the default from the clean tracked lockfile using the exact +`pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile` command, +and verify a second resolution preserves the digest. A lockfile left by a +filtered or incremental install can retain stale importer patch identities. +Refresh exact runtime versions and qualification digests together; do not +download dependencies when a task starts. + +## Candidate ACP qualification assets + +Provider branches can build their pinned assets with +`node packages/paperclip-runner/scripts/build-provider-pack.mjs /absolute/pack --candidate-providers=cursor` +(or `copilot` or `pi`). The source revision must include the named provider's +builder. Assets are installed at build time under `provider-assets//-`. +The pack manifest binds each complete asset tree. Runtime admission separately +checks the provider's source-owned closure pins and copies a verified launch snapshot. +A pack with candidate assets does not qualify or enable that provider. + +For an isolated Linux x64 Daytona qualification image, pass +`--build-arg PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS=cursor` with the normal build arguments. +Compute its content ID with the same selection: +`pnpm --silent test:e2e:runner:image-id --candidate-providers=cursor`. +Candidate assets and the default pack have distinct image identities. Never reuse +the default image's content ID for a candidate build. +Use each provider branch's recorded version and explicit model. Keep credentials +out of images. Paid qualification requires bound provider and Daytona credentials, +inspectable spend, and the shared $100 ceiling recorded in the capability report. + +The pack builder tests its copied Node interpreter after relocation. Use a +standalone Node distribution if the host interpreter depends on a package manager's +private shared libraries. Pi additionally pins its complete portable interpreter +and npm dependency graph. Refresh exact runtime versions and qualification digests together; never download dependencies when a task starts. Grok's additive native ACP profile keeps its qualified 1.0.13 executable at the verified sandbox prerequisite path. It does not replace the diff --git a/packages/adapter-utils/src/index.ts b/packages/adapter-utils/src/index.ts index 653c07e936..eb02fb4efc 100644 --- a/packages/adapter-utils/src/index.ts +++ b/packages/adapter-utils/src/index.ts @@ -117,6 +117,7 @@ export { PAPERCLIP_RUNNER_IDLE_TIMEOUT_MAX_MS, PAPERCLIP_RUNNER_DEFAULT_MODELS, PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES, + PAPERCLIP_RUNNER_ACPX_PROFILES, isPaperclipRunnerProvider, resolvePaperclipRunnerIdleTimeoutMs, resolvePaperclipRunnerModel, diff --git a/packages/adapter-utils/src/paperclip-runner-permissions.ts b/packages/adapter-utils/src/paperclip-runner-permissions.ts index ed9b2dee71..cb32c0fd78 100644 --- a/packages/adapter-utils/src/paperclip-runner-permissions.ts +++ b/packages/adapter-utils/src/paperclip-runner-permissions.ts @@ -125,7 +125,7 @@ export const PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES = { value: "approve-reads", label: "Allow Paperclip reads", description: - "Automatically allow assigned Paperclip read tools. Other operations stop with an approval-required message because this runner has no interactive approval handler.", + "Automatically allow assigned Paperclip read tools. Other operations request a supported permission decision. Company permissions and execution boundaries still apply.", }, { value: "deny-all", @@ -223,3 +223,12 @@ export function normalizeLegacyRunnerProvider( } = config; return { ...rest, provider: "codex", codexPermissionMode: "never" }; } + +/** Qualification is a release property, never an operator-configurable bypass. */ +export const PAPERCLIP_RUNNER_ACPX_PROFILES = Object.freeze([ + { value: "grok", label: "Grok Build", qualified: true, credentialEnvironment: ["XAI_API_KEY"] }, + { value: "claude", label: "Claude", qualified: true, credentialEnvironment: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"] }, + { value: "cursor", label: "Cursor", qualified: false, credentialEnvironment: ["CURSOR_API_KEY", "CURSOR_AUTH_TOKEN"] }, + { value: "copilot", label: "GitHub Copilot", qualified: false, credentialEnvironment: ["COPILOT_GITHUB_TOKEN"] }, + { value: "pi", label: "Pi", qualified: false, credentialEnvironment: ["OPENROUTER_API_KEY"] }, +] as const); diff --git a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts index 58dfd1c960..88144df3df 100644 --- a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts +++ b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts @@ -1212,7 +1212,9 @@ describe("sandbox managed runtime", () => { }); it.each(["symlink", "root_symlink", "root_alias", "root_alias_retarget", "case_alias", "EACCES", "EIO", "ENOENT"])("handles an overlay source changed after the snapshot: %s", async (change) => { - const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-overlay-source-")); + // The runtime pins a realpath root before staging. Match that spelling so + // injected I/O errors also reach the selected file on macOS's /var alias. + const rootDir = await fsPromises.realpath(await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-overlay-source-"))); cleanupDirs.push(rootDir); const workspaceLocalDir = path.join(rootDir, "workspace"); await initGitRepo(workspaceLocalDir); @@ -1301,6 +1303,7 @@ describe("sandbox managed runtime", () => { expect(syncIn).toHaveBeenCalledOnce(); } else { await expect(preparing).rejects.toThrow(change.endsWith("symlink") ? /overlay.*directory/i : failure.message); + if (statSpy) expect(statSpy).toHaveBeenCalledWith(selectedPath); expect(syncIn).not.toHaveBeenCalled(); } } finally { diff --git a/packages/paperclip-runner/README.md b/packages/paperclip-runner/README.md index a98643fad4..27913b20dd 100644 --- a/packages/paperclip-runner/README.md +++ b/packages/paperclip-runner/README.md @@ -54,7 +54,11 @@ leave the board showing an active run after the provider turn has already ended. The package also builds `paperclip-runner-acpx-sidecar`. This bounded v2 stdin/stdout bridge admits the pinned Claude and Codex ACPX profiles. It validates the exact model, session identity, tool catalog, structured input, -and terminal settlement at the process boundary. Pi remains unavailable. +and terminal settlement at the process boundary. Cursor, Copilot and Pi have +separate candidate branches and remain unavailable in production until local +and Daytona qualification passes. Their verified distributions are build-owned; +no candidate accepts an arbitrary executable. See +[the rich ACP capability report](../../doc/architecture/runner-rich-acp-capabilities.md). Remote Codex sessions relay assigned app tools through the server's configured gateway. Small catalogs are sent directly. When a catalog would exceed the @@ -96,19 +100,22 @@ do not change workspace isolation or grant credentials or connection access. task tools; `approve-reads` allows assigned reads; `deny-all` rejects requests. None of these restrictive modes is the default. +Restrictive profiles route supported permission decisions through durable runtime +requests and the existing task interaction controls. Requests are persisted +before presentation; answers are checked against the offered decisions and +acknowledged by the sidecar before settlement. Unknown, stale and duplicate +responses fail. Missing provider decision support remains a blocked disposition, +not implicit approval. Company access checks still run for each Paperclip tool. +Provider death expires pending promises; approvals are never replayed into a +replacement process. + Automatic Paperclip/read allowances currently require the Claude SDK dispatch boundary. Grok preserves these restricted settings, but its ACP requests lack -independently bound tool authority. They therefore stop with -`approval_required`, including Paperclip tool requests. Use an explicitly -selected `approve-all` policy for unattended Grok work in an assigned sandbox; -Paperclip authorization and governed approvals still apply. - -This runtime has no interactive permission handler. An operation that still -requires approval stops the turn with `approval_required`. The server marks the -task blocked, exposes the permission action to the operator, and disables -automatic retry. The operator must review the operation and the agent's -permission setting before retrying. Company access checks still run when each -Paperclip tool executes. +independently bound tool authority. Those operations require a supported operator +permission decision; a missing interactive responder stops with +`approval_required`. An explicitly selected `approve-all` policy permits unattended +Grok work in an assigned sandbox. Paperclip authorization and governed approvals +still apply. Runnerd selects only qualified provider profiles. Claude Managed and AWS AgentCore receive immutable company-profile snapshots with explicit retention, @@ -285,7 +292,11 @@ pnpm --filter @paperclipai/paperclip-runner report:runner-chaos-evals `report:runner-live-evals` is a paid, provider-backed command. Native Codex requires `OPENAI_API_KEY`; ACPX Claude requires `ANTHROPIC_API_KEY`; OpenCode candidates require `OPENROUTER_API_KEY`. The live -matrix admits no Pi profile and does not persist credential values. Set +matrix remains qualified-only and does not persist credential values. Candidate +qualification uses `eval-session --candidate-profile ` with an +explicit model and a separately materialized pinned candidate pack. This option +is a constructor-bound diagnostic opt-in; session JSON cannot enable a candidate. +Missing credentials or unverifiable spend block paid qualification. Set `PAPERCLIP_EVAL_MAX_CAMPAIGN_COST_USD` to a positive finite number to bound additional scheduling after the observed campaign total reaches that value: diff --git a/packages/paperclip-runner/devtools/issue-thread/src/ThreadItems.tsx b/packages/paperclip-runner/devtools/issue-thread/src/ThreadItems.tsx index 717f6936dd..8a8079413e 100644 --- a/packages/paperclip-runner/devtools/issue-thread/src/ThreadItems.tsx +++ b/packages/paperclip-runner/devtools/issue-thread/src/ThreadItems.tsx @@ -172,6 +172,7 @@ function ProviderActivity({ item, callbacks }: { {children.length > 0 ?
    {children.map((child, index) =>
  • {String(child.role ?? "Child agent")}{String(child.status ?? "unknown")}{String(child.summary ?? "")}
  • )}
: null} {sources.length > 0 ?
    {sources.map((source, index) => { const url = typeof source.url === "string" && /^https?:\/\//.test(source.url) ? source.url : null; return
  • {url === null ? {String(source.title ?? "Unavailable source")} : {String(source.title ?? url)}}Provider-reported
  • ; })}
: null} {output.length > 0 ?
{output}
: null} + {item.family === "provider_notice" && Array.isArray(payload.details) ?
{payload.details.slice(0, 64).map((value, index) => { const detail = valueRecord(value); return
{String(detail.name ?? "Detail")}
{String(detail.value ?? "")}
; })}
: null} {item.family === "model_identity" ?
Requested
{String(payload.requestedModel ?? "—")}
Effective
{String(payload.effectiveModel ?? "—")}
: null} {item.family === "artifact" && typeof payload.reference === "string" ? : null} diff --git a/packages/paperclip-runner/package.json b/packages/paperclip-runner/package.json index ebc0614e39..596b74ad88 100644 --- a/packages/paperclip-runner/package.json +++ b/packages/paperclip-runner/package.json @@ -74,7 +74,7 @@ "typecheck:browser": "tsc -p tsconfig.browser.json --noEmit", "test": "pnpm run test:typescript && pnpm run test:rust", "test:typescript": "pnpm run test:typescript:prep && vitest run", - "test:typescript:prep": "pnpm run ensure:eval-build-deps && pnpm run build:rust && node --test test/protocol-contract.test.mjs test/acpx-sidecar-contract.test.mjs test/acpx-codex-package-contract.test.mjs scripts/aws-agentcore-provisioning.test.mjs scripts/build-verified-provider-entrypoints.test.mjs scripts/local-provider-smoke-environment.test.mjs scripts/materialize-opencode-binary.test.mjs", + "test:typescript:prep": "pnpm run ensure:eval-build-deps && pnpm run build:rust && node --test test/protocol-contract.test.mjs test/acpx-sidecar-contract.test.mjs test/acpx-codex-package-contract.test.mjs test/acpx-rich-extensions-package-contract.test.mjs test/acpx-pi-receipt-package-contract.test.mjs test/acpx-response-delivery-package-contract.test.mjs scripts/candidate-provider-pack.test.mjs scripts/aws-agentcore-provisioning.test.mjs scripts/build-verified-provider-entrypoints.test.mjs scripts/local-provider-smoke-environment.test.mjs scripts/materialize-opencode-binary.test.mjs", "test:typescript:vitest": "node ./scripts/run-pr-vitest-lane.mjs", "test:rust": "cargo test --release --manifest-path runner/Cargo.toml --locked --workspace", "test:codex": "cargo test --manifest-path runner/Cargo.toml --locked -p paperclip-runner-core --test codex_provider", diff --git a/packages/paperclip-runner/protocol/manifest.json b/packages/paperclip-runner/protocol/manifest.json index ba8d17e452..919a0d38f8 100644 --- a/packages/paperclip-runner/protocol/manifest.json +++ b/packages/paperclip-runner/protocol/manifest.json @@ -40,7 +40,7 @@ { "path": "schemas/event-v2.schema.json", "id": "https://paperclip.dev/schemas/prp/v2/event.schema.json", - "sha256": "d7528ee2a46ad20de831fd4ee4a0a51e8325a9855fed5a9b8c428dcebc39cc11" + "sha256": "9dcf149bdb8e10041ca2212037231e7ceff11be08a0b8ee5dbd0f21974daa294" }, { "path": "schemas/event.schema.json", @@ -65,12 +65,12 @@ { "path": "schemas/provider-descriptor.schema.json", "id": "https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json", - "sha256": "a56c0c8501171c34bb72f6fba08d3872839a8b45bbc10daa93620afc9dc2b669" + "sha256": "f5badf70748d6a761a3487f001ce8282582b161cb5b80fabb6bb5f683d28c512" }, { "path": "schemas/provider-event.schema.json", "id": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json", - "sha256": "0b138a41bd3bd0b1a115f5536a2d7349537e79e36ede3b51d003f9f21ce73b50" + "sha256": "14143d53f28969fa3f940def7331a45ee0eed560dd343506ac41b6a3dfe59985" }, { "path": "schemas/question-adapter-fixture.schema.json", @@ -85,12 +85,12 @@ { "path": "schemas/question-set.schema.json", "id": "https://paperclip.dev/schemas/prp/v1/question-set.schema.json", - "sha256": "42b5441a3d388851dacb6e4500dfd4a17d878eded2e724228078b647e7440d3f" + "sha256": "6aeb2b880a60356cb48acfe26605acd9412a1df65f5308397d3ec521db5ea70f" }, { "path": "schemas/request.schema.json", "id": "https://paperclip.dev/schemas/prp/v1/request.schema.json", - "sha256": "c8d00e10150c90963f19641c075f50da7f67617dd817f41b356287ad85c53f2b" + "sha256": "985a3b0a6ae36ae5ad4cf7d708dc401db7c108f683a81cd44a03093fc4ca7862" }, { "path": "schemas/result.schema.json", diff --git a/packages/paperclip-runner/protocol/provider-schemas/acpx-sidecar.schema.json b/packages/paperclip-runner/protocol/provider-schemas/acpx-sidecar.schema.json index b4b286f4f0..1d285c52ad 100644 --- a/packages/paperclip-runner/protocol/provider-schemas/acpx-sidecar.schema.json +++ b/packages/paperclip-runner/protocol/provider-schemas/acpx-sidecar.schema.json @@ -16,6 +16,7 @@ "run.attach", "turn.start", "turn.cancel", + "turn.steer", "permission.resolve", "input.resolve", "tool.resolve", @@ -31,6 +32,7 @@ "eventType": { "enum": [ "runtime.event", + "runtime.rich_event", "runtime.permission_requested", "runtime.input_requested", "runtime.tool_called", @@ -49,7 +51,11 @@ "command": { "$ref": "#/$defs/command" }, "params": { "type": "object" } }, - "additionalProperties": false + "additionalProperties": false, + "allOf": [{ + "if": { "properties": { "command": { "const": "turn.steer" } } }, + "then": { "properties": { "params": { "$ref": "#/$defs/turnControl" } } } + }] }, "response": { "type": "object", @@ -108,6 +114,102 @@ "turnId": { "type": ["string", "null"], "maxLength": 240, "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$" }, "payload": { "type": "object" } }, + "additionalProperties": false, + "allOf": [ + { + "if": { "properties": { "eventType": { "const": "runtime.rich_event" } } }, + "then": { + "properties": { + "runId": { "type": "string" }, + "turnId": { "type": "string" }, + "payload": { "$ref": "#/$defs/richEvent" } + } + } + } + ] + }, + "richEvent": { + "type": "object", + "required": [ + "eventType", + "itemId", + "payload" + ], + "properties": { + "eventType": { + "enum": [ + "plan.updated", + "tool.execution.started", + "tool.execution.progressed", + "tool.execution.completed", + "research.started", + "research.progressed", + "research.completed", + "delegation.started", + "delegation.updated", + "delegation.completed", + "model.route.changed", + "model.verification.updated", + "context.compacted", + "artifact.viewed", + "artifact.generated", + "review.mode.changed", + "hook.started", + "hook.completed", + "memory.citation.referenced", + "safety.review.started", + "safety.review.completed", + "terminal.input.sent", + "wait.started", + "wait.completed", + "provider.notice.recorded" + ] + }, + "itemId": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$" + }, + "payload": { + "type": "object" + } + }, + "additionalProperties": false + }, + "turnControl": { + "type": "object", + "required": [ + "turnId", + "controlId", + "mode", + "message" + ], + "properties": { + "turnId": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$" + }, + "controlId": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$" + }, + "mode": { + "enum": [ + "steer", + "follow_up" + ] + }, + "message": { + "type": "string", + "minLength": 1, + "maxLength": 65536 + } + }, "additionalProperties": false } } diff --git a/packages/paperclip-runner/protocol/schemas/event-v2.schema.json b/packages/paperclip-runner/protocol/schemas/event-v2.schema.json index 8cfc4558de..0b1ba05ce1 100644 --- a/packages/paperclip-runner/protocol/schemas/event-v2.schema.json +++ b/packages/paperclip-runner/protocol/schemas/event-v2.schema.json @@ -74,7 +74,8 @@ "type": "object", "required": ["sessionGoals"], "properties": { - "sessionGoals": { "$ref": "https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability" } + "sessionGoals": { "$ref": "https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability" }, + "turnControls": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json#/$defs/turnControls" } }, "additionalProperties": true } diff --git a/packages/paperclip-runner/protocol/schemas/provider-descriptor.schema.json b/packages/paperclip-runner/protocol/schemas/provider-descriptor.schema.json index 53c174d983..9cd3470da0 100644 --- a/packages/paperclip-runner/protocol/schemas/provider-descriptor.schema.json +++ b/packages/paperclip-runner/protocol/schemas/provider-descriptor.schema.json @@ -18,14 +18,15 @@ "endpointQualifier": { "type": "string", "minLength": 1, "maxLength": 240 }, "memoryId": { "type": "string", "minLength": 1, "maxLength": 240 }, "eventExpiryDays": { "const": 90 }, - "agent": { "enum": ["pi", "claude", "codex"] }, + "agent": { "enum": ["pi", "claude", "codex", "cursor", "copilot"] }, "requestedModel": { "type": "string", "minLength": 1, "maxLength": 240 }, "acpProtocolVersion": { "const": 1 }, "agentServerPackage": { "type": "string", "minLength": 1, "maxLength": 240 }, "agentServerVersion": { "type": "string", "minLength": 1, "maxLength": 120 }, "agentRuntimePackage": { "type": ["string", "null"], "maxLength": 240 }, "agentRuntimeVersion": { "type": ["string", "null"], "maxLength": 120 }, - "acpxRecordId": { "type": ["string", "null"], "maxLength": 240 } + "acpxRecordId": { "type": ["string", "null"], "maxLength": 240 }, + "turnControls": { "$ref": "#/$defs/turnControls" } }, "allOf": [ { @@ -96,5 +97,16 @@ } } ], + "$defs": { + "turnControls": { + "type": "object", + "required": ["steering", "queuedFollowUp"], + "properties": { + "steering": { "type": "boolean" }, + "queuedFollowUp": { "type": "boolean" } + }, + "additionalProperties": false + } + }, "additionalProperties": false } diff --git a/packages/paperclip-runner/protocol/schemas/provider-event.schema.json b/packages/paperclip-runner/protocol/schemas/provider-event.schema.json index 110f0ede5c..07c5de6270 100644 --- a/packages/paperclip-runner/protocol/schemas/provider-event.schema.json +++ b/packages/paperclip-runner/protocol/schemas/provider-event.schema.json @@ -162,7 +162,7 @@ "providerNotice": { "type": "object", "required": ["schema", "noticeId", "severity", "category", "scope", "recoverable", "userActionable", "summary"], - "properties": { "schema": { "const": "paperclip.provider.notice.v1" }, "noticeId": { "$ref": "#/$defs/id" }, "severity": { "enum": ["info", "warning", "error"] }, "category": { "type": "string", "minLength": 1, "maxLength": 160 }, "scope": { "enum": ["turn", "session", "environment", "account"] }, "recoverable": { "type": "boolean" }, "userActionable": { "type": "boolean" }, "summary": { "$ref": "#/$defs/shortText" } }, + "properties": { "schema": { "const": "paperclip.provider.notice.v1" }, "noticeId": { "$ref": "#/$defs/id" }, "severity": { "enum": ["info", "warning", "error"] }, "category": { "type": "string", "minLength": 1, "maxLength": 160 }, "scope": { "enum": ["turn", "session", "environment", "account"] }, "recoverable": { "type": "boolean" }, "userActionable": { "type": "boolean" }, "summary": { "$ref": "#/$defs/shortText" }, "provenance": {"type":"object","required":["method","eventType","sessionId","turnId"],"properties":{"method":{"type":"string","maxLength":160},"eventType":{"type":"string","maxLength":160},"sessionId":{"type":"string","maxLength":240},"turnId":{"type":"string","maxLength":240},"agentId":{"type":"string","maxLength":240},"timestamp":{"type":"string","maxLength":80}},"additionalProperties":false}, "details": {"type":"array","maxItems":64,"items":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","minLength":1,"maxLength":160},"value":{"type":"string","maxLength":4000}},"additionalProperties":false}} }, "additionalProperties": false } } diff --git a/packages/paperclip-runner/protocol/schemas/question-set.schema.json b/packages/paperclip-runner/protocol/schemas/question-set.schema.json index a1bec0e67f..5921ad3446 100644 --- a/packages/paperclip-runner/protocol/schemas/question-set.schema.json +++ b/packages/paperclip-runner/protocol/schemas/question-set.schema.json @@ -7,7 +7,7 @@ "properties": { "schema": { "const": "paperclip.question_set.v1" }, "title": { "type": "string", "maxLength": 1000 }, - "description": { "type": "string", "maxLength": 4000 }, + "description": { "type": "string", "maxLength": 100000 }, "submitLabel": { "type": "string", "maxLength": 200 }, "questions": { "type": "array", diff --git a/packages/paperclip-runner/protocol/schemas/request.schema.json b/packages/paperclip-runner/protocol/schemas/request.schema.json index 83a741ef44..ec475dbac1 100644 --- a/packages/paperclip-runner/protocol/schemas/request.schema.json +++ b/packages/paperclip-runner/protocol/schemas/request.schema.json @@ -3,6 +3,181 @@ "$id": "https://paperclip.dev/schemas/prp/v1/request.schema.json", "title": "PRP request", "oneOf": [ + { + "type": "object", + "required": [ + "schema", + "requestKind", + "requestId", + "type", + "status", + "prompt", + "choices", + "origin", + "turnId" + ], + "properties": { + "schema": { + "const": "paperclip.runtime_request.v2" + }, + "requestKind": { + "const": "permission_approval" + }, + "requestId": { + "type": "string", + "minLength": 1, + "maxLength": 160 + }, + "type": { + "const": "permission" + }, + "status": { + "enum": [ + "pending", + "resolved", + "expired", + "cancelled" + ] + }, + "prompt": { + "type": "string", + "minLength": 1, + "maxLength": 4000 + }, + "choices": { + "type": "array", + "minItems": 1, + "maxItems": 4, + "items": { + "type": "object", + "required": [ + "key", + "label" + ], + "properties": { + "key": { + "enum": [ + "accept", + "accept_for_session", + "decline", + "cancel" + ] + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 500 + } + }, + "additionalProperties": false + }, + "allOf": [ + { + "contains": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "const": "accept" + } + } + }, + "minContains": 0, + "maxContains": 1 + }, + { + "contains": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "const": "accept_for_session" + } + } + }, + "minContains": 0, + "maxContains": 1 + }, + { + "contains": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "const": "decline" + } + } + }, + "minContains": 0, + "maxContains": 1 + }, + { + "contains": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "const": "cancel" + } + } + }, + "minContains": 0, + "maxContains": 1 + } + ] + }, + "details": { + "type": "object", + "additionalProperties": true + }, + "origin": { + "type": "object", + "required": [ + "adapter" + ], + "properties": { + "adapter": { + "type": "string", + "minLength": 1, + "maxLength": 160 + }, + "provider": { + "type": "string", + "minLength": 1, + "maxLength": 160 + }, + "method": { + "type": "string", + "minLength": 1, + "maxLength": 500 + } + }, + "additionalProperties": false + }, + "turnId": { + "type": "string", + "minLength": 1, + "maxLength": 240 + }, + "itemId": { + "type": [ + "string", + "null" + ], + "minLength": 1, + "maxLength": 240 + } + }, + "additionalProperties": false + }, { "type": "object", "required": ["schema", "requestKind", "requestId", "type", "status", "prompt", "input"], diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_event_payload.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_event_payload.rs index 7ab36472b1..677f3d836e 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_event_payload.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_event_payload.rs @@ -1,15 +1,19 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; +use std::sync::OnceLock; use serde_json::Value; use crate::acpx_event_scope::AcpxEventScope; use crate::acpx_sidecar_transport::AcpxSidecarEvent; -use crate::durable::{redact_text, sanitize_semantic_tool_input, sanitize_value}; +use crate::durable::{ + redact_sensitive_text_values, redact_text, sanitize_semantic_tool_input, sanitize_value, +}; use crate::generated_acpx_sidecar_contract::{ classify_generated_acpx_tool_operation, GeneratedAcpxSidecarEventType, }; use crate::local_runner::LocalRunnerError; use crate::provider_bridge::semantic_value_digest; +use crate::stable_identity::{is_stable_id, SHORT_STABLE_ID_CHARS}; const MAX_EVENT_PAYLOAD_BYTES: usize = 256 * 1024; const MAX_ID_CHARS: usize = 160; @@ -40,6 +44,10 @@ pub enum AcpxTurnStatus { #[derive(Clone, Debug, PartialEq)] pub enum AcpxEventPayload { + RichActivity { + event_type: String, + payload: Value, + }, Runtime { kind: AcpxRuntimeEventKind, tool_operation: Option<&'static str>, @@ -95,6 +103,7 @@ pub fn decode_acpx_event( } match event.event_type { + GeneratedAcpxSidecarEventType::RuntimeRichEvent => decode_rich_event(&event.payload), GeneratedAcpxSidecarEventType::RuntimeEvent => decode_runtime_event(&event.payload), GeneratedAcpxSidecarEventType::RuntimePermissionRequested => { Ok(AcpxEventPayload::PermissionRequested { @@ -113,7 +122,7 @@ pub fn decode_acpx_event( LocalRunnerError::invalid("ACPX input request omitted its question set") })?; validate_question_set(&question_set)?; - let question_set = sanitize_question_set(question_set); + let question_set = sanitize_question_set(question_set)?; let origin = optional_object(&event.payload, "origin", "input request origin")?; Ok(AcpxEventPayload::InputRequested { request_id: required_id_with_limit( @@ -192,9 +201,173 @@ pub fn decode_acpx_event( } } -fn sanitize_question_set(mut value: Value) -> Value { +// This boundary accepts display activity only. Terminal outcomes, tool dispatch, +// runtime requests and source references retain their separate authority paths. +fn rich_event_schema(event_type: &str) -> Option<&'static str> { + Some(match event_type { + "plan.updated" => "plan", + "tool.execution.started" | "tool.execution.progressed" | "tool.execution.completed" => { + "toolExecution" + } + "research.started" | "research.progressed" | "research.completed" => "research", + "delegation.started" | "delegation.updated" | "delegation.completed" => "delegation", + "model.route.changed" => "modelRoute", + "model.verification.updated" => "modelVerification", + "context.compacted" => "contextCompacted", + "artifact.viewed" => "artifactViewed", + "artifact.generated" => "artifactGenerated", + "review.mode.changed" => "reviewMode", + "hook.started" | "hook.completed" => "hook", + "memory.citation.referenced" => "memoryCitation", + "safety.review.started" | "safety.review.completed" => "safetyReview", + "terminal.input.sent" => "terminalInput", + "wait.started" | "wait.completed" => "wait", + "provider.notice.recorded" => "providerNotice", + _ => return None, + }) +} + +fn rich_event_validators( +) -> Result<&'static BTreeMap, LocalRunnerError> { + static VALIDATORS: OnceLock, String>> = + OnceLock::new(); + VALIDATORS.get_or_init(|| { + let schema: Value = serde_json::from_str(include_str!("../../../../protocol/schemas/provider-event.schema.json")) + .map_err(|_| "embedded provider-event schema is invalid".to_owned())?; + fn internal_refs(value: &Value) -> bool { + match value { + Value::Object(object) => object.iter().all(|(key, value)| { + (key != "$ref" || value.as_str().is_some_and(|reference| reference.starts_with("#/"))) && internal_refs(value) + }), + Value::Array(values) => values.iter().all(internal_refs), + _ => true, + } + } + if !internal_refs(&schema) { return Err("provider-event schema may use only internal references".to_owned()); } + let definitions = schema.get("$defs").and_then(Value::as_object).ok_or_else(|| "provider-event schema has no definitions".to_owned())?; + let mut validators = BTreeMap::new(); + for name in ["plan", "toolExecution", "research", "delegation", "modelRoute", "modelVerification", "contextCompacted", "artifactViewed", "artifactGenerated", "reviewMode", "hook", "memoryCitation", "safetyReview", "terminalInput", "wait", "providerNotice"] { + if !definitions.contains_key(name) { return Err("provider-event schema is missing an admitted definition".to_owned()); } + let selected = serde_json::json!({"$schema":"https://json-schema.org/draft/2020-12/schema", "$ref":format!("#/$defs/{name}"), "$defs":definitions}); + let validator = jsonschema::validator_for(&selected).map_err(|_| "embedded provider-event schema cannot compile".to_owned())?; + validators.insert(name.to_owned(), validator); + } + Ok(validators) + }).as_ref().map_err(|message| LocalRunnerError::invalid(message.clone())) +} + +fn decode_rich_event(value: &Value) -> Result { + let wrapper = value + .as_object() + .ok_or_else(|| LocalRunnerError::invalid("ACPX rich event must be an object"))?; + if wrapper + .keys() + .any(|key| !matches!(key.as_str(), "eventType" | "itemId" | "payload")) + { + return Err(LocalRunnerError::invalid( + "ACPX rich event cannot supply source references or authority fields", + )); + } + let item_id = wrapper + .get("itemId") + .and_then(Value::as_str) + .ok_or_else(|| LocalRunnerError::invalid("ACPX rich event omitted its display identity"))?; + if !is_stable_id(item_id, SHORT_STABLE_ID_CHARS) { + return Err(LocalRunnerError::invalid( + "ACPX rich event display identity is invalid", + )); + } + let event_type = wrapper + .get("eventType") + .and_then(Value::as_str) + .ok_or_else(|| LocalRunnerError::invalid("ACPX rich event omitted its type"))?; + let schema = rich_event_schema(event_type).ok_or_else(|| { + LocalRunnerError::invalid("ACPX rich event type is not admitted display activity") + })?; + let payload = wrapper + .get("payload") + .ok_or_else(|| LocalRunnerError::invalid("ACPX rich event omitted its payload"))?; + let validators = rich_event_validators()?; + let validator = validators + .get(schema) + .ok_or_else(|| LocalRunnerError::invalid("ACPX rich event schema is unavailable"))?; + if !validator.is_valid(payload) { + return Err(LocalRunnerError::invalid( + "ACPX rich event failed its exact provider-event schema", + )); + } + if event_type == "artifact.generated" && payload.get("registered") != Some(&Value::Bool(false)) + { + return Err(LocalRunnerError::invalid( + "ACPX display activity cannot register an artifact", + )); + } + if event_type == "plan.updated" + && (payload.get("syncStatus").and_then(Value::as_str) != Some("not_applicable") + || payload + .get("documentRevision") + .is_some_and(|revision| !revision.is_null())) + { + return Err(LocalRunnerError::invalid( + "ACPX display activity cannot synchronize a durable plan", + )); + } + for field in ["reference", "target"] { + if payload + .get(field) + .and_then(Value::as_str) + .is_some_and(|path| { + path.contains('\\') || path.contains(':') || path.chars().any(char::is_control) + }) + { + return Err(LocalRunnerError::invalid( + "ACPX activity reference must be a workspace-relative path", + )); + } + } + fn sanitize_display(value: &Value) -> Value { + match value { + Value::String(text) => Value::String(redact_sensitive_text_values(text)), + Value::Array(values) => Value::Array(values.iter().map(sanitize_display).collect()), + Value::Object(object) => Value::Object( + object + .iter() + .map(|(key, value)| (key.clone(), sanitize_display(value))) + .collect(), + ), + value => value.clone(), + } + } + let payload = sanitize_display(payload); + if !validator.is_valid(&payload) { + return Err(LocalRunnerError::invalid( + "ACPX rich event redaction exceeded its schema bounds", + )); + } + Ok(AcpxEventPayload::RichActivity { + event_type: event_type.to_owned(), + payload, + }) +} + +// Durable storage uses this same exact schema gate before lifting diagnostic +// preview limits. This does not admit an event or grant mutation authority. +pub(crate) fn has_bounded_rich_display_shape(event_type: &str, payload: &Value) -> bool { + let Some(schema) = rich_event_schema(event_type) else { + return false; + }; + serde_json::to_vec(payload).is_ok_and(|bytes| bytes.len() <= MAX_EVENT_PAYLOAD_BYTES) + && rich_event_validators().is_ok_and(|validators| { + validators + .get(schema) + .is_some_and(|validator| validator.is_valid(payload)) + }) +} + +fn sanitize_question_set(mut value: Value) -> Result { + let original = value.clone(); let Some(question_set) = value.as_object_mut() else { - return value; + return Ok(value); }; redact_object_text(question_set, &["title", "description", "submitLabel"]); if let Some(questions) = question_set @@ -223,7 +396,17 @@ fn sanitize_question_set(mut value: Value) -> Value { } } } - value + if value != original { + let description = value + .get("description") + .and_then(Value::as_str) + .unwrap_or(""); + value["description"] = Value::String(format!( + "Sensitive values were redacted from this request.\n\n{description}" + )); + } + validate_question_set(&value)?; + Ok(value) } fn redact_object_text(object: &mut serde_json::Map, keys: &[&str]) { @@ -231,7 +414,7 @@ fn redact_object_text(object: &mut serde_json::Map, keys: &[&str] let Some(text) = object.get(*key).and_then(Value::as_str) else { continue; }; - let redacted = redact_text(text); + let redacted = redact_sensitive_text_values(text); object.insert((*key).to_owned(), Value::String(redacted)); } } @@ -397,7 +580,16 @@ fn validate_plan(payload: &Value) -> Result<(), LocalRunnerError> { Ok(()) } -fn validate_question_set(value: &Value) -> Result<(), LocalRunnerError> { +pub(crate) fn validate_question_set(value: &Value) -> Result<(), LocalRunnerError> { + if serde_json::to_vec(value) + .map_err(|_| LocalRunnerError::invalid("ACPX question set is invalid"))? + .len() + > 196 * 1024 + { + return Err(LocalRunnerError::invalid( + "ACPX question set exceeds its 196 KiB durable byte bound", + )); + } let schema: Value = serde_json::from_str(include_str!( "../../../../protocol/schemas/question-set.schema.json" )) diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs index 626deef153..c2408ec731 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use std::fs::{self, DirBuilder, File}; use std::io::{Read, Write}; use std::net::TcpListener; @@ -10,11 +10,11 @@ use std::os::unix::fs::{DirBuilderExt, PermissionsExt}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -#[cfg(test)] use sha2::{Digest, Sha256}; use crate::acpx_provider_session::{ - AcpxPermissionMode, AcpxProviderSession, AcpxProviderSessionConfig, AcpxProviderSessionIdentity, + AcpxPermissionMode, AcpxProviderRuntimePolicy, AcpxProviderSession, AcpxProviderSessionConfig, + AcpxProviderSessionIdentity, AcpxTurnControlCapabilities, }; use crate::acpx_sidecar_transport::AcpxSidecarTransportConfig; #[cfg(test)] @@ -137,6 +137,8 @@ struct AcpxProviderDescriptor { permission_mode: AcpxPermissionMode, permission_mode_pinned: bool, #[serde(default)] + provider_policy: Option, + #[serde(default)] runtime_context: Value, } @@ -162,6 +164,30 @@ impl AcpxProviderDescriptor { Some("0.156.0"), "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3", ), + "pi" => ( + "openrouter/deepseek/deepseek-v4-flash-0731", + "pi-acp", + "0.0.33", + Some("@earendil-works/pi-coding-agent"), + Some("0.84.2"), + "sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f", + ), + "cursor" => ( + self.model.as_str(), + "cursor-agent", + "2026.09.26-dd393fe", + None, + None, + "sha256:1157a5d071abbd57ab132f22bace75c65e84cc47a045b0023475488755e14899", + ), + "copilot" => ( + self.model.as_str(), + "@github/copilot", + "1.0.88", + None, + None, + "sha256:b18c01603dd0169d233140709cfaa8bf5304a03cf5de78ca4f625f30013e8457", + ), "grok" => ( "grok-4.7", "builtin:grok-acp", @@ -170,12 +196,9 @@ impl AcpxProviderDescriptor { Some("1.0.13"), "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55", ), - "pi" => return Err(DurableRunnerError::invalid( - "ACPX agent pi is not executable through the verified runnerd provider boundary", - )), _ => { return Err(DurableRunnerError::invalid( - "ACPX agent must be a qualified claude or codex profile", + "ACPX agent must name a known immutable profile", )) } }; @@ -186,7 +209,10 @@ impl AcpxProviderDescriptor { || self.acpx_version != "0.13.1" || (self.agent != "claude" && self.agent != "grok" && self.model != expected.0) || self.model.trim().is_empty() - || self.model.len() > 1024 + || self.model.len() > 240 + || self.model.contains('\0') + || (matches!(self.agent.as_str(), "pi" | "cursor" | "copilot") + && self.provider_policy.is_none()) || self.agent_server_package != expected.1 || self.agent_server_version != expected.2 || self.agent_runtime_package.as_deref() != expected.3 @@ -281,6 +307,7 @@ impl AcpxProviderDescriptor { working_directory: PathBuf::from(&self.cwd), permission_mode: self.permission_mode, permission_mode_pinned: self.permission_mode_pinned, + provider_policy: self.provider_policy.clone(), system_instructions: self.instructions.clone(), runtime_context: self.runtime_context.clone(), tool_set: provider_tool_set, @@ -395,17 +422,23 @@ struct AcpxDurableState { #[serde(default)] active_turn_id: Option, #[serde(default)] + attempted_turn_controls: HashSet, + #[serde(default)] provider_exit_unconfirmed: bool, #[serde(default)] semantic_result: Option, #[serde(default)] goal_projection: Value, #[serde(default)] + turn_controls: AcpxTurnControlCapabilities, + #[serde(default)] goal_revision: u64, #[serde(default)] goal_source_revision: Option, #[serde(default)] pending_events: VecDeque, + #[serde(default)] + pending_runtime_requests: BTreeMap, #[serde(default = "initial_event_sequence")] next_event_sequence: u64, } @@ -424,12 +457,15 @@ impl AcpxDurableState { tool_set, identity: None, active_turn_id: None, + attempted_turn_controls: HashSet::new(), provider_exit_unconfirmed: false, semantic_result: None, goal_projection: Value::Null, + turn_controls: AcpxTurnControlCapabilities::default(), goal_revision: 0, goal_source_revision: None, pending_events: VecDeque::new(), + pending_runtime_requests: BTreeMap::new(), next_event_sequence: initial_event_sequence(), } } @@ -450,6 +486,7 @@ impl AcpxDurableState { "ACPX durable launch profile digest does not match runner startup", )); } + validate_pending_runtime_requests(&self.pending_runtime_requests)?; let mut ids = HashSet::new(); if self.schema != ACPX_PROVIDER_STATE_SCHEMA || self.launch_profile_digest.len() != 71 @@ -464,6 +501,12 @@ impl AcpxDurableState { | "closed" ) || self.next_event_sequence == 0 + || self.attempted_turn_controls.len() > 1024 + || self + .attempted_turn_controls + .iter() + .any(|id| !is_stable_id(id, 160)) + || (!self.pending_runtime_requests.is_empty() && self.active_turn_id.is_none()) || self.pending_events.len() > MAX_PENDING_EVENTS || self.pending_events.iter().any(|event| { event_sequence(&event.executor_event_id) @@ -501,16 +544,81 @@ impl AcpxDurableState { Ok(()) } + fn expire_runtime_requests( + &mut self, + reason: &str, + cancelled: bool, + ) -> Result<(), DurableRunnerError> { + for request in self + .pending_runtime_requests + .values() + .cloned() + .collect::>() + { + let input = request.get("type").and_then(Value::as_str) == Some("input"); + let mut payload = json!({ + "provider":"acpx", "requestId":request["requestId"], + "requestKind":request["requestKind"], "requestType":request["type"], + "turnId":request["turnId"], "itemId":request["itemId"], + "reason":reason, "replayAllowed":false, "adapter":"acpx-runtime-sidecar", + }); + if input { + payload["request"] = request; + } + self.push(NormalizedProviderEvent { + event_type: if cancelled { + "runtime_request.cancelled" + } else { + "runtime_request.expired" + } + .to_owned(), + priority: EventPriority::P0, + payload, + })?; + } + Ok(()) + } + fn push(&mut self, event: NormalizedProviderEvent) -> Result<(), DurableRunnerError> { if self.pending_events.len() >= MAX_PENDING_EVENTS { return Err(DurableRunnerError::invalid( "ACPX provider event backlog exceeds its durable limit", )); } + // Check every fallible admission condition before changing the request + // ledger. A rejected settlement must leave its pending request intact. let sequence = self.next_event_sequence; - self.next_event_sequence = sequence + let next_sequence = sequence .checked_add(1) .ok_or_else(|| DurableRunnerError::invalid("ACPX event sequence exhausted"))?; + if event.event_type == "runtime_request.created" { + let request = event.payload.get("request").cloned().ok_or_else(|| { + DurableRunnerError::invalid("ACPX runtime request omitted its payload") + })?; + let request_id = request + .get("requestId") + .and_then(Value::as_str) + .ok_or_else(|| { + DurableRunnerError::invalid("ACPX runtime request omitted its identity") + })? + .to_owned(); + let mut next = self.pending_runtime_requests.clone(); + if next.insert(request_id, request).is_some() { + return Err(DurableRunnerError::invalid( + "ACPX runtime request reused a pending durable identity", + )); + } + validate_pending_runtime_requests(&next)?; + self.pending_runtime_requests = next; + } else if matches!( + event.event_type.as_str(), + "runtime_request.resolved" | "runtime_request.expired" | "runtime_request.cancelled" + ) { + if let Some(id) = event.payload.get("requestId").and_then(Value::as_str) { + self.pending_runtime_requests.remove(id); + } + } + self.next_event_sequence = next_sequence; self.pending_events.push_back(PolledEvent { executor_event_id: event_id(sequence), event_type: event.event_type, @@ -521,6 +629,53 @@ impl AcpxDurableState { } } +fn validate_pending_runtime_requests( + requests: &BTreeMap, +) -> Result<(), DurableRunnerError> { + if requests.len() > 1024 + || serde_json::to_vec(requests).map_or(true, |bytes| bytes.len() > 8 * 1024 * 1024) + { + return Err(DurableRunnerError::invalid( + "ACPX durable runtime request ledger exceeds its bound", + )); + } + for (id, request) in requests { + let input = request.get("type").and_then(Value::as_str) == Some("input"); + if !is_stable_id(id, 160) + || request.get("requestId").and_then(Value::as_str) != Some(id) + || request.get("schema").and_then(Value::as_str) != Some("paperclip.runtime_request.v2") + || request.get("status").and_then(Value::as_str) != Some("pending") + || request.get("requestKind").and_then(Value::as_str) + != Some(if input { + "runtime" + } else { + "permission_approval" + }) + || (!input && request.get("type").and_then(Value::as_str) != Some("permission")) + || ["turnId", "itemId"].iter().any(|field| { + request + .get(field) + .and_then(Value::as_str) + .is_none_or(|value| !is_stable_id(value, DURABLE_STABLE_ID_CHARS)) + }) + || (input + && request.pointer("/input/schema").and_then(Value::as_str) + != Some("paperclip.question_set.v1")) + || (!input && request.get("input").is_some()) + || serde_json::to_vec(request).map_or(true, |bytes| bytes.len() > 256 * 1024) + { + return Err(DurableRunnerError::invalid( + "ACPX durable runtime request ledger has an invalid identity or type", + )); + } + if input { + crate::acpx_event_payload::validate_question_set(&request["input"]) + .map_err(|error| DurableRunnerError::invalid(error.to_string()))?; + } + } + Ok(()) +} + pub struct AcpxCommandExecutor { state_dir: PathBuf, context: AcpxEventProjectionContext, @@ -528,6 +683,9 @@ pub struct AcpxCommandExecutor { session: Option, restore_checked: bool, restore_error: Option, + persistence_error: Option, + #[cfg(test)] + fail_after_state_rename: bool, launch_profile: Option, } @@ -546,6 +704,9 @@ impl AcpxCommandExecutor { session: None, restore_checked: false, restore_error: None, + persistence_error: None, + #[cfg(test)] + fail_after_state_rename: false, launch_profile: config.acpx_launch_profile.clone(), } } @@ -566,6 +727,7 @@ impl AcpxCommandExecutor { } fn restore(&mut self) -> Result<(), DurableRunnerError> { + self.ensure_persistence_healthy()?; if self.restore_checked { return Ok(()); } @@ -648,6 +810,7 @@ impl AcpxCommandExecutor { .state .as_mut() .expect("ACPX state remains available during recovery"); + state.expire_runtime_requests("provider_process_lost", false)?; state.lifecycle = "closed".to_owned(); state.active_turn_id = None; state.provider_exit_unconfirmed = true; @@ -681,15 +844,17 @@ impl AcpxCommandExecutor { let session = self.start_session(true)?; let identity = session.identity().clone(); let process_id = session.process_id(); + let turn_controls = session.turn_control_capabilities(); let state = self .state .as_mut() .expect("ACPX state remains available during recovery"); state.lifecycle = "session_open".to_owned(); + state.turn_controls = turn_controls; state.push(NormalizedProviderEvent { event_type: "session.resumed".to_owned(), priority: EventPriority::P0, - payload: session_event_payload(&state.descriptor, &identity, process_id), + payload: session_event_payload(&state.descriptor, &identity, process_id, turn_controls), })?; self.session = Some(session); self.save_state() @@ -718,11 +883,46 @@ impl AcpxCommandExecutor { Ok(session) } - fn save_state(&self) -> Result<(), DurableRunnerError> { - let state = self + fn ensure_persistence_healthy(&self) -> Result<(), DurableRunnerError> { + match self.persistence_error.as_ref() { + Some(error) => Err(error.clone()), + None => Ok(()), + } + } + + fn record_state_write( + &mut self, + result: Result<(), DurableRunnerError>, + ) -> Result<(), DurableRunnerError> { + if let Err(error) = result.as_ref() { + // A rename may have succeeded before directory fsync or protection + // failed. Neither rolling back nor continuing from memory can prove + // which state survived. Stop publication and mutations until a fresh + // executor reloads the complete atomic snapshot; never resend input. + self.persistence_error = Some(error.clone()); + } + result + } + + fn save_state(&mut self) -> Result<(), DurableRunnerError> { + self.ensure_persistence_healthy()?; + let result = self .state .as_ref() - .ok_or_else(|| DurableRunnerError::invalid("ACPX provider state is unavailable"))?; + .ok_or_else(|| DurableRunnerError::invalid("ACPX provider state is unavailable")) + .and_then(|state| self.write_state(state)); + self.record_state_write(result) + } + + fn commit_staged_state(&mut self, state: AcpxDurableState) -> Result<(), DurableRunnerError> { + self.ensure_persistence_healthy()?; + let result = self.write_state(&state); + self.record_state_write(result)?; + self.state = Some(state); + Ok(()) + } + + fn write_state(&self, state: &AcpxDurableState) -> Result<(), DurableRunnerError> { let launch_profile_digest = self.launch_profile_digest()?; state.validate(&self.context, &launch_profile_digest)?; secure_directory(&self.state_dir, "provider state")?; @@ -741,6 +941,10 @@ impl AcpxCommandExecutor { file.sync_all()?; drop(file); fs::rename(&temporary, &path)?; + #[cfg(test)] + if self.fail_after_state_rename { + return Err(std::io::Error::other("injected post-rename sync failure")); + } #[cfg(unix)] File::open(&self.state_dir)?.sync_all()?; Ok(()) @@ -901,6 +1105,7 @@ impl AcpxCommandExecutor { .expect("ACPX session exists after successful start"); let identity = session.identity().clone(); let process_id = session.process_id(); + let turn_controls = session.turn_control_capabilities(); let resumed = self .state .as_ref() @@ -914,7 +1119,9 @@ impl AcpxCommandExecutor { state.identity = Some(identity.clone()); state.active_turn_id = None; state.lifecycle = "session_open".to_owned(); - let payload = session_event_payload(&state.descriptor, &identity, process_id); + state.turn_controls = turn_controls; + let payload = + session_event_payload(&state.descriptor, &identity, process_id, turn_controls); let goal = self.goal_control("session.goal.get", &json!({}))?; self.save_state()?; Ok(CommandExecution { @@ -1012,6 +1219,7 @@ impl AcpxCommandExecutor { projection["requestId"] = request_id.clone(); } state.goal_projection = projection.clone(); + let turn_controls = state.turn_controls; self.save_state()?; let event_type = match command { "session.goal.clear" => "session.goal.cleared", @@ -1024,7 +1232,7 @@ impl AcpxCommandExecutor { ( "session.capabilities.updated".to_owned(), EventPriority::P0, - json!({"sessionGoals":projection["sessionGoals"]}), + json!({"sessionGoals":projection["sessionGoals"], "turnControls":turn_controls}), ), (event_type.to_owned(), EventPriority::P0, projection), ], @@ -1065,6 +1273,7 @@ impl AcpxCommandExecutor { )); } state.active_turn_id = Some(provider_turn_id.clone()); + state.attempted_turn_controls.clear(); state.semantic_result = None; state.lifecycle = "turn_starting".to_owned(); } @@ -1101,6 +1310,18 @@ impl AcpxCommandExecutor { .as_mut() .expect("ACPX state exists after turn start"); state.lifecycle = "turn_active".to_owned(); + let previous_turn_controls = state.turn_controls; + state.turn_controls = self + .session + .as_ref() + .expect("live ACPX session") + .turn_control_capabilities(); + // session.open already published these capabilities. Preserve legacy + // turn ordering when nothing changed, while exposing lazy discovery + // and capability loss after a provider process replacement. + let capabilities = (state.turn_controls != previous_turn_controls).then(|| { + json!({"sessionGoals": state.goal_projection["sessionGoals"], "turnControls":state.turn_controls}) + }); self.save_state()?; let mut events = Vec::with_capacity(if provider_process_will_be_replaced { 2 @@ -1123,6 +1344,13 @@ impl AcpxCommandExecutor { ), )); } + if let Some(capabilities) = capabilities { + events.push(( + "session.capabilities.updated".to_owned(), + EventPriority::P0, + capabilities, + )); + } events.push(( "turn.started".to_owned(), EventPriority::P0, @@ -1139,6 +1367,91 @@ impl AcpxCommandExecutor { }) } + fn steer_turn( + &mut self, + command_id: &str, + payload: &Value, + ) -> Result { + let text = payload + .get("text") + .and_then(Value::as_str) + .ok_or_else(|| DurableRunnerError::invalid("turn.steer payload.text is required"))?; + let turn_id = payload + .get("turnId") + .and_then(Value::as_str) + .ok_or_else(|| DurableRunnerError::invalid("turn.steer payload.turnId is required"))?; + let mode = match payload.get("mode") { + None => "steer", + Some(Value::String(mode)) => mode.as_str(), + _ => { + return Err(DurableRunnerError::invalid( + "turn.steer payload.mode must be a string", + )) + } + }; + if !matches!(mode, "steer" | "follow_up") + || text.trim().is_empty() + || text.len() > 65_536 + || text.contains('\0') + { + return Err(DurableRunnerError::invalid( + "turn.steer violates its bounded contract", + )); + } + let control_id = match payload.get("correlationId") { + Some(Value::String(id)) if is_stable_id(id, 160) => id.clone(), + None => format!("control-{:x}", Sha256::digest(command_id.as_bytes())), + _ => { + return Err(DurableRunnerError::invalid( + "turn.steer correlationId is invalid", + )) + } + }; + let state = self + .state + .as_mut() + .ok_or_else(|| DurableRunnerError::invalid("ACPX provider is not prepared"))?; + if state.lifecycle != "turn_active" + || state.active_turn_id.as_deref() != Some(turn_id) + || self.session.is_none() + { + return Err(DurableRunnerError::invalid( + "turn.steer named a stale or inactive turn", + )); + } + if state.attempted_turn_controls.contains(&control_id) + || state.attempted_turn_controls.len() >= 1024 + { + return Err(DurableRunnerError::invalid( + "turn.steer is duplicate or exceeds the turn control limit", + )); + } + state.attempted_turn_controls.insert(control_id.clone()); + // Retain before the potentially mutating RPC. Controller reconnects may + // read its acknowledgement but must not replay an ambiguous attempt. + self.save_state()?; + self.session + .as_mut() + .expect("checked live session") + .steer_turn(turn_id, &control_id, mode, text) + .map_err(|error| { + DurableRunnerError::invalid(format!("ACPX turn control failed: {error}")) + })?; + Ok(CommandExecution { + result: json!({"status": "accepted", "mode": mode, "providerTurnId": turn_id, "correlationId": control_id}), + events: vec![( + "item.completed".to_owned(), + EventPriority::P0, + json!({ + "provider": "acpx", "providerTurnId": turn_id, + "itemId": format!("acpx-control-{:x}", Sha256::digest(format!("{turn_id}:{control_id}").as_bytes())), + "kind": "steering_acknowledgement", "mode": mode, "status": "acknowledged", + "text": if mode == "steer" { "Steering acknowledged for the active turn." } else { "Follow-up queued after the active work." }, + }), + )], + }) + } + fn interrupt_turn(&mut self, reason: &str) -> Result { let turn_id = self .state @@ -1202,6 +1515,7 @@ impl AcpxCommandExecutor { .state .as_mut() .expect("ACPX state remains available after provider termination"); + state.expire_runtime_requests("explicit_cancellation", true)?; state.active_turn_id = None; self.context.provider_turn_id = None; // Persist a non-attachable, recoverable boundary before the fallible @@ -1237,33 +1551,65 @@ impl AcpxCommandExecutor { .get("requestId") .and_then(Value::as_str) .ok_or_else(|| DurableRunnerError::invalid("request.resolve requires requestId"))?; - let response = payload - .get("response") - .ok_or_else(|| DurableRunnerError::invalid("request.resolve requires response"))?; let turn_id = self .state .as_ref() .and_then(|state| state.active_turn_id.clone()) .ok_or_else(|| DurableRunnerError::invalid("ACPX provider has no active turn"))?; - self.session + if payload + .get("turnId") + .and_then(Value::as_str) + .is_some_and(|id| id != self.context.turn_id) + { + return Err(DurableRunnerError::invalid( + "ACPX runtime response belongs to a stale turn", + )); + } + let session = self + .session .as_mut() - .ok_or_else(|| DurableRunnerError::invalid("ACPX session is unavailable"))? - .resolve_input( - request_id, - &turn_id, - &json!({"action": "submit", "response": response}), - ) - .map_err(|error| { - DurableRunnerError::invalid(format!("ACPX runtime response failed: {error}")) + .ok_or_else(|| DurableRunnerError::invalid("ACPX session is unavailable"))?; + let resolution = payload + .get("resolution") + .cloned() + .or_else(|| { + payload + .get("response") + .map(|response| json!({"action":"submit","response":response})) + }) + .ok_or_else(|| { + DurableRunnerError::invalid("request.resolve requires resolution or response") })?; - Ok(CommandExecution { - result: json!({"status": "delivered", "requestId": request_id}), - events: vec![( - "runtime_request.resolved".to_owned(), - EventPriority::P0, - json!({"provider": "acpx", "requestId": request_id, "status": "delivered"}), - )], + let permission = session.state().pending_permission(request_id).is_some(); + // Admit the event before sending the response, but keep its request + // retirement and outbox entry private until the state write succeeds. + let mut staged = self + .state + .clone() + .expect("ACPX response has an active durable turn"); + staged.push(NormalizedProviderEvent { + event_type: "runtime_request.resolved".to_owned(), + priority: EventPriority::P0, + payload: json!({"provider": "acpx", "requestId": request_id, "status": "delivered", + "requestKind": if permission {"permission_approval"} else {"runtime"}, + "turnId":self.context.turn_id,"itemId":self.context.item_id,"action":resolution.get("action")}), + })?; + (if permission { + session.resolve_permission(request_id, &turn_id, &resolution) + } else { + session.resolve_input(request_id, &turn_id, &resolution) }) + .map_err(|error| { + DurableRunnerError::invalid(format!("ACPX runtime response failed: {error}")) + })?; + // The sidecar ACK proves delivery, not durable controller settlement. + // Retain that fact in the same atomic state write that retires the + // pending request. A crash before this write expires the old request; + // a crash after it replays this event, never the provider response. + self.commit_staged_state(staged)?; + Ok(CommandExecution::result( + json!({"status": "delivered", "requestId": request_id}), + )) } fn deliver_tool_result( @@ -1340,6 +1686,7 @@ impl AcpxCommandExecutor { .state .as_mut() .ok_or_else(|| DurableRunnerError::invalid("ACPX provider is not prepared"))?; + state.expire_runtime_requests("explicit_cancellation", true)?; state.lifecycle = "closed".to_owned(); state.active_turn_id = None; self.context.provider_turn_id = None; @@ -1399,7 +1746,7 @@ impl AcpxCommandExecutor { if self .state .as_ref() - .is_some_and(|state| !state.pending_events.is_empty()) + .is_some_and(|state| !state.pending_events.is_empty() || state.lifecycle == "closed") || self.session.is_none() { return Ok(()); @@ -1409,10 +1756,33 @@ impl AcpxCommandExecutor { .session .as_mut() .expect("ACPX session remains available while polling") - .poll_event(Duration::from_millis(1)) - .map_err(|error| { - DurableRunnerError::invalid(format!("ACPX provider failed: {error}")) - })?; + .poll_event(Duration::from_millis(1)); + let events = match events { + Ok(events) => events, + Err(error) => { + let state = self + .state + .as_mut() + .expect("ACPX failed provider has durable state"); + state.expire_runtime_requests("provider_process_lost", false)?; + let failed_turn = state.active_turn_id.take(); + state.lifecycle = "closed".to_owned(); + state.provider_exit_unconfirmed = true; + if let Some(turn_id) = failed_turn { + state.push(NormalizedProviderEvent { event_type: "turn.failed".to_owned(), priority: EventPriority::P0, + payload: json!({"provider":"acpx","providerTurnId":turn_id,"status":"failed","providerTerminalObserved":false, + "code":"acpx_provider_transport_failed","message":crate::durable::redact_text(&error.to_string())}) })?; + state.push(NormalizedProviderEvent { event_type: "run.terminal".to_owned(), priority: EventPriority::P0, + payload: json!({"schema":"paperclip.prp.terminal.v1","status":"failed","turnTerminalState":"failed","runTerminalState":"failed","reportedWorkDisposition":"unknown","provider":"acpx"}) })?; + } + self.context.provider_turn_id = None; + // A failed transport cannot attest escaped provider cleanup. + // Drop the closed process owner; later cleanup must prove the lifetime fence. + self.session = None; + self.save_state()?; + return Ok(()); + } + }; let Some(events) = events else { break }; let mut provider_turn_settled = false; for event in events { @@ -1570,11 +1940,7 @@ impl CommandExecutor for AcpxCommandExecutor { self.goal_control(&command.command_type, &command.payload) } "turn.start" => self.start_turn(&command.payload), - "turn.steer" => Ok(CommandExecution::result(json!({ - "status": "rejected", - "code": "provider_command_unavailable", - "message": "ACPX does not support steering an active turn", - }))), + "turn.steer" => self.steer_turn(&command.command_id, &command.payload), "turn.interrupt" | "run.cancel" => self.interrupt_turn(&command.command_type), "turn.stop" => self.stop_turn_for_suspension(&command.command_type), "request.resolve" => self.resolve_request(&command.payload), @@ -1618,6 +1984,7 @@ impl CommandExecutor for AcpxCommandExecutor { } fn retained_events(&mut self) -> Result, DurableRunnerError> { + self.ensure_persistence_healthy()?; // Explicit drain runs while control traffic suppresses provider polling. // Expose the already-retained suffix so runnerd can commit and ACK it // before suspension, without restoring or advancing the provider. @@ -1631,6 +1998,7 @@ impl CommandExecutor for AcpxCommandExecutor { } fn acknowledge_events(&mut self, count: usize) -> Result<(), DurableRunnerError> { + self.ensure_persistence_healthy()?; if count == 0 { return Ok(()); } @@ -1648,6 +2016,20 @@ impl CommandExecutor for AcpxCommandExecutor { } fn shutdown(&mut self) -> Result<(), DurableRunnerError> { + if let Some(error) = self.persistence_error.clone() { + // Reap our owned process even when durable cleanup is uncertain, + // but do not overwrite the last atomic snapshot or claim success. + if let Some(mut session) = self.session.take() { + session + .shutdown("ACPX provider state persistence failed") + .map_err(|cleanup| { + DurableRunnerError::invalid(format!( + "{error}; failed to stop ACPX provider: {cleanup}" + )) + })?; + } + return Err(error); + } // A replacement durable runner may reach terminal reconciliation // before any provider command or event poll. Restore the persisted // session first so cleanup cannot succeed merely because this process @@ -1720,11 +2102,14 @@ fn session_event_payload( descriptor: &AcpxProviderDescriptor, identity: &AcpxProviderSessionIdentity, process_id: u32, + turn_controls: AcpxTurnControlCapabilities, ) -> Value { + let mut public_descriptor = descriptor.public_descriptor(Some(identity)); + public_descriptor["turnControls"] = json!(turn_controls); json!({ "provider": "acpx", "driver": "acpx_runtime", - "providerDescriptor": descriptor.public_descriptor(Some(identity)), + "providerDescriptor": public_descriptor, "runtimeIdentity": { "executionKind": "local_process", "processId": process_id, @@ -1899,6 +2284,312 @@ mod tests { }) } + fn pending_input_request(id: &str) -> Value { + json!({"schema":"paperclip.runtime_request.v2","requestId":id,"requestKind":"runtime","type":"input","status":"pending", + "turnId":"turn-1","itemId":"item-1","prompt":"Choose","input":{"schema":"paperclip.question_set.v1","questions":[{"id":"q","prompt":"Choose","answerMode":"text","required":true}]}, + "origin":{"adapter":"acpx-runtime-sidecar","provider":"cursor","method":"cursor/ask_question"}}) + } + + #[test] + fn durable_runtime_ledger_retains_only_unsettled_requests_and_rejects_forged_types() { + let operations = Vec::new(); + let tool_set = AuthorizedToolSet { + schema: TOOL_SET_SCHEMA.into(), + schema_version: 1, + catalog_digest: authorized_tool_catalog_digest(&operations).unwrap(), + operations, + }; + let mut state = AcpxDurableState::new( + serde_json::from_value(descriptor("codex")).unwrap(), + tool_set, + "test".into(), + ); + state + .push(NormalizedProviderEvent { + event_type: "runtime_request.created".into(), + priority: EventPriority::P0, + payload: json!({"request":pending_input_request("input-1")}), + }) + .unwrap(); + assert!(state + .push(NormalizedProviderEvent { + event_type: "runtime_request.created".into(), + priority: EventPriority::P0, + payload: json!({"request":pending_input_request("input-1")}) + }) + .is_err()); + let mut forged = pending_input_request("forged"); + forged["requestKind"] = json!("permission_approval"); + assert!(state + .push(NormalizedProviderEvent { + event_type: "runtime_request.created".into(), + priority: EventPriority::P0, + payload: json!({"request":forged}) + }) + .is_err()); + state.pending_events.clear(); + let mut restored: AcpxDurableState = + serde_json::from_value(serde_json::to_value(&state).unwrap()).unwrap(); + restored + .expire_runtime_requests("provider_process_lost", false) + .unwrap(); + assert_eq!(restored.pending_events.len(), 1); + assert_eq!( + restored.pending_events[0].event_type, + "runtime_request.expired" + ); + assert_eq!( + restored.pending_events[0].payload["request"], + pending_input_request("input-1") + ); + assert_eq!(restored.pending_events[0].payload["replayAllowed"], false); + restored + .expire_runtime_requests("provider_process_lost", false) + .unwrap(); + assert_eq!(restored.pending_events.len(), 1); + state + .push(NormalizedProviderEvent { + event_type: "runtime_request.resolved".into(), + priority: EventPriority::P0, + payload: json!({"requestId":"input-1"}), + }) + .unwrap(); + assert!(state.pending_runtime_requests.is_empty()); + let mut old = serde_json::to_value(state).unwrap(); + old.as_object_mut() + .unwrap() + .remove("pendingRuntimeRequests"); + assert!(serde_json::from_value::(old) + .unwrap() + .pending_runtime_requests + .is_empty()); + } + + #[test] + fn rejected_runtime_settlement_preserves_the_pending_request() { + for event_type in [ + "runtime_request.resolved", + "runtime_request.expired", + "runtime_request.cancelled", + ] { + for exhausted_sequence in [false, true] { + let operations = Vec::new(); + let mut state = AcpxDurableState::new( + serde_json::from_value(descriptor("codex")).unwrap(), + AuthorizedToolSet { + schema: TOOL_SET_SCHEMA.into(), + schema_version: 1, + catalog_digest: authorized_tool_catalog_digest(&operations).unwrap(), + operations, + }, + "test".into(), + ); + state + .push(NormalizedProviderEvent { + event_type: "runtime_request.created".into(), + priority: EventPriority::P0, + payload: json!({"request":pending_input_request("input-1")}), + }) + .unwrap(); + if exhausted_sequence { + state.next_event_sequence = u64::MAX; + } else { + state + .pending_events + .resize(MAX_PENDING_EVENTS, state.pending_events[0].clone()); + } + let before = state.clone(); + assert!(state + .push(NormalizedProviderEvent { + event_type: event_type.into(), + priority: EventPriority::P0, + payload: json!({"requestId":"input-1"}), + }) + .is_err()); + assert_eq!( + state, before, + "{event_type} must not retire a request without its event" + ); + } + } + } + + #[test] + fn post_rename_failure_blocks_settlement_publication_until_fresh_recovery() { + for event_type in [ + "runtime_request.resolved", + "runtime_request.expired", + "runtime_request.cancelled", + ] { + let directory = temporary_directory("post-rename-settlement"); + let marker = directory.join("must-not-start-provider"); + let command_path = directory.join("sidecar"); + write_artifact( + &command_path, + format!("#!/bin/sh\ntouch '{}'\n", marker.display()).as_bytes(), + true, + ); + let launch_profile = AcpxLaunchProfile { + authority_digest: format!("sha256:{}", "d".repeat(64)), + command: command_path.clone(), + args: Vec::new(), + artifacts: vec![artifact(&command_path)], + }; + let mut value = descriptor("codex"); + value["sidecarCommand"] = json!(command_path); + value["sidecarArgs"] = json!([]); + let descriptor: AcpxProviderDescriptor = serde_json::from_value(value).unwrap(); + let identity = AcpxProviderSessionIdentity { + kind: "acpx".into(), + normalized_session_id: "session-1".into(), + acpx_record_id: "record-1".into(), + backend_session_id: "backend-1".into(), + agent_session_id: "agent-1".into(), + profile_digest: descriptor.command_digest.clone(), + workspace_digest: format!("sha256:{}", "a".repeat(64)), + requested_model: descriptor.model.clone(), + effective_model: descriptor.model.clone(), + permission_mode: Some(descriptor.permission_mode), + provider_lifetime_fence_candidates: [60_001, 60_002, 60_003], + }; + let operations = Vec::new(); + let mut state = AcpxDurableState::new( + descriptor, + AuthorizedToolSet { + schema: TOOL_SET_SCHEMA.into(), + schema_version: 1, + catalog_digest: authorized_tool_catalog_digest(&operations).unwrap(), + operations, + }, + launch_profile.canonical_digest().unwrap(), + ); + state.identity = Some(identity); + state.lifecycle = "turn_active".into(); + state.active_turn_id = Some("turn-1".into()); + for request_id in ["input-1", "input-2"] { + state + .push(NormalizedProviderEvent { + event_type: "runtime_request.created".into(), + priority: EventPriority::P0, + payload: json!({"request":pending_input_request(request_id)}), + }) + .unwrap(); + } + state.pending_events.clear(); + let config = test_config(&directory, Some(launch_profile)); + let mut original = AcpxCommandExecutor::with_runner_config(&directory, &config); + original.restore_checked = true; + original.state = Some(state.clone()); + original.save_state().unwrap(); + let mut staged = state.clone(); + staged + .push(NormalizedProviderEvent { + event_type: event_type.into(), + priority: EventPriority::P0, + payload: json!({"requestId":"input-1"}), + }) + .unwrap(); + original.fail_after_state_rename = true; + let error = if event_type == "runtime_request.resolved" { + let error = original.commit_staged_state(staged.clone()).unwrap_err(); + assert_eq!(original.state.as_ref(), Some(&state)); + error + } else { + // Existing cancellation/expiry paths mutate their working + // state before save; the same latch must quarantine them. + original.state = Some(staged.clone()); + original.save_state().unwrap_err() + }; + assert!(error + .to_string() + .contains("injected post-rename sync failure")); + original.fail_after_state_rename = false; + let disk = fs::read(original.state_path()).unwrap(); + assert_eq!( + serde_json::from_slice::(&disk).unwrap(), + staged + ); + for result in [ + original.retained_events().map(|_| ()), + original.poll_events().map(|_| ()), + original.acknowledge_events(0), + original.acknowledge_events(1), + original.restore(), + original.save_state(), + original.shutdown(), + ] { + assert_eq!(result.unwrap_err().to_string(), error.to_string()); + } + for command_type in ["request.resolve", "runner.drain", "runner.suspend"] { + let blocked = original.execute(&Command { + schema: "paperclip.prp.command.v1".into(), + command_id: "blocked-command".into(), + controller_seq: 1, + command_type: command_type.into(), + issued_at: "2026-09-01T00:00:00.000Z".into(), + deadline_at: None, + precondition: None, + payload: json!({"requestId":"input-1", "resolution":{"action":"submit"}}), + }); + assert_eq!(blocked.unwrap_err().to_string(), error.to_string()); + } + assert_eq!(fs::read(original.state_path()).unwrap(), disk); + drop(original); + + let mut recovered = AcpxCommandExecutor::with_runner_config(&directory, &config); + let events = recovered.poll_events().unwrap(); + let settlements: Vec<_> = events + .iter() + .filter(|event| event.event_type.starts_with("runtime_request.")) + .collect(); + assert_eq!(settlements.len(), 2); + assert_eq!(settlements[0].event_type, event_type); + assert_eq!(settlements[0].payload["requestId"], "input-1"); + assert_eq!( + settlements[0].executor_event_id, + staged.pending_events[0].executor_event_id + ); + assert_eq!(settlements[1].event_type, "runtime_request.expired"); + assert_eq!(settlements[1].payload["requestId"], "input-2"); + assert_eq!(settlements[1].payload["replayAllowed"], false); + assert!( + event_sequence(&settlements[0].executor_event_id).unwrap() + < event_sequence(&settlements[1].executor_event_id).unwrap() + ); + assert!(recovered + .resolve_request(&json!({"requestId":"input-1", "resolution":{"action":"submit"}})) + .is_err()); + assert_eq!(recovered.poll_events().unwrap(), events); + recovered.acknowledge_events(events.len()).unwrap(); + drop(recovered); + let mut recovered_again = AcpxCommandExecutor::with_runner_config(&directory, &config); + assert!(recovered_again.poll_events().unwrap().is_empty()); + assert!( + !marker.exists(), + "recovery must not restart a provider or replay input" + ); + fs::remove_dir_all(directory).unwrap(); + } + } + + #[test] + fn turn_control_rejects_nonstring_mode_before_provider_access() { + let directory = temporary_directory("control-mode"); + let config = test_config(&directory, None); + let mut executor = AcpxCommandExecutor::with_runner_config(&directory, &config); + for mode in [Value::Null, json!(1), json!(false), json!({})] { + let error = executor + .steer_turn( + "command-1", + &json!({"text":"change direction", "turnId":"turn-1", "mode":mode}), + ) + .err() + .unwrap(); + assert!(error.to_string().contains("mode must be a string")); + } + fs::remove_dir_all(directory).unwrap(); + } + #[test] fn retained_events_exposes_terminal_suffix_without_restoring_provider() { let directory = temporary_directory("retained-terminal-suffix"); @@ -2025,7 +2716,62 @@ mod tests { } #[test] - fn rejects_pi_before_process_launch() { + fn admits_candidate_descriptors_only_with_explicit_policy_and_exact_distribution() { + for (agent, package, version, digest, runtime_package, runtime_version, model) in [ + ( + "cursor", + "cursor-agent", + "2026.09.26-dd393fe", + "sha256:1157a5d071abbd57ab132f22bace75c65e84cc47a045b0023475488755e14899", + None, + None, + "explicit-model", + ), + ( + "copilot", + "@github/copilot", + "1.0.88", + "sha256:b18c01603dd0169d233140709cfaa8bf5304a03cf5de78ca4f625f30013e8457", + None, + None, + "explicit-model", + ), + ( + "pi", + "pi-acp", + "0.0.33", + "sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f", + Some("@earendil-works/pi-coding-agent"), + Some("0.84.2"), + "openrouter/deepseek/deepseek-v4-flash-0731", + ), + ] { + let mut value = descriptor("codex"); + value["agent"] = json!(agent); + value["model"] = json!(model); + value["agentServerPackage"] = json!(package); + value["agentServerVersion"] = json!(version); + value["agentRuntimePackage"] = json!(runtime_package); + value["agentRuntimeVersion"] = json!(runtime_version); + value["commandDigest"] = json!(digest); + let missing: AcpxProviderDescriptor = serde_json::from_value(value.clone()).unwrap(); + assert!(missing.validate(&context()).is_err()); + value["providerPolicy"] = json!({"readOnly":true}); + let valid: AcpxProviderDescriptor = serde_json::from_value(value.clone()).unwrap(); + valid.validate(&context()).unwrap(); + for field in ["model", "agentServerVersion", "commandDigest"] { + let mut wrong = value.clone(); + wrong[field] = json!(""); + let invalid: AcpxProviderDescriptor = serde_json::from_value(wrong).unwrap(); + assert!(invalid.validate(&context()).is_err()); + } + value["providerPolicy"] = json!({"readOnly":true, "protectedPaths":[]}); + assert!(serde_json::from_value::(value).is_err()); + } + } + + #[test] + fn rejects_pi_with_another_profile_identity_before_process_launch() { let mut pi = descriptor("codex"); pi["agent"] = json!("pi"); let pi: AcpxProviderDescriptor = serde_json::from_value(pi).unwrap(); @@ -2342,6 +3088,15 @@ mod tests { state.lifecycle = "turn_active".to_owned(); state.identity = Some(identity); state.active_turn_id = Some("turn-1".to_owned()); + state + .push(NormalizedProviderEvent { + event_type: "runtime_request.created".into(), + priority: EventPriority::P0, + payload: json!({"request":pending_input_request("restart-input")}), + }) + .unwrap(); + state.pending_events.clear(); // The controller already acknowledged creation. + let config = test_config(&directory, Some(launch_profile)); let mut original = AcpxCommandExecutor::with_runner_config(&directory, &config); original.state = Some(state); @@ -2403,11 +3158,15 @@ mod tests { assert_eq!(snapshot.result["status"], "closed"); assert!(!marker.exists()); let events = recovered.poll_events().unwrap(); - assert_eq!(events[0].event_type, "turn.failed"); - assert_eq!(events[0].payload["providerShutdownFailed"], true); - assert_eq!(events[1].event_type, "run.terminal"); - assert_eq!(events[1].payload["schema"], "paperclip.prp.terminal.v1"); - assert_eq!(events[1].payload["turnTerminalState"], "failed"); + assert_eq!(events[0].event_type, "runtime_request.expired"); + assert_eq!(events[0].payload["requestId"], "restart-input"); + assert_eq!(events[0].payload["replayAllowed"], false); + assert_eq!(events[1].event_type, "turn.failed"); + assert_eq!(events[1].payload["providerShutdownFailed"], true); + assert_eq!(events[2].event_type, "run.terminal"); + assert_eq!(events[2].payload["schema"], "paperclip.prp.terminal.v1"); + assert_eq!(events[2].payload["turnTerminalState"], "failed"); + assert_eq!(recovered.poll_events().unwrap(), events); let cleanup_error = recovered .shutdown() .expect_err("cleanup must not succeed while the original lifetime remains active"); diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_session.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_session.rs index c64d02bc5b..c92f044940 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_session.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_session.rs @@ -51,6 +51,12 @@ pub struct AcpxProviderSessionIdentity { pub provider_lifetime_fence_candidates: [u16; 3], } +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct AcpxProviderRuntimePolicy { + pub read_only: bool, +} + #[derive(Clone, Debug)] pub struct AcpxProviderSessionConfig { pub transport: AcpxSidecarTransportConfig, @@ -63,6 +69,7 @@ pub struct AcpxProviderSessionConfig { pub working_directory: PathBuf, pub permission_mode: AcpxPermissionMode, pub permission_mode_pinned: bool, + pub provider_policy: Option, pub system_instructions: String, pub runtime_context: Value, pub tool_set: AuthorizedToolSet, @@ -76,9 +83,11 @@ impl AcpxProviderSessionConfig { "claude" => "claude-sonnet-5", "grok" => "grok-4.7", "codex" => "gpt-5.6-sol", + "pi" => "openrouter/deepseek/deepseek-v4-flash-0731", + "cursor" | "copilot" => self.model.as_str(), _ => { return Err(LocalRunnerError::invalid( - "ACPX agent must be claude, codex, or grok", + "ACPX agent must name a known immutable profile", )) } }; @@ -89,6 +98,13 @@ impl AcpxProviderSessionConfig { ))); } validate_text(&self.model, MAX_MODEL_CHARS, "ACPX model")?; + if matches!(self.agent.as_str(), "pi" | "cursor" | "copilot") + && self.provider_policy.is_none() + { + return Err(LocalRunnerError::invalid( + "ACPX candidate requires explicit provider read-only policy", + )); + } validate_stable_id(&self.run_id, SHORT_STABLE_ID_CHARS, "ACPX run id")?; validate_stable_id( &self.normalized_session_id, @@ -209,6 +225,30 @@ impl AcpxProviderSessionIdentity { } } +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct AcpxTurnControlCapabilities { + pub steering: bool, + pub queued_follow_up: bool, +} + +fn verified_turn_controls( + value: Option<&Value>, + agent: &str, +) -> Result { + let Some(value) = value else { + return Ok(AcpxTurnControlCapabilities::default()); + }; + let controls: AcpxTurnControlCapabilities = serde_json::from_value(value.clone()) + .map_err(|_| LocalRunnerError::invalid("ACPX negotiated turn controls are malformed"))?; + if agent != "pi" && (controls.steering || controls.queued_follow_up) { + return Err(LocalRunnerError::invalid( + "ACPX profile cannot advertise these turn controls", + )); + } + Ok(controls) +} + pub struct AcpxProviderSession { transport: AcpxSidecarTransport, config: AcpxProviderSessionConfig, @@ -216,6 +256,7 @@ pub struct AcpxProviderSession { tool_bridge: ProviderToolBridge, reserved_tool_bridge: ProviderToolBridge, identity: AcpxProviderSessionIdentity, + turn_controls: AcpxTurnControlCapabilities, catalog_revision: u64, working_directory: PathBuf, closed: bool, @@ -235,7 +276,7 @@ impl AcpxProviderSession { let mut transport = AcpxSidecarTransport::start_for_agent(&config.transport, &config.agent)?; let bootstrap = bootstrap(&mut transport, config); - let (identity, state) = match bootstrap { + let (identity, state, turn_controls) = match bootstrap { Ok(value) => value, Err(error) => { let cleanup = transport.shutdown(); @@ -249,6 +290,7 @@ impl AcpxProviderSession { tool_bridge, reserved_tool_bridge, identity, + turn_controls, catalog_revision: config.catalog_revision, working_directory: config.working_directory.clone(), closed: false, @@ -264,6 +306,10 @@ impl AcpxProviderSession { &self.identity } + pub fn turn_control_capabilities(&self) -> AcpxTurnControlCapabilities { + self.turn_controls + } + pub fn state(&self) -> &AcpxProviderState { &self.state } @@ -378,6 +424,11 @@ impl AcpxProviderSession { "ACPX sidecar did not confirm the requested turn", ))); } + self.turn_controls = + match verified_turn_controls(response.get("turnControls"), &self.config.agent) { + Ok(controls) => controls, + Err(error) => return Err(self.fail_closed(error)), + }; if let Err(error) = self.state.begin_turn(turn_id) { return Err(self.fail_closed(error)); } @@ -386,6 +437,54 @@ impl AcpxProviderSession { Ok(response) } + pub fn steer_turn( + &mut self, + turn_id: &str, + control_id: &str, + mode: &str, + message: &str, + ) -> Result { + self.ensure_open()?; + validate_stable_id(turn_id, DURABLE_STABLE_ID_CHARS, "ACPX turn id")?; + validate_stable_id(control_id, SHORT_STABLE_ID_CHARS, "ACPX control id")?; + if !matches!(mode, "steer" | "follow_up") + || message.trim().is_empty() + || message.len() > 65_536 + || message.contains('\0') + { + return Err(LocalRunnerError::invalid( + "ACPX turn control violates its bounded contract", + )); + } + let supported = if mode == "steer" { + self.turn_controls.steering + } else { + self.turn_controls.queued_follow_up + }; + if !supported { + return Err(LocalRunnerError::invalid( + "ACPX turn control was not negotiated", + )); + } + self.state.reserve_turn_control(turn_id, control_id)?; + // The sidecar checks the negotiated live capability. An error may follow + // delivery, so the reservation must never be released for automatic retry. + let response = self.transport.request( + GeneratedAcpxSidecarCommand::TurnSteer, + json!({"turnId": turn_id, "controlId": control_id, "mode": mode, "message": message}), + )?; + if response.get("accepted").and_then(Value::as_bool) != Some(true) + || response.get("turnId").and_then(Value::as_str) != Some(turn_id) + || response.get("controlId").and_then(Value::as_str) != Some(control_id) + || response.get("mode").and_then(Value::as_str) != Some(mode) + { + return Err(self.fail_closed(LocalRunnerError::invalid( + "ACPX sidecar did not acknowledge the exact turn control", + ))); + } + Ok(response) + } + pub fn interrupt_turn( &mut self, turn_id: &str, @@ -562,9 +661,16 @@ impl AcpxProviderSession { ); } AcpxProviderStateEvent::PermissionRequest { .. } => { - return Err(self.fail_closed(LocalRunnerError::invalid( - "ACPX permission request violated the pinned runner policy", - ))); + if self.config.agent == "codex" + || matches!( + self.config.permission_mode, + AcpxPermissionMode::ApproveAll | AcpxPermissionMode::DenyAll + ) + { + return Err(self.fail_closed(LocalRunnerError::invalid( + "ACPX permission request violated the pinned runner policy", + ))); + } } _ => {} } @@ -629,6 +735,52 @@ impl AcpxProviderSession { Ok(()) } + pub fn resolve_permission( + &mut self, + request_id: &str, + turn_id: &str, + resolution: &Value, + ) -> Result<(), LocalRunnerError> { + self.ensure_bound_turn(turn_id)?; + let details = self.state.pending_permission(request_id).ok_or_else(|| { + LocalRunnerError::invalid("ACPX permission request is stale or unknown") + })?; + let object = resolution.as_object().ok_or_else(|| { + LocalRunnerError::invalid("ACPX permission resolution must be an object") + })?; + let action = object.get("action").and_then(Value::as_str).unwrap_or(""); + if object.len() != 1 + || !matches!( + action, + "accept" | "accept_for_session" | "decline" | "cancel" + ) + || !details + .get("choices") + .and_then(Value::as_array) + .is_some_and(|choices| { + choices + .iter() + .any(|choice| choice.get("key").and_then(Value::as_str) == Some(action)) + }) + { + return Err(LocalRunnerError::invalid( + "ACPX permission resolution is not an offered choice", + )); + } + let mut next_state = self.state.clone(); + next_state.complete_permission(request_id)?; + let response = match self.transport.request( + GeneratedAcpxSidecarCommand::PermissionResolve, + json!({"requestId":request_id,"turnId":turn_id,"resolution":resolution}), + ) { + Ok(response) => response, + Err(error) => return Err(self.fail_closed(error)), + }; + self.verify_resolution(&response, "permission")?; + self.state = next_state; + Ok(()) + } + pub fn resolve_input( &mut self, request_id: &str, @@ -786,12 +938,13 @@ impl AcpxProviderSession { &restart_config.transport, &restart_config.agent, )?; - let (replacement_identity, _) = match bootstrap(&mut replacement, &restart_config) { - Ok(value) => value, - Err(error) => { - return Err(self.reject_replacement(replacement, error)); - } - }; + let (replacement_identity, _, turn_controls) = + match bootstrap(&mut replacement, &restart_config) { + Ok(value) => value, + Err(error) => { + return Err(self.reject_replacement(replacement, error)); + } + }; if replacement_identity != self.identity { return Err(self.reject_replacement( replacement, @@ -801,6 +954,7 @@ impl AcpxProviderSession { )); } self.transport = replacement; + self.turn_controls = turn_controls; self.transport_terminated = false; Ok(()) } @@ -1037,7 +1191,14 @@ impl Drop for AcpxProviderSession { fn bootstrap( transport: &mut AcpxSidecarTransport, config: &AcpxProviderSessionConfig, -) -> Result<(AcpxProviderSessionIdentity, AcpxProviderState), LocalRunnerError> { +) -> Result< + ( + AcpxProviderSessionIdentity, + AcpxProviderState, + AcpxTurnControlCapabilities, + ), + LocalRunnerError, +> { let sidecar_tools = sidecar_run_tool_operations(&config.tool_set); let initialized = transport.request( GeneratedAcpxSidecarCommand::Initialize, @@ -1055,6 +1216,7 @@ fn bootstrap( "model": config.model, "permissionMode": config.permission_mode, "permissionModePinned": config.permission_mode_pinned, + "providerPolicy": config.provider_policy, "systemInstructions": config.system_instructions, "runtimeContext": config.runtime_context, "tools": &sidecar_tools, @@ -1062,6 +1224,7 @@ fn bootstrap( }), )?; let identity = verify_open_response(&opened, transport.process_id(), config)?; + let turn_controls = verified_turn_controls(opened.get("turnControls"), &config.agent)?; let attached = transport.request( GeneratedAcpxSidecarCommand::RunAttach, @@ -1078,7 +1241,11 @@ fn bootstrap( "ACPX sidecar did not confirm the requested run attachment", )); } - Ok((identity, AcpxProviderState::new(&config.run_id)?)) + Ok(( + identity, + AcpxProviderState::new(&config.run_id)?, + turn_controls, + )) } fn verify_initialize_response(value: &Value, process_id: u32) -> Result<(), LocalRunnerError> { @@ -1270,6 +1437,35 @@ fn with_cleanup_error( #[cfg(test)] mod tests { + #[test] + fn turn_controls_require_exact_live_pi_capability_fields() { + use super::*; + assert_eq!( + verified_turn_controls(None, "pi").unwrap(), + AcpxTurnControlCapabilities::default() + ); + assert!( + verified_turn_controls(Some(&json!({"steering":true,"queuedFollowUp":true})), "pi") + .unwrap() + .steering + ); + for value in [ + Value::Null, + json!({}), + json!({"steering":1,"queuedFollowUp":false}), + json!({"steering":true,"queuedFollowUp":true,"extra":true}), + ] { + assert!(verified_turn_controls(Some(&value), "pi").is_err()); + } + for agent in ["codex", "claude", "cursor", "copilot"] { + assert!(verified_turn_controls( + Some(&json!({"steering":true,"queuedFollowUp":false})), + agent + ) + .is_err()); + } + } + use super::*; #[test] diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_state.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_state.rs index 2a68007e99..876cf9d00f 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_state.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_state.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use serde_json::Value; @@ -61,6 +61,12 @@ pub enum AcpxProviderStateEvent { question_set: Value, origin: Option, }, + RuntimeRequestEnded { + request_id: String, + question_set: Option, + origin: Option, + status: AcpxTurnStatus, + }, SemanticResult(AcpxSemanticResult), AssistantMessage { turn_id: String, @@ -84,6 +90,7 @@ struct PendingInput { runtime_request_id: String, value_bytes: usize, question_set: Value, + origin: Option, } /// Reduces validated sidecar events into bounded provider state. @@ -101,10 +108,11 @@ pub struct AcpxProviderState { assistant_message_id: Option, pending_tools: BTreeMap, pending_tool_input_bytes: usize, - pending_permissions: BTreeMap, + pending_permissions: BTreeMap, pending_inputs: BTreeMap, pending_runtime_request_bytes: usize, semantic_result: Option, + attempted_turn_controls: BTreeSet, } impl AcpxProviderState { @@ -121,6 +129,7 @@ impl AcpxProviderState { pending_inputs: BTreeMap::new(), pending_runtime_request_bytes: 0, semantic_result: None, + attempted_turn_controls: BTreeSet::new(), }) } @@ -165,6 +174,28 @@ impl AcpxProviderState { || !self.pending_inputs.is_empty() } + pub(crate) fn reserve_turn_control( + &mut self, + turn_id: &str, + control_id: &str, + ) -> Result<(), LocalRunnerError> { + if self.active_turn_id() != Some(turn_id) { + return Err(LocalRunnerError::invalid( + "ACPX turn control named a stale or inactive turn", + )); + } + if self.attempted_turn_controls.contains(control_id) { + return Err(LocalRunnerError::invalid( + "ACPX turn control was already attempted", + )); + } + if self.attempted_turn_controls.len() >= 1024 { + return Err(LocalRunnerError::invalid("ACPX turn control limit reached")); + } + self.attempted_turn_controls.insert(control_id.to_owned()); + Ok(()) + } + pub fn begin_turn(&mut self, turn_id: impl Into) -> Result<(), LocalRunnerError> { if self.scope.active_turn_id().is_some() || !self.pending_tools.is_empty() @@ -185,6 +216,7 @@ impl AcpxProviderState { self.assistant_text.clear(); self.assistant_message_id = None; self.semantic_result = None; + self.attempted_turn_controls.clear(); Ok(()) } @@ -194,6 +226,16 @@ impl AcpxProviderState { ) -> Result, LocalRunnerError> { let payload = decode_acpx_event(&self.scope, event)?; match payload { + AcpxEventPayload::RichActivity { + event_type, + payload, + } => Ok(vec![AcpxProviderStateEvent::Activity( + NormalizedProviderEvent { + event_type, + priority: crate::durable::EventPriority::P1, + payload, + }, + )]), AcpxEventPayload::Runtime { kind, tool_operation, @@ -224,7 +266,7 @@ impl AcpxProviderState { )); } self.pending_permissions - .insert(request_id.clone(), value_bytes); + .insert(request_id.clone(), (value_bytes, details.clone())); self.pending_runtime_request_bytes += value_bytes; Ok(vec![AcpxProviderStateEvent::PermissionRequest { request_id, @@ -238,7 +280,8 @@ impl AcpxProviderState { question_set, origin, } => { - let value_bytes = value_bytes(&question_set)?; + let value_bytes = value_bytes(&question_set)? + + origin.as_ref().map(value_bytes).transpose()?.unwrap_or(0); self.admit_runtime_request(&request_id, value_bytes)?; let runtime_request_id = project_acpx_runtime_request_id(&request_id) .expect("a decoded ACPX input request has a bounded identity"); @@ -260,6 +303,7 @@ impl AcpxProviderState { runtime_request_id, value_bytes, question_set: question_set.clone(), + origin: origin.clone(), }, ) .is_some() @@ -320,8 +364,8 @@ impl AcpxProviderState { .expect("a decoded terminal event has a turn binding") .to_owned(); self.scope.clear_turn(&turn_id)?; + let mut events = self.end_pending_runtime_requests(status); self.clear_pending_requests(); - let mut events = Vec::new(); if status == AcpxTurnStatus::Completed && !self.assistant_text.is_empty() { events.push(AcpxProviderStateEvent::AssistantMessage { turn_id: turn_id.clone(), @@ -379,7 +423,7 @@ impl AcpxProviderState { } pub fn complete_permission(&mut self, request_id: &str) -> Result<(), LocalRunnerError> { - let value_bytes = self.pending_permissions.remove(request_id).ok_or_else(|| { + let (value_bytes, _) = self.pending_permissions.remove(request_id).ok_or_else(|| { LocalRunnerError::invalid("ACPX permission result has no pending request") })?; self.pending_runtime_request_bytes = self @@ -388,6 +432,12 @@ impl AcpxProviderState { Ok(()) } + pub fn pending_permission(&self, request_id: &str) -> Option<&Value> { + self.pending_permissions + .get(request_id) + .map(|(_, details)| details) + } + pub fn complete_input(&mut self, request_id: &str) -> Result<(), LocalRunnerError> { let provider_request_id = self .pending_inputs @@ -571,6 +621,31 @@ impl AcpxProviderState { Ok(()) } + pub(crate) fn end_pending_runtime_requests( + &mut self, + status: AcpxTurnStatus, + ) -> Vec { + let mut events = Vec::new(); + for (request_id, _) in std::mem::take(&mut self.pending_permissions) { + events.push(AcpxProviderStateEvent::RuntimeRequestEnded { + request_id, + question_set: None, + origin: None, + status, + }); + } + for (request_id, pending) in std::mem::take(&mut self.pending_inputs) { + events.push(AcpxProviderStateEvent::RuntimeRequestEnded { + request_id, + question_set: Some(pending.question_set), + origin: pending.origin, + status, + }); + } + self.pending_runtime_request_bytes = 0; + events + } + fn clear_pending_requests(&mut self) { self.pending_tools.clear(); self.pending_tool_input_bytes = 0; diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs index f524d8a296..9bb2c3b6c0 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs @@ -99,9 +99,12 @@ impl AcpxSidecarTransport { "claude" => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"], "codex" => &["OPENAI_API_KEY", "CODEX_API_KEY"], "grok" => &["XAI_API_KEY", "PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET"], + "pi" => &["OPENROUTER_API_KEY"], + "cursor" => &["CURSOR_API_KEY", "CURSOR_AUTH_TOKEN"], + "copilot" => &["COPILOT_GITHUB_TOKEN"], _ => { return Err(LocalRunnerError::invalid( - "ACPX sidecar credentials require a qualified claude, codex, or grok agent", + "ACPX sidecar credentials require a known agent profile", )) } }; @@ -128,6 +131,11 @@ impl AcpxSidecarTransport { "PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT", "PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST", ]; + if matches!(agent, "pi" | "cursor" | "copilot") { + // Credential values alone are not proof of an explicit task binding. + // The sidecar checks this controller-minted provider/session marker. + keys.push("PAPERCLIP_ACPX_CREDENTIAL_BINDING"); + } keys.extend_from_slice(credential_keys); Self::start_with_environment_keys(config, &keys) } @@ -688,6 +696,9 @@ fn response_error_classification(error: &ResponseError) -> &'static str { "AGENT_STARTUP_FAILED.OTHER" => return "agent_startup_other", "AGENT_DISCONNECTED" => return "agent_disconnected", "AUTH_REQUIRED" => return "authentication_required", + "COPILOT_AUTH_REQUIRED" => return "authentication_required", + "COPILOT_ENTITLEMENT_DENIED" => return "provider_entitlement_denied", + "COPILOT_MODEL_UNAVAILABLE" => return "requested_model_unsupported", "SESSION_RESUME_REQUIRED" => return "session_resume_required", "SESSION_MODE_REPLAY_FAILED" => return "session_mode_replay_failed", "SESSION_MODEL_REPLAY_FAILED" => return "session_model_replay_failed", @@ -798,6 +809,30 @@ mod tests { } } + #[test] + fn candidate_auth_diagnostics_use_only_closed_codes_and_never_provider_text() { + for (code, expected) in [ + ("COPILOT_AUTH_REQUIRED", "authentication_required"), + ("COPILOT_ENTITLEMENT_DENIED", "provider_entitlement_denied"), + ("COPILOT_MODEL_UNAVAILABLE", "requested_model_unsupported"), + ("COPILOT_AUTH_REQUIRED_EXTRA", "unclassified"), + ("COPILOT_REQUEST_FAILED", "unclassified"), + ("UNKNOWN_CANDIDATE_FAILURE", "unclassified"), + ] { + let error = ResponseError { + code: code.to_owned(), + message: + "private-token-canary COPILOT_AUTH_REQUIRED https://user:secret@example.invalid" + .to_owned(), + retryable: false, + }; + let classification = response_error_classification(&error); + assert_eq!(classification, expected); + assert!(!classification.contains("canary")); + assert!(!classification.contains("secret")); + } + } + #[test] fn classifies_only_allowlisted_internal_sidecar_failures() { let error = |code: &str, message: &str| ResponseError { diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-acpx-sidecar.rs b/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-acpx-sidecar.rs index 2d84a6dbe7..58e9429653 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-acpx-sidecar.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-acpx-sidecar.rs @@ -148,6 +148,8 @@ fn run() -> Result<(), Box> { "url": std::env::var("PAPERCLIP_NATIVE_MCP_URL").ok(), "hasToken": std::env::var("PAPERCLIP_NATIVE_MCP_TOKEN").is_ok(), "hasUnrelatedSecret": std::env::var("UNRELATED_EVAL_SECRET").is_ok(), + "credentialBinding": std::env::var("PAPERCLIP_ACPX_CREDENTIAL_BINDING").ok(), + "credentialKeys": (["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN", "OPENAI_API_KEY", "CODEX_API_KEY", "OPENROUTER_API_KEY", "CURSOR_API_KEY", "CURSOR_AUTH_TOKEN", "COPILOT_GITHUB_TOKEN", "GITHUB_TOKEN", "GH_TOKEN"].into_iter().filter(|key| std::env::var(key).is_ok()).collect::>()), } }), )?; @@ -204,6 +206,10 @@ fn run() -> Result<(), Box> { | "goals" | "bootstrap-wrong-model" | "bootstrap-wrong-run" + | "controls" + | "controls-wrong-ack" + | "controls-lazy" + | "controls-downgrade" | "turns" | "turns-wrong-turn" | "turns-wrong-cancel" @@ -226,6 +232,8 @@ fn run() -> Result<(), Box> { | "turns-mismatched-reserved-result-terminal" | "turns-unauthorized-tool" | "turns-permission" + | "permissions-interactive" + | "permissions-wrong-ack" | "resolutions" | "resolutions-error-redaction" | "resolutions-projected-id" @@ -387,7 +395,12 @@ fn run() -> Result<(), Box> { )?; next_sequence += 1; } - if command == "turn.start" && mode == "turns-permission" { + if command == "turn.start" + && matches!( + mode, + "turns-permission" | "permissions-interactive" | "permissions-wrong-ack" + ) + { write_turn_event( &mut stdout, next_sequence, @@ -398,6 +411,7 @@ fn run() -> Result<(), Box> { "requestId":"permission-1", "kind":"execute", "title":"Run a command?", + "choices":[{"key":"accept","label":"Allow once"},{"key":"cancel","label":"Cancel"}], }), )?; next_sequence += 1; @@ -671,6 +685,9 @@ fn bootstrap_success( profile_digest: &str, ) -> Value { if command == "permission.resolve" { + if mode.starts_with("permissions-") { + return json!({"protocolVersion": GENERATED_ACPX_SIDECAR_PROTOCOL_VERSION, "id": id, "ok":true, "result":{"resolved": mode != "permissions-wrong-ack"}}); + } return json!({ "protocolVersion": GENERATED_ACPX_SIDECAR_PROTOCOL_VERSION, "id": id, @@ -717,6 +734,7 @@ fn bootstrap_success( "providerLifetimeFenceCandidates": [60001, 60002, 60003], }, "status": {}, + "turnControls": {"steering": matches!(mode, "controls" | "controls-wrong-ack" | "controls-downgrade"), "queuedFollowUp":matches!(mode, "controls" | "controls-wrong-ack" | "controls-downgrade")}, }) } "run.attach" => json!({ @@ -724,8 +742,13 @@ fn bootstrap_success( "catalogRevision": params.get("catalogRevision"), }), "turn.start" => json!({ + "turnControls": {"steering": matches!(mode, "controls" | "controls-wrong-ack" | "controls-lazy"), "queuedFollowUp":matches!(mode, "controls" | "controls-wrong-ack" | "controls-lazy")}, "turnId": if mode == "turns-wrong-turn" { "wrong-turn" } else { params.get("turnId").and_then(Value::as_str).unwrap_or("missing") }, }), + "turn.steer" => json!({ + "accepted": true, "turnId": params.get("turnId"), "controlId": params.get("controlId"), + "mode": if mode == "controls-wrong-ack" { json!("wrong") } else { params["mode"].clone() }, + }), "turn.cancel" => json!({"cancelled":mode != "turns-wrong-cancel"}), "session.suspend" => json!({ "suspended":mode != "suspend-wrong-ack", diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs b/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs index bac103d0ff..ce3b9e3e4b 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs @@ -33,9 +33,9 @@ fn send(value: Value) -> io::Result<()> { stdout.flush() } -fn send_split_event_burst(state: &FakeState) -> io::Result<()> { +fn send_split_event_burst(state: &FakeState, count: usize) -> io::Result<()> { let turn_id = state.active_turn_id.as_deref().unwrap_or("provider-turn-1"); - for index in 0..96 { + for index in 0..count { send(json!({ "method": "item/agentMessage/delta", "params": { @@ -619,6 +619,13 @@ fn run() -> Result<(), Box> { let emit_runtime_elicitation = args.iter().any(|value| value == "--runtime-elicitation"); let emit_structured_activity = args.iter().any(|value| value == "--structured-activity"); let emit_split_event_burst = args.iter().any(|value| value == "--split-event-burst"); + let split_event_prefix_count = argument(&args, "--split-event-prefix-count") + .map(|value| value.parse::()) + .transpose()? + .unwrap_or(96); + if !(1..=4096).contains(&split_event_prefix_count) { + return Err("split event prefix count must be between 1 and 4096".into()); + } let split_event_suffix_count = argument(&args, "--split-event-suffix-count") .map(|value| value.parse::()) .transpose()? @@ -1669,7 +1676,7 @@ fn run() -> Result<(), Box> { send_structured_activity(&state)?; finish_turn(&state_path, &mut state, "completed")?; } else if emit_split_event_burst { - send_split_event_burst(&state)?; + send_split_event_burst(&state, split_event_prefix_count)?; } else if emit_question { send_question(&state)?; } else if !hold_turn { diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs index c0742fe7f6..437e0ea01a 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs @@ -3787,11 +3787,17 @@ fn codex_question_set( } let option_id = format!("option-{}", index + 1); labels.insert(option_id.clone(), label.chars().take(240).collect()); - Some(json!({ + let mut canonical_option = json!({ "id": option_id, "label": label.chars().take(240).collect::(), - "description": option.get("description").and_then(Value::as_str).map(|value| value.chars().take(1000).collect::()), - })) + }); + // Native descriptions are optional/nullable. Canonical input + // permits an omitted description or a string, never null. + if let Some(description) = option.get("description").and_then(Value::as_str) { + canonical_option["description"] = + json!(description.chars().take(1000).collect::()); + } + Some(canonical_option) }) .collect::>(); if !options.is_empty() && canonical_options.len() != options.len() { @@ -4628,6 +4634,44 @@ done ); } + #[test] + fn codex_optional_option_descriptions_produce_schema_valid_resolvable_input() { + let (_, question_set, option_labels) = codex_question_set( + &json!(42), + &json!({"questions":[{ + "id":"environment", "question":"Where?", + "options":[ + {"label":"Staging"}, + {"label":"Production", "description":null}, + {"label":"Preview", "description":"Temporary deployment"} + ] + }]}), + ) + .unwrap(); + let options = question_set["questions"][0]["options"].as_array().unwrap(); + assert!(options[0].get("description").is_none()); + assert!(options[1].get("description").is_none()); + assert_eq!(options[2]["description"], "Temporary deployment"); + let pending = PendingRuntimeRequest { + rpc_id: json!(42), + turn_id: "turn-1".to_owned(), + method: "item/tool/requestUserInput".to_owned(), + params: Value::Null, + question_set, + option_labels, + retained_bytes: 0, + }; + for (index, label) in ["Staging", "Production", "Preview"].iter().enumerate() { + // The canonical response validator also validates the retained + // question-set schema, just as durable presentation does. + let native = codex_question_response(&pending, &json!({ + "schema":"paperclip.question_response.v1", + "answers":{"environment":{"selectedOptionIds":[format!("option-{}", index + 1)]}} + })).unwrap(); + assert_eq!(native["answers"]["environment"]["answers"], json!([label])); + } + } + #[test] fn converts_codex_questions_and_responses_without_provider_leakage() { let (request_id, question_set, labels) = codex_question_set( diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/mod.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/mod.rs index bc28290fef..24418539af 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/mod.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/mod.rs @@ -16,8 +16,8 @@ pub use runner::{ TerminalDeliveryReconciliation, }; pub(crate) use state::{ - create_private_temporary_file, open_private_regular_file, redact_text, - sanitize_semantic_tool_input, sanitize_value, verify_private_directory, + create_private_temporary_file, open_private_regular_file, redact_sensitive_text_values, + redact_text, sanitize_semantic_tool_input, sanitize_value, verify_private_directory, }; pub use state::{ Command, CommandDisposition, DurableState, DurableStateStore, EventPriority, diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs index 8f4eab51ca..0a1d9bdf5c 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs @@ -1996,6 +1996,125 @@ mod tests { } } + #[test] + fn response_persistence_failure_with_blocked_drain_remains_indeterminate_on_replay() { + struct PersistenceFailedResponseExecutor { + delivered_responses: usize, + retained_reads: usize, + } + impl CommandExecutor for PersistenceFailedResponseExecutor { + fn execute( + &mut self, + command: &Command, + ) -> Result { + assert_eq!(command.command_type, "request.resolve"); + assert_eq!(command.payload["requestId"], "input-1"); + // Model a real pipe delivery ACK followed by an uncertain + // provider-state write. Its staged settlement is not durable. + self.delivered_responses += 1; + Err(DurableRunnerError::invalid( + "ACPX response state persistence failed after delivery", + )) + } + + fn retained_events(&mut self) -> Result, DurableRunnerError> { + self.retained_reads += 1; + Err(DurableRunnerError::invalid( + "ACPX persistence latch blocks retained settlement", + )) + } + + fn poll_events(&mut self) -> Result, DurableRunnerError> { + panic!("failed response settlement must not poll or restore the provider") + } + + fn acknowledge_events(&mut self, _: usize) -> Result<(), DurableRunnerError> { + panic!("an uncertain settlement must not receive a journal ACK") + } + } + + let directory = std::env::temp_dir().join(format!( + "paperclip-response-persistence-failure-{}", + uuid::Uuid::new_v4() + )); + let config = config(directory.clone()); + let store = DurableStateStore::new(&directory).unwrap(); + let (mut state, _) = store.load_or_create(&config).unwrap(); + let mut executor = PersistenceFailedResponseExecutor { + delivered_responses: 0, + retained_reads: 0, + }; + let mut resolve = command("request.resolve"); + resolve.payload = json!({ + "requestId": "input-1", + "resolution": {"action": "submit", "response": {"answer": "accepted"}}, + }); + + let failure = + process_command(&mut state, &store, &config, &mut executor, &resolve).unwrap_err(); + assert!(failure + .to_string() + .starts_with("ACPX response state persistence failed after delivery")); + assert!(failure + .to_string() + .contains("retained failure evidence remains uncommitted")); + assert!(failure + .to_string() + .contains("ACPX persistence latch blocks retained settlement")); + assert_eq!(executor.delivered_responses, 1); + assert_eq!(executor.retained_reads, 1); + assert_eq!( + state.processed_commands[&resolve.command_id].status, + "pending" + ); + assert!(state.outbox.is_empty()); + assert_eq!(state.highest_source_seq(), 0); + + // Inspect the actual journal before recovery changes pending into + // indeterminate. Neither a failed/completed result nor a resolved event + // may replace the pre-effect marker while the provider snapshot is unsure. + let journal: DurableState = + serde_json::from_slice(&fs::read(store.path()).unwrap()).unwrap(); + assert_eq!( + journal.processed_commands[&resolve.command_id].status, + "pending" + ); + assert!(journal.outbox.is_empty()); + assert_eq!(journal.highest_source_seq(), 0); + + let pending = process_command(&mut state, &store, &config, &mut executor, &resolve) + .unwrap() + .0; + assert_eq!(pending.status, "pending"); + assert_eq!(executor.delivered_responses, 1); + assert_eq!(executor.retained_reads, 1); + + let (mut recovered, existed) = store.load_or_create(&config).unwrap(); + assert!(existed); + assert_eq!( + recovered.processed_commands[&resolve.command_id].status, + "indeterminate" + ); + let mut replacement = CountingExecutor { calls: 0 }; + let replay = process_command(&mut recovered, &store, &config, &mut replacement, &resolve) + .unwrap() + .0; + assert_eq!(replay.status, "indeterminate"); + assert_eq!(replay.result["code"], "execution_indeterminate"); + assert_eq!( + replacement.calls, 0, + "recovery must never resend the response" + ); + assert!(recovered.outbox.is_empty()); + let (reloaded_again, _) = store.load_or_create(&config).unwrap(); + assert_eq!( + reloaded_again.processed_commands[&resolve.command_id], + replay + ); + assert!(reloaded_again.outbox.is_empty()); + fs::remove_dir_all(directory).unwrap(); + } + #[test] fn unlimited_lifetime_keeps_attempts_bounded_after_weeks_of_work() { let mut config = config(std::env::temp_dir()); diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs index e137a20af7..9d160c5ebe 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs @@ -532,6 +532,8 @@ impl DurableState { "durable identity or validation semantics contain credential-shaped material", )); } + let sanitized_payload = + preserve_bounded_display_content(event_type.as_str(), &payload, sanitized_payload)?; let sanitized_payload = finalize_semantic_tool_input_payload(event_type.as_str(), &payload, sanitized_payload)?; @@ -1592,6 +1594,79 @@ pub(crate) fn sanitize_value(value: &Value) -> Value { } } +fn sanitize_bounded_display_value(value: &Value) -> Value { + match value { + Value::Object(object) => Value::Object( + object + .iter() + .map(|(key, value)| { + let value = if protocol_authorization_boundary(key, value) { + value.clone() + } else if sensitive_key(key, value) { + Value::String("[REDACTED]".to_owned()) + } else { + sanitize_bounded_display_value(value) + }; + (key.clone(), value) + }) + .collect(), + ), + Value::Array(values) => { + Value::Array(values.iter().map(sanitize_bounded_display_value).collect()) + } + Value::String(text) => Value::String(redact_sensitive_text_values(text)), + value => value.clone(), + } +} + +fn preserve_bounded_display_content( + event_type: &str, + original: &Value, + mut sanitized: Value, +) -> Result { + use crate::acpx_event_payload::{has_bounded_rich_display_shape, validate_question_set}; + + // Rich display payloads are already closed and byte-bounded by their exact + // protocol schemas. Applying a diagnostic preview cap again would silently + // change complete output into a truncated artifact without its metadata. + if has_bounded_rich_display_shape(event_type, original) { + let preserved = sanitize_bounded_display_value(original); + if !has_bounded_rich_display_shape(event_type, &preserved) { + return Err(DurableRunnerError::invalid( + "redacted display activity exceeds its schema bounds", + )); + } + return Ok(preserved); + } + if matches!( + event_type, + "runtime_request.created" | "runtime_request.expired" | "runtime_request.cancelled" + ) { + if let Some(input) = original.pointer("/request/input") { + if input.get("schema").and_then(Value::as_str) == Some("paperclip.question_set.v1") { + validate_question_set(input) + .map_err(|error| DurableRunnerError::invalid(error.to_string()))?; + let mut preserved = sanitize_bounded_display_value(input); + if &preserved != input { + let description = preserved + .get("description") + .and_then(Value::as_str) + .unwrap_or(""); + preserved["description"] = Value::String(format!( + "Sensitive values were redacted from this request.\n\n{description}" + )); + } + validate_question_set(&preserved) + .map_err(|error| DurableRunnerError::invalid(error.to_string()))?; + if let Some(target) = sanitized.pointer_mut("/request/input") { + *target = preserved; + } + } + } + } + Ok(sanitized) +} + pub(crate) fn sanitize_semantic_tool_input( operation_id: &str, input: &Value, @@ -1741,7 +1816,7 @@ pub(crate) fn redact_text(input: &str) -> String { redacted } -fn redact_sensitive_text_values(input: &str) -> String { +pub(crate) fn redact_sensitive_text_values(input: &str) -> String { redact_sensitive_text_values_with_context(input, false) } @@ -3146,6 +3221,78 @@ mod tests { assert_eq!(payload["diagnostic"], json!("token=[REDACTED]")); } + #[test] + fn durable_display_content_preserves_complete_utf8_plans_and_tool_output() { + let mut config = config(PathBuf::from("unused")); + config.max_outbox_bytes = 512 * 1024; + config.max_frame_bytes = 256 * 1024; + let mut state = DurableState::new(&config); + let text = "Review 漢字\n".repeat(2_000); + let input = json!({ + "schema":"paperclip.question_set.v1", + "description":text, + "questions":[{"id":"revision-123","prompt":"Approve?","required":true,"answerMode":"single_select","options":[{"id":"accept","label":"Accept"},{"id":"reject","label":"Reject"}]}] + }); + state.enqueue_event(&config, "runtime_request.created", EventPriority::P0, json!({ + "request":{"schema":"paperclip.runtime_request.v2","requestId":"request-1","type":"input","status":"pending","input":input}, + "diagnostic":text + })).unwrap(); + let stored = state.outbox[0] + .envelope + .pointer("/payload/payload") + .unwrap(); + assert_eq!( + stored.pointer("/request/input/description"), + Some(&json!(text)) + ); + assert!(stored["diagnostic"] + .as_str() + .unwrap() + .ends_with("…[truncated]")); + + state.enqueue_event(&config, "tool.execution.completed", EventPriority::P1, json!({ + "schema":"paperclip.tool.execution.v1","executionId":"tool-1","transport":"builtin","operation":"read","status":"completed", + "output":text,"outputBytes":text.len(),"outputTruncated":false,"outputDigest":null + })).unwrap(); + let stored = state.outbox[1] + .envelope + .pointer("/payload/payload") + .unwrap(); + assert_eq!(stored["output"], text); + assert_eq!(stored["outputTruncated"], false); + + for event_type in ["runtime_request.expired", "runtime_request.cancelled"] { + state.enqueue_event(&config, event_type, EventPriority::P0, json!({ + "requestId":"request-1", "replayAllowed":false, + "request":{"schema":"paperclip.runtime_request.v2","requestId":"request-1","type":"input","status":"pending","input":input} + })).unwrap(); + assert_eq!( + state + .outbox + .last() + .unwrap() + .envelope + .pointer("/payload/payload/request/input/description"), + Some(&json!(text)) + ); + } + + let mut oversized = input; + oversized["description"] = json!("漢".repeat(70_000)); + assert!(state + .enqueue_event( + &config, + "runtime_request.created", + EventPriority::P0, + json!({ + "request":{"input":oversized} + }) + ) + .unwrap_err() + .to_string() + .contains("196 KiB")); + } + #[test] fn credential_shaped_question_identity_and_validation_fail_closed() { let config = config(PathBuf::from("unused")); diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_sidecar_contract.rs b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_sidecar_contract.rs index 5d6ba17aa5..367a3bb54d 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_sidecar_contract.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_sidecar_contract.rs @@ -32,6 +32,7 @@ pub enum GeneratedAcpxSidecarCommand { RunAttach, TurnStart, TurnCancel, + TurnSteer, PermissionResolve, InputResolve, ToolResolve, @@ -52,6 +53,7 @@ impl GeneratedAcpxSidecarCommand { Self::RunAttach => "run.attach", Self::TurnStart => "turn.start", Self::TurnCancel => "turn.cancel", + Self::TurnSteer => "turn.steer", Self::PermissionResolve => "permission.resolve", Self::InputResolve => "input.resolve", Self::ToolResolve => "tool.resolve", @@ -70,6 +72,8 @@ impl GeneratedAcpxSidecarCommand { pub enum GeneratedAcpxSidecarEventType { #[serde(rename = "runtime.event")] RuntimeEvent, + #[serde(rename = "runtime.rich_event")] + RuntimeRichEvent, #[serde(rename = "runtime.permission_requested")] RuntimePermissionRequested, #[serde(rename = "runtime.input_requested")] diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs index a442643d1d..90cddbdc28 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs @@ -3185,6 +3185,14 @@ impl CodexCommandExecutor { } fn steer_turn(&mut self, payload: &Value) -> Result { + if payload + .get("mode") + .is_some_and(|mode| mode.as_str() != Some("steer")) + { + return Err(DurableRunnerError::invalid( + "Codex does not expose queued follow-up through turn.steer", + )); + } let text = payload .get("text") .and_then(Value::as_str) diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs b/packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs index 1e4fb43f60..e0eb95f611 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs @@ -195,9 +195,47 @@ pub fn project_acpx_state_event( AcpxProviderStateEvent::ToolResult(result) => { Ok(vec![project_acpx_tool_result(context, result)?]) } - AcpxProviderStateEvent::PermissionRequest { .. } => Err(LocalRunnerError::invalid( - "ACPX permission request reached projection outside the pinned runner policy", - )), + AcpxProviderStateEvent::PermissionRequest { + request_id, + title, + details, + .. + } => { + validate_projection_identity(request_id, "permission request", SHORT_STABLE_ID_CHARS)?; + let choices = details + .get("choices") + .and_then(Value::as_array) + .filter(|choices| !choices.is_empty() && choices.len() <= 4) + .ok_or_else(|| { + LocalRunnerError::invalid("ACPX permission request omitted its choices") + })?; + let mut seen = std::collections::HashSet::new(); + for choice in choices { + let key = choice.get("key").and_then(Value::as_str).unwrap_or(""); + if !matches!(key, "accept" | "accept_for_session" | "decline" | "cancel") + || !seen.insert(key) + || choice + .get("label") + .and_then(Value::as_str) + .is_none_or(|label| label.is_empty() || label.len() > 500) + { + return Err(LocalRunnerError::invalid( + "ACPX permission request contains invalid choices", + )); + } + } + one( + "runtime_request.created", + EventPriority::P0, + json!({"request": { + "schema":"paperclip.runtime_request.v2", "requestKind":"permission_approval", + "requestId":request_id, "turnId":context.turn_id, "itemId":context.item_id, + "type":"permission", "status":"pending", "prompt":title, "choices":choices, + "details":details, + "origin":{"adapter":"acpx-runtime-sidecar","provider":"acpx","method":"session/request_permission"}, + }}), + ) + } AcpxProviderStateEvent::InputRequest { request_id, question_set, @@ -216,7 +254,7 @@ pub fn project_acpx_state_event( .and_then(Value::as_str) }) .map(|value| bounded_text(value, MAX_TEXT_CHARS)) - .unwrap_or_else(|| "Codex needs your input".to_owned()); + .unwrap_or_else(|| "Provider needs your input".to_owned()); let origin = project_runtime_request_origin(origin.as_ref())?; one( "runtime_request.created", @@ -237,6 +275,60 @@ pub fn project_acpx_state_event( }), ) } + AcpxProviderStateEvent::RuntimeRequestEnded { + request_id, + question_set, + origin, + status, + } => { + let cancelled = matches!( + status, + AcpxTurnStatus::Cancelled | AcpxTurnStatus::Interrupted + ); + let reason = match status { + AcpxTurnStatus::Completed => "turn_completed", + AcpxTurnStatus::Failed => "provider_process_lost", + _ => "explicit_cancellation", + }; + let (request_id, request) = if let Some(question_set) = question_set { + let created = project_acpx_state_event( + context, + &AcpxProviderStateEvent::InputRequest { + request_id: request_id.clone(), + question_set: question_set.clone(), + origin: origin.clone(), + }, + )?; + let request = created[0].payload["request"].clone(); + (request["requestId"].clone(), Some(request)) + } else { + validate_projection_identity( + request_id, + "permission request", + SHORT_STABLE_ID_CHARS, + )?; + (json!(request_id), None) + }; + let mut payload = json!({ + "provider":"acpx", "requestId":request_id, + "requestKind": if request.is_some() {"runtime"} else {"permission_approval"}, + "requestType": if request.is_some() {"input"} else {"permission"}, + "turnId":context.turn_id, "itemId":context.item_id, + "reason":reason, "replayAllowed":false, "adapter":"acpx-runtime-sidecar", + }); + if let Some(request) = request { + payload["request"] = request; + } + one( + if cancelled { + "runtime_request.cancelled" + } else { + "runtime_request.expired" + }, + EventPriority::P0, + payload, + ) + } AcpxProviderStateEvent::SemanticResult(result) => { validate_semantic_projection_identity(&result.call_id, &result.operation_id)?; if is_reserved_terminal_operation(&result.operation_id) { @@ -1130,6 +1222,40 @@ fn normalize_acpx_status( )] } +/// Matches the TypeScript ACP display-name parser. This is presentation metadata; +/// semantic dispatch and the preclassified operation never derive authority from it. +fn acpx_mcp_tool_identity(value: &str) -> Option<(&str, &str)> { + let has_line_terminator = |text: &str| text.contains(['\n', '\r', '\u{2028}', '\u{2029}']); + if value + .get(..5) + .is_some_and(|prefix| prefix.eq_ignore_ascii_case("mcp__")) + { + let rest = &value[5..]; + if !has_line_terminator(rest) { + // The namespace is nonempty and the first eligible separator wins, + // including overlapping separators when the namespace starts with `_`. + for (index, _) in rest.char_indices().skip(1) { + if let Some(name) = rest[index..] + .strip_prefix("__") + .filter(|name| !name.is_empty()) + { + return Some((&rest[..index], name)); + } + } + } + } + if value + .get(..4) + .is_some_and(|prefix| prefix.eq_ignore_ascii_case("mcp.")) + { + let (namespace, name) = value[4..].split_once('.')?; + if !namespace.is_empty() && !name.is_empty() && !has_line_terminator(name) { + return Some((namespace, name)); + } + } + None +} + fn normalize_acpx_tool_call( payload: &Value, item_id: &str, @@ -1138,8 +1264,9 @@ fn normalize_acpx_tool_call( let native_status = string(payload.get("status")); let status = provider_status(native_status, native_status == "completed"); let terminal = status != "running"; - let raw_title = string(payload.get("title")); - let title = bounded_text(raw_title, 240); + let raw_title = string(payload.get("title")).trim(); + let mcp = acpx_mcp_tool_identity(raw_title); + let name = bounded_text(mcp.map_or(raw_title, |(_, name)| name), 240); let output = match payload.get("rawOutput").or_else(|| payload.get("output")) { Some(Value::String(value)) => value.clone(), Some(value) => serde_json::to_string(value).unwrap_or_default(), @@ -1148,11 +1275,11 @@ fn normalize_acpx_tool_call( let mut normalized = json!({ "schema": "paperclip.tool.execution.v1", "executionId": item_id, - "transport": "builtin", + "transport": if mcp.is_some() { "mcp" } else { "builtin" }, "operation": operation, - "name": if title.is_empty() { Value::Null } else { Value::String(title) }, + "name": if name.is_empty() { Value::Null } else { Value::String(name) }, "target": safe_acpx_location(payload.pointer("/locations/0"), operation == "edit"), - "namespace": Value::Null, + "namespace": mcp.map(|(namespace, _)| bounded_text(namespace, 240)), "readOnly": matches!(operation, "read" | "search" | "list"), "status": status, "durationMs": Value::Null, diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_event_projection.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_event_projection.rs index 221e05483a..6e7651a95a 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_event_projection.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_event_projection.rs @@ -23,6 +23,24 @@ fn project(event: AcpxProviderStateEvent) -> Vec { project_acpx_state_event(&context(), &event).unwrap() } +fn canonical_request_branch(request_kind: &str, request_type: &str) -> Value { + let schema: Value = serde_json::from_str(include_str!( + "../../../../protocol/schemas/request.schema.json" + )) + .unwrap(); + schema["oneOf"] + .as_array() + .unwrap() + .iter() + .find(|branch| { + branch["properties"]["schema"]["const"] == "paperclip.runtime_request.v2" + && branch["properties"]["requestKind"]["const"] == request_kind + && branch["properties"]["type"]["const"] == request_type + }) + .expect("canonical v2 request branch") + .clone() +} + fn reduced_semantic_result( call_id: &str, operation_id: &str, @@ -366,7 +384,32 @@ fn projects_assistant_terminal_and_diagnostic_events_fail_closed() { assert!(project_acpx_state_event(&context(), &permission) .unwrap_err() .to_string() - .contains("pinned runner policy")); + .contains("omitted its choices")); +} + +#[test] +fn projects_only_the_permission_choices_offered_by_the_provider() { + let events = project(AcpxProviderStateEvent::PermissionRequest { + request_id: "permission-1".to_owned(), + kind: "write".to_owned(), + title: "Edit source".to_owned(), + details: json!({"choices":[{"key":"accept","label":"Allow once"},{"key":"cancel","label":"Cancel"}]}), + }); + assert_eq!(events[0].event_type, "runtime_request.created"); + let schema = canonical_request_branch("permission_approval", "permission"); + let validator = jsonschema::validator_for(&schema).unwrap(); + assert!(validator.is_valid(&events[0].payload["request"])); + assert_eq!( + events[0].payload["request"]["requestKind"], + "permission_approval" + ); + assert_eq!( + events[0].payload["request"]["choices"] + .as_array() + .unwrap() + .len(), + 2 + ); } #[test] @@ -506,11 +549,7 @@ fn deterministically_projects_bounded_upstream_request_ids() { #[test] fn runtime_request_projection_preserves_durable_identity_boundaries() { - let canonical_request_schema: Value = serde_json::from_str(include_str!( - "../../../../protocol/schemas/request.schema.json" - )) - .unwrap(); - let mut runtime_request_schema = canonical_request_schema["oneOf"][0].clone(); + let mut runtime_request_schema = canonical_request_branch("runtime", "input"); // This test owns identity projection. The question-set validator has its // own coverage, so replace its remote reference with an unconstrained // local schema before compiling the canonical runtime-request branch. diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs index 2ee2e712dc..f00049620d 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs @@ -51,6 +51,7 @@ fn config(directory: &std::path::Path) -> AcpxProviderSessionConfig { working_directory: directory.to_owned(), permission_mode: AcpxPermissionMode::ApproveReads, permission_mode_pinned: true, + provider_policy: None, system_instructions: "Complete the supplied task.".to_owned(), runtime_context: serde_json::Value::Null, tool_set: AuthorizedToolSet { diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_events.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_events.rs index 2dc412039b..12ff5078ca 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_events.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_events.rs @@ -336,6 +336,131 @@ fn does_not_label_non_usd_acpx_cost_as_usd() { assert_eq!(usage[0].payload["cumulative"]["providerCostUsd"], 0.0); } +#[test] +fn matches_typescript_mcp_display_identity_across_tool_lifecycle() { + for (title, namespace, name) in [ + ( + "mcp__paperclip__write_document", + "paperclip", + "write_document", + ), + ( + "mcp.paperclip.write_document", + "paperclip", + "write_document", + ), + ( + " MCP__Paperclip__write_document ", + "Paperclip", + "write_document", + ), + ("McP.other.read_document", "other", "read_document"), + ("mcp__paperclip__nested__tool", "paperclip", "nested__tool"), + ("mcp.paperclip.nested.tool", "paperclip", "nested.tool"), + ("mcp_____write_document", "_", "write_document"), + ("mcp__é__write_document", "é", "write_document"), + ] { + for (tag, status, event_type) in [ + ("tool_call", "pending", "tool.execution.started"), + ( + "tool_call_update", + "in_progress", + "tool.execution.progressed", + ), + ("tool_call_update", "completed", "tool.execution.completed"), + ] { + let events = normalize_acpx_runtime_event( + AcpxRuntimeEventKind::ToolCall, + &json!({ + "type": "tool_call", "tag": tag, "status": status, + "toolCallId": "write-plan", "title": title, + "rawInput": {"private": "not-for-the-log"}, + "operationId": "finish_task", "semantic_tool": {"phase": "result"} + }), + Some("execute"), + "event-7", + "turn-1", + 3, + ); + assert_eq!(events.len(), 1); + assert_eq!(events[0].event_type, event_type); + let payload = &events[0].payload; + assert_eq!(payload["executionId"], "write-plan"); + assert_eq!(payload["transport"], "mcp"); + assert_eq!(payload["namespace"], namespace); + assert_eq!(payload["name"], name); + // Parsing the display title never changes preclassified operation authority. + assert_eq!(payload["operation"], "execute"); + assert_eq!(payload["readOnly"], false); + for dropped in ["rawInput", "operationId", "semantic_tool"] { + assert!(payload.get(dropped).is_none()); + } + assert!(!payload.to_string().contains("not-for-the-log")); + } + } +} + +#[test] +fn bounds_mcp_namespace_and_name_independently_after_parsing() { + let namespace = "é".repeat(300); + let events = normalize( + AcpxRuntimeEventKind::ToolCall, + json!({"type": "tool_call", "title": format!("mcp__{namespace}__write_document")}), + ); + assert_eq!(events[0].payload["transport"], "mcp"); + assert_eq!(events[0].payload["name"], "write_document"); + assert_eq!( + events[0].payload["namespace"] + .as_str() + .unwrap() + .chars() + .count(), + 240 + ); + let events = normalize( + AcpxRuntimeEventKind::ToolCall, + json!({"type": "tool_call", "title": format!("mcp.paperclip.{}", "雪".repeat(300))}), + ); + assert_eq!(events[0].payload["namespace"], "paperclip"); + assert_eq!( + events[0].payload["name"].as_str().unwrap().chars().count(), + 240 + ); + for title in [ + "mcp__Authorization: Bearer namespace-secret__write_document", + "mcp.paperclip.Authorization: Bearer tool-secret", + ] { + let events = normalize( + AcpxRuntimeEventKind::ToolCall, + json!({"type": "tool_call", "title": title}), + ); + assert!(!events[0].payload.to_string().contains("namespace-secret")); + assert!(!events[0].payload.to_string().contains("tool-secret")); + } +} + +#[test] +fn leaves_builtin_and_malformed_mcp_titles_as_display_only_builtin_names() { + for title in [ + "Read file", + "write_document", + "paperclip__write_document", + "mcp____write_document", + "mcp__paperclip__", + "mcp..write_document", + "mcp.paperclip.", + "mcp__paperclip__write\ndocument", + ] { + let events = normalize( + AcpxRuntimeEventKind::ToolCall, + json!({"type": "tool_call", "title": title}), + ); + assert_eq!(events[0].payload["transport"], "builtin"); + assert!(events[0].payload["namespace"].is_null()); + assert_eq!(events[0].payload["name"], title); + } +} + #[test] fn maps_tool_lifecycle_and_preserves_safe_display_paths() { let started = normalize( diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs index 0f1ff03cfb..89e4cdee12 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs @@ -50,6 +50,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { working_directory: std::env::temp_dir(), permission_mode: AcpxPermissionMode::ApproveReads, permission_mode_pinned: true, + provider_policy: None, system_instructions: "Complete the supplied task.".to_owned(), runtime_context: serde_json::Value::Null, tool_set: tool_set(), @@ -260,3 +261,42 @@ fn fails_closed_when_a_resolution_is_not_acknowledged() { assert!(error.contains("did not confirm tool resolution"), "{error}"); assert!(session.shutdown("already closed").is_ok()); } + +#[test] +fn interactive_permission_requires_an_offered_action_and_acknowledgement() { + for mode in ["permissions-interactive", "permissions-wrong-ack"] { + let mut cfg = config(mode); + cfg.agent = "claude".to_owned(); + cfg.model = "claude-sonnet-5".to_owned(); + let mut session = AcpxProviderSession::start(&cfg).unwrap(); + session + .start_turn("turn-1", "Run validation", &std::env::temp_dir()) + .unwrap(); + session.poll_event(Duration::from_secs(1)).unwrap().unwrap(); + assert!(session.state().pending_permission("permission-1").is_some()); + assert!(session + .resolve_permission("permission-1", "old-turn", &json!({"action":"accept"})) + .is_err()); + assert!(session + .resolve_permission( + "permission-1", + "turn-1", + &json!({"action":"accept_for_session"}) + ) + .is_err()); + assert!(session.state().pending_permission("permission-1").is_some()); + let result = + session.resolve_permission("permission-1", "turn-1", &json!({"action":"accept"})); + if mode == "permissions-wrong-ack" { + assert!(result.is_err()); + assert!(session.state().pending_permission("permission-1").is_some()); + } else { + result.unwrap(); + assert!(session.state().pending_permission("permission-1").is_none()); + assert!(session + .resolve_permission("permission-1", "turn-1", &json!({"action":"accept"})) + .is_err()); + } + session.shutdown("verified").unwrap(); + } +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs index 7223ae7c5f..c9b9ce5933 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs @@ -45,6 +45,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { working_directory: std::env::temp_dir(), permission_mode: AcpxPermissionMode::ApproveReads, permission_mode_pinned: true, + provider_policy: None, system_instructions: "Complete the supplied task.".to_owned(), runtime_context: serde_json::Value::Null, tool_set: tool_set(), @@ -130,8 +131,12 @@ fn bootstraps_a_codex_session_and_confirms_run_identity() { #[test] fn validates_qualified_policy_and_tool_catalog_before_spawning() { let mut invalid_agent = config("bootstrap"); - invalid_agent.agent = "pi".to_owned(); - assert!(start_error(&invalid_agent).contains("claude, codex, or grok")); + invalid_agent.agent = "unknown".to_owned(); + assert!(start_error(&invalid_agent).contains("known immutable profile")); + + let mut missing_policy = config("bootstrap"); + missing_policy.agent = "copilot".to_owned(); + assert!(start_error(&missing_policy).contains("explicit provider read-only policy")); let mut unpinned = config("bootstrap"); unpinned.permission_mode_pinned = false; diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_state.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_state.rs index 4c3361de93..072a812d56 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_state.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_state.rs @@ -608,3 +608,72 @@ fn mutation_prose_survives_sidecar_decode_pending_state_and_semantic_projection( assert!(!input.to_string().contains("actual-credential")); } } + +#[test] +fn terminal_requests_expire_with_their_projected_identity_before_terminal_and_never_replay() { + for status in ["failed", "cancelled", "interrupted", "completed"] { + let mut state = AcpxProviderState::new("run-1").unwrap(); + state.begin_turn("turn-1").unwrap(); + let long_id = format!("input-{}", "x".repeat(200)); + let input = state.accept_event(&event(1, GeneratedAcpxSidecarEventType::RuntimeInputRequested, Some("turn-1"), + json!({"requestId":long_id,"questionSet":question_set(),"origin":{"adapter":"acpx-runtime-sidecar","provider":"cursor","method":"cursor/create_plan"}}))).unwrap(); + state.accept_event(&event(2, GeneratedAcpxSidecarEventType::RuntimePermissionRequested, Some("turn-1"), + json!({"requestId":"permission-1","title":"Run validation","choices":[{"key":"cancel","label":"Cancel"}]}))).unwrap(); + let context = AcpxEventProjectionContext { + run_id: "run-1".into(), + normalized_session_id: "session-1".into(), + turn_id: "turn-1".into(), + provider_turn_id: Some("turn-1".into()), + item_id: "item-1".into(), + }; + let created = project_acpx_state_event(&context, &input[0]).unwrap(); + let projected_id = created[0].payload["request"]["requestId"].clone(); + let terminal = state + .accept_event(&event( + 3, + GeneratedAcpxSidecarEventType::RuntimeTurnTerminal, + Some("turn-1"), + json!({"status":status}), + )) + .unwrap(); + let projected: Vec<_> = terminal + .iter() + .flat_map(|event| project_acpx_state_event(&context, event).unwrap()) + .collect(); + assert_eq!(projected.len(), 3); + assert_eq!(projected[0].payload["requestId"], "permission-1"); + assert_eq!(projected[1].payload["requestId"], projected_id); + assert_eq!(projected[1].payload["request"]["input"], question_set()); + assert_eq!( + projected[1].payload["request"]["origin"]["method"], + "cursor/create_plan" + ); + for ended in &projected[..2] { + assert_eq!(ended.payload["replayAllowed"], false); + assert_eq!( + ended.event_type, + if matches!(status, "cancelled" | "interrupted") { + "runtime_request.cancelled" + } else { + "runtime_request.expired" + } + ); + } + assert!(projected[2].event_type.starts_with("turn.")); + assert!(state + .pending_question_set(projected_id.as_str().unwrap()) + .is_none()); + assert!(state.complete_permission("permission-1").is_err()); + assert!(state + .complete_input(projected_id.as_str().unwrap()) + .is_err()); + assert!(state + .accept_event(&event( + 4, + GeneratedAcpxSidecarEventType::RuntimeTurnTerminal, + Some("turn-1"), + json!({"status":status}) + )) + .is_err()); + } +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs index 92eb28ab27..eebd2e237f 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs @@ -26,6 +26,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { working_directory: std::env::temp_dir(), permission_mode: AcpxPermissionMode::ApproveReads, permission_mode_pinned: true, + provider_policy: None, system_instructions: "Complete the supplied task.".to_owned(), runtime_context: serde_json::Value::Null, tool_set: AuthorizedToolSet { diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs index 1436a25cd8..f2897a5415 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs @@ -36,8 +36,18 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { request_timeout: Duration::from_secs(1), shutdown_grace: Duration::from_millis(100), }, - agent: "codex".to_owned(), - model: "gpt-5.6-sol".to_owned(), + agent: if mode.starts_with("controls") { + "pi" + } else { + "codex" + } + .to_owned(), + model: if mode.starts_with("controls") { + "openrouter/deepseek/deepseek-v4-flash-0731" + } else { + "gpt-5.6-sol" + } + .to_owned(), run_id: "run-1".to_owned(), catalog_revision: 1, runtime_directory: std::env::temp_dir(), @@ -45,6 +55,15 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { working_directory: std::env::temp_dir(), permission_mode: AcpxPermissionMode::ApproveReads, permission_mode_pinned: true, + provider_policy: if mode.starts_with("controls") { + Some( + paperclip_runner_core::acpx_provider_session::AcpxProviderRuntimePolicy { + read_only: false, + }, + ) + } else { + None + }, system_instructions: "Complete the supplied task.".to_owned(), runtime_context: serde_json::Value::Null, tool_set: tool_set(), @@ -693,3 +712,69 @@ fn fails_closed_before_returning_an_unauthorized_tool_call() { assert!(session.state().pending_tool("call-1").is_none()); assert!(session.shutdown("already closed").is_ok()); } + +#[test] +fn turn_controls_preserve_mode_and_reject_stale_duplicate_and_oversized_delivery() { + let mut session = AcpxProviderSession::start(&config("controls")).unwrap(); + assert!(session + .steer_turn("turn-1", "control-1", "steer", "message") + .is_err()); + session + .start_turn("turn-1", "Work", &std::env::temp_dir()) + .unwrap(); + let first = session + .steer_turn("turn-1", "control-1", "steer", "Change focus") + .unwrap(); + assert_eq!(first["mode"], "steer"); + assert!(session + .steer_turn("turn-1", "control-1", "follow_up", "Duplicate") + .unwrap_err() + .to_string() + .contains("already attempted")); + let queued = session + .steer_turn("turn-1", "control-2", "follow_up", "Then validate") + .unwrap(); + assert_eq!(queued["mode"], "follow_up"); + assert!(session + .steer_turn("turn-2", "control-3", "steer", "Stale") + .is_err()); + assert!(session + .steer_turn("turn-1", "control-3", "cancel", "Wrong") + .is_err()); + assert!(session + .steer_turn("turn-1", "control-3", "steer", &"a".repeat(65_537)) + .is_err()); + session.shutdown("verified controls").unwrap(); +} + +#[test] +fn turn_controls_fail_closed_on_mismatched_acknowledgement() { + let mut session = AcpxProviderSession::start(&config("controls-wrong-ack")).unwrap(); + session + .start_turn("turn-1", "Work", &std::env::temp_dir()) + .unwrap(); + assert!(session + .steer_turn("turn-1", "control-1", "steer", "Change focus") + .unwrap_err() + .to_string() + .contains("exact turn control")); + assert!(session + .steer_turn("turn-1", "control-2", "follow_up", "Do not replay") + .is_err()); +} + +#[test] +fn lazy_warm_handshake_updates_live_turn_control_discovery() { + let mut session = AcpxProviderSession::start(&config("controls-lazy")).unwrap(); + assert!(!session.turn_control_capabilities().steering); + assert!(!session.turn_control_capabilities().queued_follow_up); + session + .start_turn("turn-lazy", "Work", &std::env::temp_dir()) + .unwrap(); + assert!(session.turn_control_capabilities().steering); + assert!(session.turn_control_capabilities().queued_follow_up); + session + .steer_turn("turn-lazy", "control-1", "follow_up", "Then validate") + .unwrap(); + session.shutdown("verified live handshake").unwrap(); +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_rich_events.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_rich_events.rs new file mode 100644 index 0000000000..dc570b69a4 --- /dev/null +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_rich_events.rs @@ -0,0 +1,181 @@ +use paperclip_runner_core::acpx_event_payload::{decode_acpx_event, AcpxEventPayload}; +use paperclip_runner_core::acpx_event_scope::AcpxEventScope; +use paperclip_runner_core::acpx_provider_state::{AcpxProviderState, AcpxProviderStateEvent}; +use paperclip_runner_core::acpx_sidecar_transport::AcpxSidecarEvent; +use paperclip_runner_core::durable::EventPriority; +use paperclip_runner_core::generated_acpx_sidecar_contract::GeneratedAcpxSidecarEventType; +use serde_json::{json, Value}; + +fn scope() -> AcpxEventScope { + let mut scope = AcpxEventScope::new("run-1").unwrap(); + scope.bind_turn("turn-1").unwrap(); + scope +} +fn event(event_type: &str, payload: Value) -> AcpxSidecarEvent { + AcpxSidecarEvent { + sequence: 1, + event_type: GeneratedAcpxSidecarEventType::RuntimeRichEvent, + run_id: Some("run-1".to_owned()), + turn_id: Some("turn-1".to_owned()), + payload: json!({"eventType":event_type,"itemId":"display-1","payload":payload}), + } +} +fn plan() -> Value { + json!({"schema":"paperclip.plan.updated.v1","planId":"plan-1","revision":2,"explanation":"Changed order","steps":[{"stepId":"step-1","body":"Inspect","status":"completed"},{"stepId":"step-2","body":"Build","status":"in_progress"}],"complete":false,"syncStatus":"not_applicable","documentRevision":null}) +} +fn artifact() -> Value { + json!({"schema":"paperclip.artifact.generated.v1","artifactId":"image-1","status":"completed","reference":"outputs/diagram.png","mediaType":"image/png","registered":false,"failure":null}) +} + +#[test] +fn rich_activity_preserves_plans_children_and_artifact_metadata_without_mutation_authority() { + let mut state = AcpxProviderState::new("run-1").unwrap(); + state.begin_turn("turn-1").unwrap(); + for (event_type, payload) in [ + ("plan.updated", plan()), + ("artifact.generated", artifact()), + ( + "delegation.completed", + json!({"schema":"paperclip.delegation.v1","delegationId":"task-1","action":"spawn","status":"completed","children":[{"childId":"child-1","role":"explore","model":"small-model","status":"completed","summary":"Found the implementation","activitySummary":"Searched src; duration 310 ms"}]}), + ), + ] { + let projected = state + .accept_event(&event(event_type, payload.clone())) + .unwrap(); + assert_eq!(projected.len(), 1); + let AcpxProviderStateEvent::Activity(activity) = &projected[0] else { + panic!("rich event became an authority-bearing event"); + }; + assert_eq!(activity.event_type, event_type); + assert_eq!(activity.priority, EventPriority::P1); + assert_eq!(activity.payload, payload); + assert!(!state.has_pending_requests()); + assert_eq!(state.active_turn_id(), Some("turn-1")); + } +} + +#[test] +fn rich_activity_rejects_terminal_semantic_source_and_schema_injection() { + for event_type in [ + "turn.completed", + "run.result", + "run.completed", + "semantic_tool.input", + "semantic_tool.result", + "runtime.input_requested", + "workspace.changed", + "harness.diagnostic", + "unknown.event", + ] { + assert!( + decode_acpx_event(&scope(), &event(event_type, plan())).is_err(), + "{event_type}" + ); + } + assert!(decode_acpx_event(&scope(), &event("artifact.generated", plan())).is_err()); + for field in ["source", "sourceRef", "runId", "priority"] { + let mut injected = event("plan.updated", plan()); + injected.payload[field] = json!("forged"); + assert!(decode_acpx_event(&scope(), &injected).is_err()); + } + let mut extra = plan(); + extra["sourceRef"] = json!("file:///private/key"); + assert!(decode_acpx_event(&scope(), &event("plan.updated", extra)).is_err()); + let mut registered = artifact(); + registered["registered"] = json!(true); + assert!(decode_acpx_event(&scope(), &event("artifact.generated", registered)).is_err()); + let mut synchronized = plan(); + synchronized["syncStatus"] = json!("synchronized"); + synchronized["documentRevision"] = json!(1); + assert!(decode_acpx_event(&scope(), &event("plan.updated", synchronized)).is_err()); + for path in [ + "/private/key", + "../outside", + "file:///private/key", + "C:\\private\\key", + "outputs/evil\u{0000}.png", + ] { + let mut escaped = artifact(); + escaped["reference"] = json!(path); + assert!( + decode_acpx_event(&scope(), &event("artifact.generated", escaped)).is_err(), + "{path}" + ); + } +} + +#[test] +fn rich_activity_requires_the_current_run_and_active_turn() { + let mut event = event("plan.updated", plan()); + event.run_id = Some("old-run".to_owned()); + assert!(decode_acpx_event(&scope(), &event).is_err()); + event.run_id = None; + assert!(decode_acpx_event(&scope(), &event).is_err()); + event.run_id = Some("run-1".to_owned()); + event.turn_id = Some("old-turn".to_owned()); + assert!(decode_acpx_event(&scope(), &event).is_err()); + event.turn_id = None; + assert!(decode_acpx_event(&scope(), &event).is_err()); + event.turn_id = Some("turn-1".to_owned()); + let mut settled = scope(); + settled.clear_turn("turn-1").unwrap(); + assert!(decode_acpx_event(&settled, &event).is_err()); +} + +#[test] +fn rich_activity_is_byte_bounded_and_preserves_long_schema_bounded_output() { + let text = "Unicode 漢字\n".repeat(1_000); + let payload = json!({"schema":"paperclip.tool.execution.v1","executionId":"tool-1","transport":"builtin","operation":"read","status":"completed","output":text,"outputBytes":text.len(),"outputTruncated":false,"outputDigest":null}); + let AcpxEventPayload::RichActivity { payload, .. } = + decode_acpx_event(&scope(), &event("tool.execution.completed", payload)).unwrap() + else { + panic!("not rich activity"); + }; + assert_eq!(payload["output"], text); + let mut oversized = plan(); + oversized["steps"] = json!((0..100).map(|index|json!({"stepId":format!("step-{index}"),"body":"漢".repeat(3_000),"status":"pending"})).collect::>()); + assert!( + decode_acpx_event(&scope(), &event("plan.updated", oversized)) + .unwrap_err() + .to_string() + .contains("256 KiB") + ); +} + +fn question_event(description: String) -> AcpxSidecarEvent { + AcpxSidecarEvent { + sequence: 1, + event_type: GeneratedAcpxSidecarEventType::RuntimeInputRequested, + run_id: Some("run-1".to_owned()), + turn_id: Some("turn-1".to_owned()), + payload: json!({"requestId":"plan-request","questionSet":{"schema":"paperclip.question_set.v1","description":description,"questions":[{"id":"revision-123","prompt":"Approve?","required":true,"answerMode":"single_select","options":[{"id":"accept","label":"Accept"},{"id":"reject","label":"Reject"}]}]}}), + } +} + +#[test] +fn full_plan_presentation_survives_utf8_redaction_without_diagnostic_truncation() { + let description = "Review 漢字\n".repeat(2_000); + let AcpxEventPayload::InputRequested { question_set, .. } = + decode_acpx_event(&scope(), &question_event(description.clone())).unwrap() + else { + panic!("not input"); + }; + assert_eq!(question_set["description"], description); + let secret = format!("{description}\napi_key=topsecret\nFinal action"); + let AcpxEventPayload::InputRequested { question_set, .. } = + decode_acpx_event(&scope(), &question_event(secret)).unwrap() + else { + panic!("not input"); + }; + let displayed = question_set["description"].as_str().unwrap(); + assert!(displayed.starts_with("Sensitive values were redacted")); + assert!(displayed.ends_with("Final action")); + assert!(!displayed.contains("topsecret")); + assert!(!displayed.contains("[truncated]")); + assert!( + decode_acpx_event(&scope(), &question_event("漢".repeat(70_000))) + .unwrap_err() + .to_string() + .contains("196 KiB") + ); +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_sidecar_transport.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_sidecar_transport.rs index eed4288783..59497b1879 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_sidecar_transport.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_sidecar_transport.rs @@ -233,12 +233,32 @@ fn assigned_gateway_binding_reaches_qualified_sidecar_without_unrelated_secrets( "fixture-token-never-returned-in-test-output", ) .env("UNRELATED_EVAL_SECRET", "must-not-cross-boundary") + .env( + "PAPERCLIP_ACPX_CREDENTIAL_BINDING", + "controller-session-binding", + ) + .envs( + [ + "ANTHROPIC_API_KEY", + "CLAUDE_CODE_OAUTH_TOKEN", + "OPENAI_API_KEY", + "CODEX_API_KEY", + "OPENROUTER_API_KEY", + "CURSOR_API_KEY", + "CURSOR_AUTH_TOKEN", + "COPILOT_GITHUB_TOKEN", + "GITHUB_TOKEN", + "GH_TOKEN", + ] + .into_iter() + .map(|key| (key, "fixture-credential")), + ) .status() .unwrap(); assert!(status.success(), "isolated gateway environment test failed"); return; } - for agent in ["claude", "codex"] { + for agent in ["claude", "codex", "pi", "cursor", "copilot"] { let mut sidecar = AcpxSidecarTransport::start_for_agent( &AcpxSidecarTransportConfig { command: PathBuf::from(env!("CARGO_BIN_EXE_fake-acpx-sidecar")), @@ -260,6 +280,23 @@ fn assigned_gateway_binding_reaches_qualified_sidecar_without_unrelated_secrets( "assigned gateway credential was dropped" ); assert_eq!(response["hasUnrelatedSecret"], false); + let expected = match agent { + "claude" => vec!["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"], + "codex" => vec!["OPENAI_API_KEY", "CODEX_API_KEY"], + "pi" => vec!["OPENROUTER_API_KEY"], + "cursor" => vec!["CURSOR_API_KEY", "CURSOR_AUTH_TOKEN"], + "copilot" => vec!["COPILOT_GITHUB_TOKEN"], + _ => unreachable!(), + }; + assert_eq!(response["credentialKeys"], json!(expected)); + assert_eq!( + response["credentialBinding"], + if matches!(agent, "pi" | "cursor" | "copilot") { + json!("controller-session-binding") + } else { + serde_json::Value::Null + } + ); sidecar.shutdown().unwrap(); } } diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/codex_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/codex_provider.rs index 51f5952365..060f5d1959 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/codex_provider.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/codex_provider.rs @@ -851,16 +851,21 @@ fn codex_rejects_replay_of_a_completed_tool_call_id_in_the_same_turn() { .start_turn("Inspect the fake task once.", &config.cwd) .expect("start provider turn"); - let first_call = (0..32) - .find_map(|_| match provider.poll().expect("poll first tool call") { - Some(CodexProviderEvent::ToolCall { - call_id, - operation_id, - .. - }) => Some((call_id, operation_id)), - _ => None, - }) - .expect("observe the first semantic tool call"); + let first_call_deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + let first_call = + std::iter::from_fn(|| (std::time::Instant::now() < first_call_deadline).then_some(())) + .find_map(|_| match provider.poll().expect("poll first tool call") { + Some(CodexProviderEvent::ToolCall { + call_id, + operation_id, + .. + }) => Some((call_id, operation_id)), + _ => { + std::thread::sleep(std::time::Duration::from_millis(1)); + None + } + }) + .expect("observe the first semantic tool call"); provider .deliver_tool_result(&ToolResult { call_id: first_call.0, @@ -870,9 +875,17 @@ fn codex_rejects_replay_of_a_completed_tool_call_id_in_the_same_turn() { }) .expect("deliver the first semantic result"); - let replay_error = (0..32) - .find_map(|_| provider.poll().err()) - .expect("same-turn replay of the completed call id is rejected"); + let replay_deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + let replay_error = + std::iter::from_fn(|| (std::time::Instant::now() < replay_deadline).then_some(())) + .find_map(|_| { + let error = provider.poll().err(); + if error.is_none() { + std::thread::sleep(std::time::Duration::from_millis(1)); + } + error + }) + .expect("same-turn replay of the completed call id is rejected"); assert!( replay_error .to_string() diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs index c23930eeff..9699a80278 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs @@ -16,6 +16,8 @@ use sha2::{Digest, Sha256}; const CODEX_ACPX_DIGEST: &str = "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3"; +const PI_ACPX_DIGEST: &str = + "sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f"; fn temporary_directory(label: &str) -> PathBuf { let nonce = SystemTime::now() @@ -76,6 +78,18 @@ fn acpx_config(state_dir: &Path, mode: &str) -> DurableRunnerConfig { config } +fn pi_acpx_config(state_dir: &Path, mode: &str) -> DurableRunnerConfig { + let mut config = acpx_config(state_dir, mode); + *config + .acpx_launch_profile + .as_mut() + .unwrap() + .args + .last_mut() + .unwrap() = PI_ACPX_DIGEST.to_owned(); + config +} + fn qualified_artifact(path: PathBuf) -> QualifiedLaunchArtifact { QualifiedLaunchArtifact { sha256: format!("sha256:{:x}", Sha256::digest(fs::read(&path).unwrap())), @@ -211,6 +225,527 @@ fn prepare_payload_with_mode(directory: &Path, agent: &str, mode: &str) -> Value }) } +fn pi_prepare_payload(directory: &Path, mode: &str) -> Value { + let mut payload = prepare_payload_with_mode(directory, "pi", mode); + let provider = &mut payload["provider"]; + provider["model"] = json!("openrouter/deepseek/deepseek-v4-flash-0731"); + provider["agentServerPackage"] = json!("pi-acp"); + provider["agentServerVersion"] = json!("0.0.33"); + provider["agentRuntimePackage"] = json!("@earendil-works/pi-coding-agent"); + provider["agentRuntimeVersion"] = json!("0.84.2"); + provider["commandDigest"] = json!(PI_ACPX_DIGEST); + provider["sidecarArgs"][3] = json!(PI_ACPX_DIGEST); + provider["providerPolicy"] = json!({"readOnly":true}); + payload +} + +fn pending_acpx_runtime_request( + directory: &Path, + mode: &str, +) -> ( + NativeProviderCommandExecutor, + DurableRunnerConfig, + String, + u32, +) { + let mut config = acpx_config(directory, mode); + let mut payload = prepare_payload_with_mode(directory, "codex", mode); + if mode.starts_with("permissions-") { + // Codex's pinned runner policy does not allow interactive ACP permissions. + let digest = "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a"; + *config + .acpx_launch_profile + .as_mut() + .unwrap() + .args + .last_mut() + .unwrap() = digest.into(); + let provider = &mut payload["provider"]; + provider["agent"] = json!("claude"); + provider["model"] = json!("claude-sonnet-5"); + provider["agentServerPackage"] = json!("@agentclientprotocol/claude-agent-acp"); + provider["agentServerVersion"] = json!("0.73.0"); + provider["agentRuntimePackage"] = json!("@anthropic-ai/claude-agent-sdk"); + provider["agentRuntimeVersion"] = json!("0.3.280"); + provider["commandDigest"] = json!(digest); + provider["sidecarArgs"][3] = json!(digest); + } else { + let operations = vec![AuthorizedTool { + operation_id: "issues.read".into(), + version: 1, + description: "Read an issue.".into(), + input_schema: json!({"type":"object"}), + response_schema: json!({"type":"object"}), + }]; + payload["authorizedTools"]["catalogDigest"] = + json!(authorized_tool_catalog_digest(&operations).unwrap()); + payload["authorizedTools"]["operations"] = json!(operations); + } + let mut executor = NativeProviderCommandExecutor::with_runner_config(directory, &config); + executor + .execute(&command(1, "run.prepare", payload)) + .unwrap(); + let opened = executor + .execute(&command(2, "session.open", json!({}))) + .unwrap(); + let process_id = opened.result["processId"].as_u64().unwrap() as u32; + executor + .execute(&command( + 3, + "turn.start", + json!({"text":"Ask before continuing", "turnId":"turn-1"}), + )) + .unwrap(); + let deadline = std::time::Instant::now() + Duration::from_secs(2); + loop { + let events = executor.poll_events().unwrap(); + let request_id = events + .iter() + .find(|event| event.event_type == "runtime_request.created") + .and_then(|event| event.payload.pointer("/request/requestId")) + .and_then(Value::as_str) + .map(str::to_owned); + executor.acknowledge_events(events.len()).unwrap(); + if let Some(request_id) = request_id { + return (executor, config, request_id, process_id); + } + assert!( + std::time::Instant::now() < deadline, + "request was not presented" + ); + } +} + +fn acpx_runtime_resolution(mode: &str, request_id: &str) -> Value { + json!({"requestId":request_id, "turnId":"turn-1", "resolution": + if mode.starts_with("permissions-") { json!({"action":"accept"}) } + else { json!({"action":"submit", "response":{"schema":"paperclip.question_response.v1", + "answers":{"target":{"selectedOptionIds":["first"]}}}}) } + }) +} + +fn shutdown_recovered_acpx_fixture(executor: &mut NativeProviderCommandExecutor, directory: &Path) { + // The fake sidecar advertises the same three lifetime-fence ports for all + // fixtures. Parallel recovered shutdowns hold a quorum through state fsync, + // temporarily preventing another fixture from proving cleanup. Retry only + // that rejection, using the real production quorum proof on every attempt. + // This is fixture synchronization, not a production cleanup workaround. + let deadline = std::time::Instant::now() + Duration::from_secs(5); + let mut blocked_attempts = 0; + loop { + match executor.shutdown() { + Ok(()) => break, + Err(error) => { + assert_eq!( + error.to_string(), + "ACPX original provider lifetime remains active; cleanup is not yet proven", + "unexpected recovered fixture shutdown failure" + ); + let persisted: Value = serde_json::from_slice( + &fs::read(directory.join("acpx-provider-state.json")).unwrap(), + ) + .unwrap(); + assert_eq!(persisted["providerExitUnconfirmed"], true); + blocked_attempts += 1; + if blocked_attempts == 1 { + eprintln!("recovered fixture cleanup waiting for the shared lifetime quorum"); + } + assert!( + std::time::Instant::now() < deadline, + "recovered fixture cleanup never acquired the lifetime quorum" + ); + std::thread::sleep(Duration::from_millis(10)); + } + } + } + if blocked_attempts > 0 { + eprintln!("recovered fixture cleanup proved after {blocked_attempts} quorum rejections"); + } + let persisted: Value = + serde_json::from_slice(&fs::read(directory.join("acpx-provider-state.json")).unwrap()) + .unwrap(); + assert_eq!(persisted["providerExitUnconfirmed"], false); +} + +#[test] +fn acpx_response_delivery_survives_crash_before_journaling_without_replaying_the_response() { + for mode in [ + "permissions-interactive", + "resolutions", + "resolutions-projected-id", + ] { + let directory = temporary_directory(mode); + let (mut executor, config, request_id, _) = pending_acpx_runtime_request(&directory, mode); + let resolve = command( + 4, + "request.resolve", + acpx_runtime_resolution(mode, &request_id), + ); + let delivered = executor.execute(&resolve).unwrap(); + assert_eq!(delivered.result["status"], "delivered"); + assert!( + delivered.events.is_empty(), + "settlement must use the durable provider outbox" + ); + let persisted: Value = + serde_json::from_slice(&fs::read(directory.join("acpx-provider-state.json")).unwrap()) + .unwrap(); + assert!(persisted["pendingRuntimeRequests"] + .get(&request_id) + .is_none()); + let retained = executor.retained_events().unwrap(); + assert_eq!(retained.len(), 1); + assert_eq!(retained[0].event_type, "runtime_request.resolved"); + assert_eq!(retained[0].payload["requestId"], request_id); + assert_eq!( + persisted["pendingEvents"][0]["executorEventId"], + retained[0].executor_event_id + ); + assert!(executor + .execute(&command(5, "request.resolve", resolve.payload.clone())) + .is_err()); + assert_eq!(executor.retained_events().unwrap(), retained); + + // Simulate death after the sidecar ACK/state fsync, before the runner + // journals this event or acknowledges the provider's retained prefix. + drop(executor); + let mut recovered = NativeProviderCommandExecutor::with_runner_config(&directory, &config); + let events = recovered.poll_events().unwrap(); + assert_eq!(events[0], retained[0]); + assert_eq!( + events + .iter() + .filter(|event| event.event_type.starts_with("runtime_request.")) + .count(), + 1 + ); + assert!(recovered + .execute(&command(6, "request.resolve", resolve.payload)) + .is_err()); + assert_eq!(recovered.poll_events().unwrap(), events); + assert_eq!( + recovered + .execute(&command(7, "session.snapshot", json!({}))) + .unwrap() + .result["status"], + "closed" + ); + // Polling is not an ACK; only the journal's ACK retires the event. + recovered.acknowledge_events(events.len()).unwrap(); + assert!(recovered.poll_events().unwrap().is_empty()); + drop(recovered); + let mut recovered_again = + NativeProviderCommandExecutor::with_runner_config(&directory, &config); + assert!(recovered_again.poll_events().unwrap().is_empty()); + shutdown_recovered_acpx_fixture(&mut recovered_again, &directory); + fs::remove_dir_all(directory).unwrap(); + } +} + +#[test] +fn acpx_delivered_response_is_acknowledged_once_before_session_close() { + for mode in ["permissions-interactive", "resolutions"] { + let directory = temporary_directory("response-ack"); + let (mut executor, _, request_id, _) = pending_acpx_runtime_request(&directory, mode); + let response = acpx_runtime_resolution(mode, &request_id); + executor + .execute(&command(4, "request.resolve", response.clone())) + .unwrap(); + let events = executor.poll_events().unwrap(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].event_type, "runtime_request.resolved"); + assert_eq!(executor.poll_events().unwrap(), events); + executor.acknowledge_events(1).unwrap(); + assert!(executor + .execute(&command(5, "request.resolve", response)) + .is_err()); + executor + .execute(&command(6, "session.close", json!({}))) + .unwrap(); + assert!( + executor.poll_events().unwrap().is_empty(), + "close must not expire a delivered request" + ); + executor.shutdown().unwrap(); + fs::remove_dir_all(directory).unwrap(); + } +} + +#[test] +fn acpx_uncommitted_delivery_expires_after_failed_state_write_without_replay() { + for mode in ["permissions-interactive", "resolutions"] { + let directory = temporary_directory("response-save-failure"); + let (mut executor, config, request_id, process_id) = + pending_acpx_runtime_request(&directory, mode); + let state_path = directory.join("acpx-provider-state.json"); + let previous_path = directory.join("previous-state.json"); + let previous = fs::read(&state_path).unwrap(); + fs::rename(&state_path, &previous_path).unwrap(); + // Force the atomic state replacement to fail after the real sidecar + // has acknowledged delivery, without adding production fault hooks. + fs::create_dir(&state_path).unwrap(); + let response = acpx_runtime_resolution(mode, &request_id); + let error = executor + .execute(&command(4, "request.resolve", response.clone())) + .unwrap_err(); + assert!(error + .to_string() + .contains("atomically replace ACPX provider state")); + assert_eq!(fs::read(&previous_path).unwrap(), previous); + // Neither ordinary polling nor the explicit drain/ACK path may publish + // a delivered response whose settlement failed to persist. + for result in [ + executor.retained_events().map(|_| ()), + executor.poll_events().map(|_| ()), + executor.acknowledge_events(0), + executor.acknowledge_events(1), + executor + .execute(&command(5, "runner.drain", json!({}))) + .map(|_| ()), + executor + .execute(&command(6, "request.resolve", response.clone())) + .map(|_| ()), + executor.shutdown(), + ] { + assert_eq!(result.unwrap_err().to_string(), error.to_string()); + } + #[cfg(unix)] + assert!( + !std::process::Command::new("kill") + .args(["-0", &process_id.to_string()]) + .env_clear() + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .unwrap() + .success(), + "failed durable cleanup must still reap the owned sidecar" + ); + assert_eq!(fs::read(&previous_path).unwrap(), previous); + fs::remove_dir(&state_path).unwrap(); + fs::rename(&previous_path, &state_path).unwrap(); + // Repairing the file path does not authorize this uncertain executor + // to resume, retry an approval, or overwrite the recovery snapshot. + assert!(executor.poll_events().is_err()); + assert!(executor.retained_events().is_err()); + assert!(executor.shutdown().is_err()); + assert_eq!(fs::read(&state_path).unwrap(), previous); + drop(executor); + + let mut recovered = NativeProviderCommandExecutor::with_runner_config(&directory, &config); + let events = recovered.poll_events().unwrap(); + let requests: Vec<_> = events + .iter() + .filter(|event| event.event_type.starts_with("runtime_request.")) + .collect(); + assert_eq!(requests.len(), 1); + assert_eq!(requests[0].event_type, "runtime_request.expired"); + assert_eq!(requests[0].payload["requestId"], request_id); + assert_eq!(requests[0].payload["replayAllowed"], false); + assert!(recovered + .execute(&command(5, "request.resolve", response)) + .is_err()); + assert_eq!(recovered.poll_events().unwrap(), events); + recovered.acknowledge_events(events.len()).unwrap(); + shutdown_recovered_acpx_fixture(&mut recovered, &directory); + fs::remove_dir_all(directory).unwrap(); + } +} + +#[test] +fn acpx_failed_settlement_ack_replays_the_same_delivered_event_without_expiry() { + for mode in ["permissions-interactive", "resolutions"] { + let directory = temporary_directory("response-ack-save-failure"); + let (mut executor, config, request_id, _) = pending_acpx_runtime_request(&directory, mode); + let response = acpx_runtime_resolution(mode, &request_id); + executor + .execute(&command(4, "request.resolve", response.clone())) + .unwrap(); + let committed = executor.poll_events().unwrap(); + assert_eq!(committed.len(), 1); + assert_eq!(committed[0].event_type, "runtime_request.resolved"); + let state_path = directory.join("acpx-provider-state.json"); + let previous_path = directory.join("previous-state.json"); + let previous = fs::read(&state_path).unwrap(); + fs::rename(&state_path, &previous_path).unwrap(); + fs::create_dir(&state_path).unwrap(); + // The journal has committed this executorEventId, but retiring the + // provider's retained prefix must not escape a failed state write. + let error = executor.acknowledge_events(1).unwrap_err(); + assert!(error + .to_string() + .contains("atomically replace ACPX provider state")); + for result in [ + executor.retained_events().map(|_| ()), + executor.poll_events().map(|_| ()), + executor.acknowledge_events(0), + executor + .execute(&command(5, "request.resolve", response.clone())) + .map(|_| ()), + executor.shutdown(), + ] { + assert_eq!(result.unwrap_err().to_string(), error.to_string()); + } + assert_eq!(fs::read(&previous_path).unwrap(), previous); + fs::remove_dir(&state_path).unwrap(); + fs::rename(&previous_path, &state_path).unwrap(); + drop(executor); + + let mut recovered = NativeProviderCommandExecutor::with_runner_config(&directory, &config); + let events = recovered.poll_events().unwrap(); + assert_eq!(events[0], committed[0]); + assert_eq!( + events + .iter() + .filter(|event| event.event_type.starts_with("runtime_request.")) + .count(), + 1 + ); + assert!(recovered + .execute(&command(6, "request.resolve", response)) + .is_err()); + assert_eq!(recovered.poll_events().unwrap(), events); + recovered.acknowledge_events(events.len()).unwrap(); + assert!(recovered.poll_events().unwrap().is_empty()); + shutdown_recovered_acpx_fixture(&mut recovered, &directory); + drop(recovered); + let mut recovered_again = + NativeProviderCommandExecutor::with_runner_config(&directory, &config); + assert!(recovered_again.poll_events().unwrap().is_empty()); + recovered_again.shutdown().unwrap(); + fs::remove_dir_all(directory).unwrap(); + } +} + +#[test] +fn acpx_uncommitted_expiry_and_cancellation_cannot_escape_through_poll_or_drain() { + for (mode, close) in [ + ("permissions-wrong-ack", false), + ("permissions-interactive", true), + ("resolutions", true), + ] { + let directory = temporary_directory("expiry-save-failure"); + let (mut executor, config, request_id, _) = pending_acpx_runtime_request(&directory, mode); + let state_path = directory.join("acpx-provider-state.json"); + let previous_path = directory.join("previous-state.json"); + let previous = fs::read(&state_path).unwrap(); + fs::rename(&state_path, &previous_path).unwrap(); + fs::create_dir(&state_path).unwrap(); + let response = acpx_runtime_resolution(mode, &request_id); + let error = if close { + executor + .execute(&command(4, "session.close", json!({}))) + .unwrap_err() + } else { + assert!(executor + .execute(&command(4, "request.resolve", response.clone())) + .unwrap_err() + .to_string() + .contains("did not confirm permission resolution")); + executor.poll_events().unwrap_err() + }; + assert!(error + .to_string() + .contains("atomically replace ACPX provider state")); + for result in [ + executor.retained_events().map(|_| ()), + executor.poll_events().map(|_| ()), + executor.acknowledge_events(1), + executor + .execute(&command(5, "runner.drain", json!({}))) + .map(|_| ()), + executor + .execute(&command(6, "request.resolve", response.clone())) + .map(|_| ()), + executor.shutdown(), + ] { + assert_eq!(result.unwrap_err().to_string(), error.to_string()); + } + assert_eq!(fs::read(&previous_path).unwrap(), previous); + fs::remove_dir(&state_path).unwrap(); + fs::rename(&previous_path, &state_path).unwrap(); + assert!(executor.shutdown().is_err()); + assert_eq!(fs::read(&state_path).unwrap(), previous); + drop(executor); + + let mut recovered = NativeProviderCommandExecutor::with_runner_config(&directory, &config); + let events = recovered.poll_events().unwrap(); + let settlements: Vec<_> = events + .iter() + .filter(|event| event.event_type.starts_with("runtime_request.")) + .collect(); + assert_eq!(settlements.len(), 1); + assert_eq!(settlements[0].event_type, "runtime_request.expired"); + assert_eq!(settlements[0].payload["requestId"], request_id); + assert_eq!(settlements[0].payload["replayAllowed"], false); + assert!(recovered + .execute(&command(7, "request.resolve", response)) + .is_err()); + assert_eq!(recovered.poll_events().unwrap(), events); + recovered.acknowledge_events(events.len()).unwrap(); + shutdown_recovered_acpx_fixture(&mut recovered, &directory); + fs::remove_dir_all(directory).unwrap(); + } +} + +#[test] +fn acpx_failed_or_cancelled_responses_retain_one_durable_settlement() { + for (mode, close) in [ + ("permissions-wrong-ack", false), + ("permissions-interactive", true), + ("resolutions", true), + ] { + let directory = temporary_directory("response-expiry"); + let (mut executor, config, request_id, _) = pending_acpx_runtime_request(&directory, mode); + if close { + executor + .execute(&command(4, "session.close", json!({}))) + .unwrap(); + } else { + let error = executor + .execute(&command( + 4, + "request.resolve", + acpx_runtime_resolution(mode, &request_id), + )) + .unwrap_err(); + assert!(error + .to_string() + .contains("did not confirm permission resolution")); + } + let events = executor.poll_events().unwrap(); + let requests: Vec<_> = events + .iter() + .filter(|event| event.event_type.starts_with("runtime_request.")) + .collect(); + assert_eq!(requests.len(), 1); + assert_eq!( + requests[0].event_type, + if close { + "runtime_request.cancelled" + } else { + "runtime_request.expired" + } + ); + assert_eq!(requests[0].payload["requestId"], request_id); + assert_eq!(requests[0].payload["replayAllowed"], false); + drop(executor); + let mut recovered = NativeProviderCommandExecutor::with_runner_config(&directory, &config); + assert_eq!(recovered.poll_events().unwrap(), events); + assert!(recovered + .execute(&command( + 5, + "request.resolve", + acpx_runtime_resolution(mode, &request_id) + )) + .is_err()); + recovered.acknowledge_events(events.len()).unwrap(); + assert!(recovered.poll_events().unwrap().is_empty()); + shutdown_recovered_acpx_fixture(&mut recovered, &directory); + fs::remove_dir_all(directory).unwrap(); + } +} + #[test] fn preserves_acpx_semantic_disposition_in_the_run_terminal() { let directory = temporary_directory("acpx-blocked"); @@ -820,18 +1355,83 @@ fn rejects_opencode_launch_profile_drift_across_fresh_recovery() { } #[test] -fn rejects_pi_before_starting_a_sidecar() { - let directory = temporary_directory("pi"); - let config = config(&directory); +fn rejects_pi_with_an_unqualified_model_before_starting_a_sidecar() { + let directory = temporary_directory("pi-model"); + let config = pi_acpx_config(&directory, "bootstrap"); + let mut payload = pi_prepare_payload(&directory, "bootstrap"); + // All Pi distribution and policy fields are correct. Admission must reject + // the model itself, not rely on the old blanket exclusion of this harness. + payload["provider"]["model"] = json!("gpt-5.6-sol"); let mut executor = NativeProviderCommandExecutor::with_runner_config(&directory, &config); let error = executor - .execute(&command( - 1, - "run.prepare", - prepare_payload(&directory, "pi"), - )) + .execute(&command(1, "run.prepare", payload)) .unwrap_err(); - assert!(error.to_string().contains("agent pi is not executable")); + assert!(error + .to_string() + .contains("does not match a qualified immutable profile")); assert!(!directory.join("acpx-runtime").exists()); + assert!(!directory.join("acpx-provider-state.json").exists()); fs::remove_dir_all(directory).unwrap(); } + +#[test] +fn publishes_only_changed_pi_controls_before_the_new_turn() { + for (mode, initially_available, turn_available) in [ + ("controls-lazy", false, true), + ("controls-downgrade", true, false), + ("controls", true, true), + ] { + let directory = temporary_directory(mode); + let config = pi_acpx_config(&directory, mode); + let mut executor = NativeProviderCommandExecutor::with_runner_config(&directory, &config); + executor + .execute(&command( + 1, + "run.prepare", + pi_prepare_payload(&directory, mode), + )) + .unwrap(); + let opened = executor + .execute(&command(2, "session.open", json!({}))) + .unwrap(); + assert_eq!( + opened.events[0].2["providerDescriptor"]["turnControls"], + json!({"steering":initially_available, "queuedFollowUp":initially_available}) + ); + + let started = executor + .execute(&command( + 3, + "turn.start", + json!({ + "text":"Work", "turnId":"provider-turn-controls" + }), + )) + .unwrap(); + assert_eq!(started.result["providerTurnId"], "provider-turn-controls"); + let expected_types = if initially_available != turn_available { + vec!["session.capabilities.updated", "turn.started"] + } else { + vec!["turn.started"] + }; + assert_eq!( + started + .events + .iter() + .map(|event| event.0.as_str()) + .collect::>(), + expected_types + ); + if initially_available != turn_available { + assert_eq!( + started.events[0].2["turnControls"], + json!({"steering":turn_available, "queuedFollowUp":turn_available}) + ); + } + let turn = &started.events.last().unwrap().2; + assert_eq!(turn["providerTurnId"], "provider-turn-controls"); + assert_eq!(turn["turn"]["id"], "provider-turn-controls"); + executor.shutdown().unwrap(); + fs::remove_dir_all(directory).unwrap(); + } +} diff --git a/packages/paperclip-runner/scripts/build-provider-pack.mjs b/packages/paperclip-runner/scripts/build-provider-pack.mjs index 167051e0be..0e6e036bf9 100644 --- a/packages/paperclip-runner/scripts/build-provider-pack.mjs +++ b/packages/paperclip-runner/scripts/build-provider-pack.mjs @@ -18,10 +18,11 @@ import { tmpdir } from "node:os"; import { dirname, join, relative, resolve } from "node:path"; import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; +import { parseProviderPackArguments, materializeCandidateProviderPack } from "./candidate-provider-pack.mjs"; const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const workspaceRoot = resolve(packageRoot, "../.."); -const outputArgument = process.argv.slice(2).find((value) => value !== "--"); +const { output: outputArgument, candidates } = parseProviderPackArguments(process.argv.slice(2)); const outputRoot = resolve( process.cwd(), outputArgument ?? join(packageRoot, "provider-pack"), @@ -34,7 +35,7 @@ if ( throw new Error(`Refusing unsafe provider-pack output path: ${outputRoot}`); } -const temporaryParent = mkdtempSync(join(tmpdir(), "paperclip-provider-pack-")); +const temporaryParent = realpathSync(mkdtempSync(join(tmpdir(), "paperclip-provider-pack-"))); const temporaryRoot = join(temporaryParent, "pack"); function canonicalJson(value) { @@ -163,6 +164,24 @@ try { mkdirSync(dirname(stableNodeCommand), { recursive: true, mode: 0o755 }); copyFileSync(process.execPath, stableNodeCommand); chmodSync(stableNodeCommand, 0o755); + const relocatedNode = spawnSync(stableNodeCommand, ["--version"], { + cwd: temporaryRoot, env: { PATH: "/usr/bin:/bin" }, encoding: "utf8", timeout: 10_000, + }); + if (relocatedNode.status !== 0 || relocatedNode.stdout.trim() !== `v${process.versions.node}`) { + throw new Error("Provider pack Node is not portable after relocation; build with a standalone Node distribution"); + } + + const candidateProviders = {}; + for (const provider of candidates) { + const assetPath = `provider-assets/${provider}/${process.platform}-${process.arch}`; + const metadata = await materializeCandidateProviderPack({ provider, outputRoot: join(temporaryRoot, assetPath) }); + if (typeof metadata?.version !== "string" || !metadata.version || metadata.version.length > 120 + || !/^sha256:[a-f0-9]{64}$/.test(metadata.profileDigest) + || !/^sha256:[a-f0-9]{64}$/.test(metadata.closureDigest)) throw new Error("Candidate builder omitted its pinned identity"); + candidateProviders[provider] = { version: metadata.version, profileDigest: metadata.profileDigest, + closureDigest: metadata.closureDigest, qualification: "pending", path: assetPath, + sha256: sha256Tree(join(temporaryRoot, assetPath)) }; + } // pnpm's generated .bin shims embed the temporary deployment directory in // NODE_PATH. That makes an otherwise identical provider pack hash differ on @@ -314,6 +333,7 @@ try { codex: "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3", }, + ...(candidates.length ? { candidateProviders } : {}), artifacts: { grokLauncher: { path: "dist/providers/grok/launcher.cjs", diff --git a/packages/paperclip-runner/scripts/candidate-provider-pack.mjs b/packages/paperclip-runner/scripts/candidate-provider-pack.mjs new file mode 100644 index 0000000000..083d8fbb3a --- /dev/null +++ b/packages/paperclip-runner/scripts/candidate-provider-pack.mjs @@ -0,0 +1,22 @@ +const CANDIDATES = new Set(["cursor", "copilot", "pi"]); + +export function parseProviderPackArguments(args) { + let output; + const candidates = []; + for (const value of args.filter(value => value !== "--")) { + if (value.startsWith("--candidate-providers=")) { + for (const provider of value.slice("--candidate-providers=".length).split(",").filter(Boolean)) { + if (!CANDIDATES.has(provider) || candidates.includes(provider)) throw new Error("Unknown or duplicate candidate provider"); + candidates.push(provider); + } + } else if (value.startsWith("--") || output !== undefined) throw new Error("Invalid provider-pack arguments"); + else output = value; + } + return { output, candidates }; +} + +/** Closed source-owned builder registry; provider branches add their exact pins. */ +export async function materializeCandidateProviderPack({ provider, outputRoot }) { + if (!CANDIDATES.has(provider)) throw new Error("Unknown candidate provider"); + throw new Error(`The ${provider} candidate distribution builder is not included in this source revision`); +} diff --git a/packages/paperclip-runner/scripts/candidate-provider-pack.test.mjs b/packages/paperclip-runner/scripts/candidate-provider-pack.test.mjs new file mode 100644 index 0000000000..c95cccff97 --- /dev/null +++ b/packages/paperclip-runner/scripts/candidate-provider-pack.test.mjs @@ -0,0 +1,14 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { parseProviderPackArguments, materializeCandidateProviderPack } from "./candidate-provider-pack.mjs"; + +test("candidate builds are explicit and leave qualified-only builds unchanged", () => { + assert.deepEqual(parseProviderPackArguments(["--", "/pack"]), { output: "/pack", candidates: [] }); + assert.deepEqual(parseProviderPackArguments(["/pack", "--candidate-providers=pi,cursor"]), { output: "/pack", candidates: ["pi", "cursor"] }); +}); +test("candidate builder cannot admit unknown providers, options or duplicate assets", async () => { + for (const args of [["--candidate-providers=cursor,cursor"], ["--candidate-providers=other"], ["--executable=/tmp/x"], ["/one", "/two"]]) { + assert.throws(() => parseProviderPackArguments(args)); + } + await assert.rejects(materializeCandidateProviderPack({ provider: "arbitrary", outputRoot: "/tmp/unused" }), /Unknown candidate/); +}); diff --git a/packages/paperclip-runner/src/backends/codex-acpx-native-backend.ts b/packages/paperclip-runner/src/backends/codex-acpx-native-backend.ts index ee3cd71e0e..19483fb425 100644 --- a/packages/paperclip-runner/src/backends/codex-acpx-native-backend.ts +++ b/packages/paperclip-runner/src/backends/codex-acpx-native-backend.ts @@ -5,6 +5,7 @@ import { type CodexAcpxDriverOptions, } from "../drivers/acpx/codex-acpx-driver.js"; import { resolveQualifiedAcpxProfile } from "../drivers/acpx/qualified-profiles.js"; +import { ACPX_CAPABILITY_PROFILES } from "../drivers/acpx/capability-profiles.js"; import { HarnessDriverBackend } from "./harness-driver-backend.js"; import { nativeSystemInstructions, @@ -13,7 +14,7 @@ import { export interface CodexAcpxNativeSessionBackendOptions extends Omit< CodexAcpxDriverOptions, - "model" | "permissionMode" | "systemInstructions" + "model" | "permissionMode" | "systemInstructions" | "providerPolicy" > {} export type AcpxNativeSessionBackendOptions = @@ -30,11 +31,6 @@ export function createAcpxNativeSessionBackend( if (input.provider.kind !== "acpx") { throw new Error("ACPX backend requires provider kind acpx"); } - if (input.provider.agent === "pi") { - throw new Error( - "Pi ACPX backend is unavailable until descriptor-confined verified launch is implemented", - ); - } const qualifiedProfile = resolveQualifiedAcpxProfile( input.provider.agent, input.provider.model, @@ -58,6 +54,10 @@ export function createAcpxNativeSessionBackend( } } + if (ACPX_CAPABILITY_PROFILES[input.provider.agent].qualification !== "qualified") { + throw new Error("ACPX candidate direct execution requires completed qualification; use the host-controlled runnerd evaluation path"); + } + const constraints = nativeTaskConstraints(input); const systemInstructions = [ nativeSystemInstructions(input), @@ -73,6 +73,7 @@ export function createAcpxNativeSessionBackend( model: input.provider.model, permissionMode: input.provider.permissionMode ?? "approve-reads", systemInstructions, + providerPolicy: { readOnly: "executionMode" in input && input.executionMode === "plan" }, }), ); } diff --git a/packages/paperclip-runner/src/backends/codex-native-backend.ts b/packages/paperclip-runner/src/backends/codex-native-backend.ts index 1cdd0bb6af..6f8efb8e15 100644 --- a/packages/paperclip-runner/src/backends/codex-native-backend.ts +++ b/packages/paperclip-runner/src/backends/codex-native-backend.ts @@ -1,4 +1,5 @@ import { createCodexTaskEnvelope } from "../contracts/codex.js"; +import { ACPX_CAPABILITY_PROFILES } from "../drivers/acpx/capability-profiles.js"; import { NATIVE_EXECUTION_INPUT_SCHEMA } from "../contracts/native-execution.js"; import type { NativeExecutionInput } from "../contracts/native-execution.js"; import type { PersistedHarnessSession } from "../contracts/harness-driver.js"; @@ -85,14 +86,9 @@ function transportDriverIdentity(input: NativeExecutionInput): { version: input.provider.agentCoreProfile.qualificationRevision, }; case "acpx": - if (input.provider.agent === "pi") { - throw new Error( - "Native ACPX backend for pi is unavailable until descriptor-confined verified launch is implemented", - ); - } return { kind: "acpx_runtime", - displayName: `${input.provider.agent === "grok" ? "Grok Build" : input.provider.agent === "claude" ? "Claude" : "Codex"} via ACPX`, + displayName: `${ACPX_CAPABILITY_PROFILES[input.provider.agent].displayName} via ACPX`, version: "0.13.1", }; default: diff --git a/packages/paperclip-runner/src/backends/harness-driver-backend.test.ts b/packages/paperclip-runner/src/backends/harness-driver-backend.test.ts index 317f80e5a1..d550d594d6 100644 --- a/packages/paperclip-runner/src/backends/harness-driver-backend.test.ts +++ b/packages/paperclip-runner/src/backends/harness-driver-backend.test.ts @@ -97,6 +97,24 @@ const driver: HarnessDriver = { }; describe("HarnessDriverBackend", () => { + it("refreshes negotiated controls after a warm handshake and separates follow-ups", async () => { + let controls = { steering: false, queuedFollowUp: false }; + const steer = vi.fn(async () => {}); + const session = Object.assign(new FakeHarnessSession(), { steer, turnControlCapabilities: () => ({ ...controls }) }); + const backend = new HarnessDriverBackend({ ...driver, openSession: async () => session }); + const opened = await backend.openSession({ identity: { + runId: "run-1", sessionId: "session-1", companyId: "company-1", issueId: "issue-1", agentId: "agent-1", + } }); + expect(await opened.capabilities()).toMatchObject({ steering: false, queuedFollowUp: false }); + controls = { steering: false, queuedFollowUp: true }; + expect(await opened.capabilities()).toMatchObject(controls); + expect(() => opened.steer!({ turnId: "turn-1", message: { role: "user", text: "steer" }, correlationId: "c-1" })).toThrow(); + await opened.steer!({ turnId: "turn-1", message: { role: "user", text: "follow" }, correlationId: "c-2", mode: "follow_up" }); + expect(steer).toHaveBeenCalledWith(expect.objectContaining({ mode: "follow_up" })); + controls = { steering: true, queuedFollowUp: false }; + expect(await opened.capabilities()).toMatchObject(controls); + expect(() => opened.steer!({ turnId: "turn-1", message: { role: "user", text: "follow" }, correlationId: "c-3", mode: "follow_up" })).toThrow(); + }); it.each([false, true])("honors driver steering support even when the transport exposes a steer method (%s)", async supported => { const steer = vi.fn(async () => { throw new Error("provider does not support steering"); }); const session = Object.assign(new FakeHarnessSession(), { steer }); diff --git a/packages/paperclip-runner/src/backends/harness-driver-backend.ts b/packages/paperclip-runner/src/backends/harness-driver-backend.ts index ebd6ef2d3c..fd280b5df8 100644 --- a/packages/paperclip-runner/src/backends/harness-driver-backend.ts +++ b/packages/paperclip-runner/src/backends/harness-driver-backend.ts @@ -450,10 +450,12 @@ class HarnessNativeSession implements NativeSession { } async capabilities() { + const negotiated = this.#session.turnControlCapabilities?.(); return { resume: true, typedEvents: true, - steering: this.#steeringSupported && this.#session.steer !== undefined, + steering: (negotiated?.steering ?? this.#steeringSupported) && this.#session.steer !== undefined, + queuedFollowUp: negotiated?.queuedFollowUp === true && this.#session.steer !== undefined, interruption: this.#session.interrupt !== undefined, structuredResult: true, read: this.#session.read !== undefined, @@ -682,13 +684,15 @@ class HarnessNativeSession implements NativeSession { } steer(input: { + mode?: "steer" | "follow_up"; turnId: string; message: { role: "user"; text: string }; correlationId?: string; }) { this.#assertProtocolIntegrity(); - if (!this.#steeringSupported || this.#session.steer === undefined) - throw new Error("steering is unavailable"); + const negotiated = this.#session.turnControlCapabilities?.(); + const supported = input.mode === "follow_up" ? negotiated?.queuedFollowUp === true : (negotiated?.steering ?? this.#steeringSupported); + if (!supported || this.#session.steer === undefined) throw new Error("steering or queued follow-up is unavailable"); return this.#withProtocolIntegrity(() => this.#session.steer!(input)); } diff --git a/packages/paperclip-runner/src/backends/native-backend-factory.test.ts b/packages/paperclip-runner/src/backends/native-backend-factory.test.ts index 7bb8daf7e2..33b8db8c47 100644 --- a/packages/paperclip-runner/src/backends/native-backend-factory.test.ts +++ b/packages/paperclip-runner/src/backends/native-backend-factory.test.ts @@ -1,3 +1,4 @@ +import { QUALIFIED_ACPX_PROFILES, resolveQualifiedAcpxProfile } from "../drivers/acpx/qualified-profiles.js"; import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -497,6 +498,16 @@ describe("native backend factory", () => { "requires an instance runtime directory", ); }); + it.each(["pi", "cursor", "copilot"] as const)("constructs %s identity on the supplied runnerd transport without starting a provider", async agent => { + const input = acpxExecution(); + if (input.provider.kind !== "acpx") throw new Error("invalid fixture"); + const model = agent === "pi" ? QUALIFIED_ACPX_PROFILES.pi.qualificationModel : "explicit-fixture-model"; + Object.assign(input.provider, { agent, model, profile: resolveQualifiedAcpxProfile(agent, model) }); + const backend = createNativeSessionBackend(input, { + codexTransportFactory: () => { throw new Error("descriptor must not launch the transport"); }, + }); + await expect(backend.descriptor()).resolves.toMatchObject({ name: "acpx_runtime", version: "0.13.1" }); + }); it.each(["claude" as const])( "constructs the qualified %s ACPX backend", @@ -512,12 +523,15 @@ describe("native backend factory", () => { }, ); - it("rejects Pi before constructing an ACPX backend", () => { - expect(() => - createNativeSessionBackend(acpxExecution("pi"), { - acpxRuntimeDirectory: "/runtime", - }), - ).toThrow("descriptor-confined verified launch"); + it.each(["pi", "cursor", "copilot"] as const)("rejects unqualified %s direct execution even with an exact persisted profile", agent => { + const input = acpxExecution(); + if (input.provider.kind !== "acpx") throw new Error("invalid fixture"); + const model = agent === "pi" ? QUALIFIED_ACPX_PROFILES.pi.qualificationModel : "explicit-fixture-model"; + const profile = resolveQualifiedAcpxProfile(agent, model); + Object.assign(input.provider, { agent, model, profile }); + expect(() => createNativeSessionBackend(input, { acpxRuntimeDirectory: "/runtime", + acpxEnvironment: { COPILOT_GITHUB_TOKEN: "explicit-fixture", CURSOR_API_KEY: "explicit-fixture", OPENROUTER_API_KEY: "explicit-fixture" }, + })).toThrow("ACPX candidate direct execution requires completed qualification"); }); it("rejects a Codex ACPX snapshot that drifts from its qualified profile", () => { diff --git a/packages/paperclip-runner/src/backends/native-backend-factory.ts b/packages/paperclip-runner/src/backends/native-backend-factory.ts index 0e50b6d1ea..630bfa7798 100644 --- a/packages/paperclip-runner/src/backends/native-backend-factory.ts +++ b/packages/paperclip-runner/src/backends/native-backend-factory.ts @@ -77,11 +77,6 @@ export function createNativeSessionBackend( }); } if (input.provider.kind === "acpx") { - if (input.provider.agent === "pi") { - throw new Error( - "Native ACPX backend for pi is unavailable until descriptor-confined verified launch is implemented", - ); - } if (!options.acpxRuntimeDirectory?.trim()) { throw new Error("ACPX backend requires an instance runtime directory"); } diff --git a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts index 927631ba91..35cfd2635c 100644 --- a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts +++ b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts @@ -4,6 +4,9 @@ import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, it, vi } from "vitest"; +import { normalizeAcpxPermission } from "../drivers/acpx/acp-permission-adapter.js"; +import { ACPX_CAPABILITY_PROFILES } from "../drivers/acpx/capability-profiles.js"; +import { resolveQualifiedAcpxProfile } from "../drivers/acpx/qualified-profiles.js"; import { ACPX_SIDECAR_PROTOCOL_VERSION } from "../drivers/acpx/sidecar-protocol.js"; import { canonicalProviderEventsFromAcpxRuntimeEvent } from "../provider-events.js"; import { @@ -31,6 +34,29 @@ afterEach(async () => { }); describe("qualified ACPX runtime sidecar", () => { + it.each(["pi", "copilot", "cursor", "codex", "claude"])("offers verified session permission grants only for %s", async agent => { + const source = readFileSync(fileURLToPath(new URL("./acpx-runtime-sidecar.ts", import.meta.url)), "utf8"); + const start = source.indexOf(" const { signal } = context;", source.indexOf("async function waitForPermission")); + const end = source.indexOf("\nasync function waitForInput", start); + expect(start).toBeGreaterThan(0); + const permissions = new Map(); + const emitted: Array<{ choices: Array<{ key: string }> }> = []; + const wait = new Function("permissions", "openParams", "normalizeAcpxPermission", "emit", + `let turnId = "turn-1", requestSequence = 0; const MAX_PENDING_INPUTS = 512; + const stableRequestId = () => "request-1"; const requireAcpxResponseDelivery = c => c.responseDelivery; + return async function(activeTurnId, request, context) { ${source.slice(start, end)}`)( + permissions, { agent }, normalizeAcpxPermission, (_event: string, payload: { choices: Array<{ key: string }> }) => emitted.push(payload), + ); + const abort = new AbortController(); + const pending = wait("turn-1", { sessionId: "session", inferredKind: "edit", raw: { + sessionId: "session", toolCall: { toolCallId: "call", title: "Edit file" }, + options: ["allow_once", "allow_always", "reject_once"].map(kind => ({ kind, optionId: kind, name: kind })), + } }, { signal: abort.signal, responseDelivery: Promise.resolve() }); + expect(emitted[0]!.choices.some(choice => choice.key === "accept_for_session")).toBe(agent === "pi" || agent === "copilot"); + abort.abort(); + await expect(pending).resolves.toEqual({ outcome: "cancel" }); + expect(permissions.size).toBe(0); + }); it("preserves ACP input presence through the bounded sidecar handoff", () => { const source = readFileSync( fileURLToPath(new URL("./acpx-runtime-sidecar.ts", import.meta.url)), @@ -528,12 +554,27 @@ describe("qualified ACPX runtime sidecar", () => { }, ); + it.each([ + ["pi", "openrouter/deepseek/deepseek-v4-flash-0731"], + ["cursor", "explicit-cursor-model"], + ["copilot", "explicit-copilot-model"], + ] as const)("initializes the declared %s candidate without promoting its profile", async (agent, model) => { + const sidecar = startSidecar(); + sidecar.write(initializeRequest(1, agent, model)); + const frame = await sidecar.next((value) => value.id === 1); + expect(frame).toMatchObject({ id: 1, ok: true }); + const result = frame.result as Record; + expect(result.profile).toEqual(resolveQualifiedAcpxProfile(agent, model)); + expect(result.profile).toMatchObject({ reportedModelId: model }); + expect(ACPX_CAPABILITY_PROFILES[agent].qualification).toBe("pending"); + }); + it("fails closed after an unsupported provider bootstrap", async () => { const sidecar = startSidecar(); sidecar.write( initializeRequest( 1, - "pi", + "unknown-provider", "openrouter/deepseek/deepseek-v4-flash-0731", ), ); @@ -545,7 +586,7 @@ describe("qualified ACPX runtime sidecar", () => { ok: false, error: { code: "acpx_sidecar_command_failed", - message: "ACPX agent must be claude, codex, or grok", + message: "ACPX agent must be claude, codex, grok, cursor, copilot, or pi", retryable: false, }, }); @@ -681,7 +722,7 @@ function loadWaitForTool(input: { "utf8", ); const start = source.indexOf("async function waitForTool"); - const end = source.indexOf("\nasync function waitForInput", start); + const end = source.indexOf("\nasync function waitForPermission", start); if (start < 0 || end < 0) throw new Error("waitForTool source not found"); const functionSource = source .slice(start, end) diff --git a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts index ddb436cfa5..3f32d5baf8 100644 --- a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts +++ b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts @@ -1,14 +1,20 @@ #!/usr/bin/env node import { createHash } from "node:crypto"; import { createInterface } from "node:readline"; +import { deliverAcpxResponse, requireAcpxResponseDelivery } from "../drivers/acpx/response-delivery.js"; +import { createAcpxSidecarHostEnvironment } from "../drivers/acpx/environment.js"; import type { AcpElicitationContext, AcpElicitationRequest, AcpElicitationResponse, AcpRuntimeEvent, + AcpPermissionRequest, + AcpPermissionDecision, } from "acpx/runtime"; +import { acpxProfileClientCapabilities, bindAcpxExtensionTurn, validateAcpxRichEvent, createAcpxProfileExtensionAdapter, type AcpxExtensionInput } from "../drivers/acpx/profile-extensions.js"; +import type { PaperclipQuestionSet } from "../contracts/question-set.js"; import { createAcpxToolEventNormalizer, createGrokMessageNormalizer } from "../provider-events.js"; import { parseNativeRuntimeContext } from "../contracts/runtime-context.js"; import { @@ -25,6 +31,7 @@ import { } from "../drivers/acpx/acp-question-adapter.js"; import { openCodexAcpxRuntime } from "../drivers/acpx/codex-runtime-adapter.js"; import { AcpxApprovalRequiredError } from "../drivers/acpx/permission-policy.js"; +import { normalizeAcpxPermission, type NormalizedAcpxPermission } from "../drivers/acpx/acp-permission-adapter.js"; import { acpxGoalProjection } from "../drivers/acpx/session-goals.js"; import { acpxProviderSessionIdentity } from "../drivers/acpx/recovery-identity.js"; import { @@ -56,6 +63,7 @@ import { import { safeAcpxLocations } from "./acpx-sidecar-locations.js"; import { persistedAcpxTurnUsage, + acpxUsageEstimateNotice, qualifiedAcpxUsageBreakdown, } from "../drivers/acpx/usage-accounting.js"; import { validatePrpStructuredRunResult } from "../protocol/replay-contract.js"; @@ -80,6 +88,9 @@ import { verifyOpenedAcpxSidecarHost, } from "./acpx-sidecar-lifecycle.js"; +import { AcpxTurnControlLedger, parseAcpxTurnControl } from "../drivers/acpx/turn-controls.js"; + +const turnControls = new AcpxTurnControlLedger(); const MAX_PENDING_TOOLS = 512; const MAX_PENDING_INPUTS = 16; let goalSourceRevision = 0; @@ -114,8 +125,18 @@ interface PendingTool { interface PendingInput { turnId: string; - normalized: NormalizedAcpForm; - settle(response: AcpElicitationResponse): void; + responseDelivery: Promise; + questionSet: PaperclipQuestionSet; + prepareResolution(resolution: HarnessRuntimeRequestResolution): () => void; + cancel(): void; + cleanup(): void; +} + +interface PendingPermission { + turnId: string; + responseDelivery: Promise; + normalized: NormalizedAcpxPermission; + settle(response: AcpPermissionDecision): void; cleanup(): void; } @@ -136,6 +157,7 @@ let initializedModel: string | null = null; let inputClosed = false; const tools = new Map(); const inputs = new Map(); +const permissions = new Map(); const lines = createInterface({ input: process.stdin, @@ -260,11 +282,13 @@ async function dispatch( normalizedSessionId: params.normalizedSessionId, workingDirectory: params.workingDirectory, agent: params.agent, + clientCapabilities: acpxProfileClientCapabilities(params.agent), model: params.model, permissionMode: params.permissionMode, + providerPolicy: params.providerPolicy, systemInstructions: params.systemInstructions, runtimeContext: params.runtimeContext, - environment: process.env, + environment: createAcpxSidecarHostEnvironment(process.env, params.agent, params.normalizedSessionId), expectedIdentity: params.expectedIdentity, semanticTools: { tools: params.tools, @@ -306,6 +330,7 @@ async function dispatch( ), sidecarPid: process.pid, status: opened.status, + turnControls: openedHost.steeringCapability() ?? { steering: false, queuedFollowUp: false }, }; } if (request.command === "run.attach") { @@ -330,22 +355,54 @@ async function dispatch( if (turnId) throw new Error("ACPX sidecar already has an active turn"); const currentTurnId = boundedIdentity(request.params.turnId, "turnId"); turnId = currentTurnId; + turnControls.begin(currentTurnId); let runtimeTurn: AcpxRuntimeTurn; + const extensions = bindAcpxExtensionTurn({ + adapter: createAcpxProfileExtensionAdapter(openParams!.agent, { + workspacePath: openParams!.workingDirectory, sessionId: activeHost.identity().backendSessionId, turnId: currentTurnId, + }), + active: () => turnId === currentTurnId && host === activeHost, + sessionId: activeHost.identity().backendSessionId, + waitForInput: (input, context) => waitForExtensionInput(currentTurnId, input, context), + emit: event => emit("runtime.rich_event", { ...event }, currentTurnId), + }); let usageBefore: unknown; try { usageBefore = await readSidecarHostStatusWithin(activeHost); runtimeTurn = activeHost.startTurn({ requestId: `${runId}:${currentTurnId}`, text: boundedText(request.params.message, "message", 1024 * 1024), + onExtensionRequest: extensions.onExtensionRequest, + onExtensionNotification: extensions.onExtensionNotification, onElicitation: (providerRequest, context) => waitForInput(currentTurnId, providerRequest, context), + onPermissionRequest: (providerRequest, context) => + waitForPermission(currentTurnId, providerRequest, context), }); } catch (error) { turnId = null; throw error; } - void pumpTurn(currentTurnId, runtimeTurn, activeHost, usageBefore); - return { turnId: currentTurnId }; + void pumpTurn(currentTurnId, runtimeTurn, activeHost, usageBefore, extensions.drain); + // Warm sessions may defer initialize until their first prompt. Publish only + // the capabilities of that live initialized connection, never old disk state. + await runtimeTurn.promptStarted; + return { turnId: currentTurnId, turnControls: activeHost.steeringCapability() ?? { steering: false, queuedFollowUp: false } }; + } + if (request.command === "turn.steer") { + const control = parseAcpxTurnControl(request.params); + const activeHost = requireHost(); + if (!runId || control.turnId !== turnId) throw new Error("cannot control a stale ACPX turn"); + const capability = activeHost.steeringCapability(); + if (!(control.mode === "steer" ? capability?.steering : capability?.queuedFollowUp)) { + throw new Error("ACP provider did not negotiate this turn control"); + } + turnControls.reserve(control, turnId); + const runtimeRequestId = `${runId}:${control.turnId}`; + if (control.mode === "steer") await activeHost.steerActiveTurn(control.message, runtimeRequestId); + else await activeHost.queueFollowUp(control.message, runtimeRequestId); + if (host !== activeHost || turnId !== control.turnId) throw new Error("ACP turn settled before control acknowledgement"); + return { accepted: true, turnId: control.turnId, controlId: control.controlId, mode: control.mode }; } if (request.command === "turn.cancel") { const expected = boundedIdentity(request.params.turnId, "turnId"); @@ -360,9 +417,17 @@ async function dispatch( return { cancelled: true }; } if (request.command === "permission.resolve") { - throw new Error( - "ACPX permissions are resolved by the admitted runner policy", - ); + const requestId = boundedIdentity(request.params.requestId, "requestId"); + const expectedTurnId = boundedIdentity(request.params.turnId, "turnId"); + const pending = permissions.get(requestId); + if (!pending || pending.turnId !== expectedTurnId || turnId !== expectedTurnId) { + throw new Error("permission request is stale or unknown"); + } + const resolution = parseHarnessRuntimeRequestResolution("permission_approval", request.params.resolution); + const decision = pending.normalized.resolve(resolution); + if (!permissions.delete(requestId)) throw new Error("permission request lost its settlement race"); + pending.cleanup(); + return await deliverAcpxResponse(pending.responseDelivery, () => pending.settle(decision)); } if (request.command === "input.resolve") { const requestId = boundedIdentity(request.params.requestId, "requestId"); @@ -378,17 +443,13 @@ async function dispatch( const resolution = parseHarnessRuntimeRequestResolution( "elicitation", request.params.resolution, - pending.normalized.questionSet, - ); - const providerResponse = elicitationResponse( - pending.normalized, - resolution, + pending.questionSet, ); + const deliver = pending.prepareResolution(resolution); if (!inputs.delete(requestId)) throw new Error("input request lost its settlement race"); pending.cleanup(); - pending.settle(providerResponse); - return { resolved: true }; + return await deliverAcpxResponse(pending.responseDelivery, deliver); } if (request.command === "tool.resolve") { const callId = boundedIdentity(request.params.callId, "callId"); @@ -527,6 +588,7 @@ async function pumpTurn( runtimeTurn: AcpxRuntimeTurn, activeHost: AcpxRuntimeHost, usageBefore: unknown, + drainExtensions: () => Promise, ): Promise { let terminal: Record; try { @@ -546,13 +608,17 @@ async function pumpTurn( ); } const result = await runtimeTurn.result; + await drainExtensions(); try { const usage = persistedAcpxTurnUsage( usageBefore, await readSidecarHostStatusWithin(activeHost), runtimeTurn.requestId, + openParams?.agent, ); if (usage) { + const estimate = acpxUsageEstimateNotice(usage, `${currentTurnId}:usage-estimate`); + if (estimate) { validateAcpxRichEvent(estimate); emit("runtime.rich_event", { ...estimate }, currentTurnId); } emit( "runtime.event", sanitizeRuntimeEvent(usage as unknown as AcpRuntimeEvent), @@ -670,6 +736,37 @@ async function waitForTool(call: RunnerToolCall): Promise { }); } +async function waitForPermission( + activeTurnId: string, + request: AcpPermissionRequest, + context: { signal: AbortSignal; responseDelivery?: Promise }, +): Promise { + const { signal } = context; + if (turnId !== activeTurnId || signal.aborted || permissions.size >= MAX_PENDING_INPUTS) { + return { outcome: "cancel" }; + } + const normalized = normalizeAcpxPermission(request, ["pi", "copilot"].includes(openParams?.agent ?? "") ? { allowAlwaysScope: "session" } : {}); + const responseDelivery = requireAcpxResponseDelivery(context); + const requestId = stableRequestId(activeTurnId, ++requestSequence, normalized.toolCallId); + return await new Promise((settle) => { + const abort = () => { + if (!permissions.delete(requestId)) return; + signal.removeEventListener("abort", abort); + settle({ outcome: "cancel" }); + }; + permissions.set(requestId, { + turnId: activeTurnId, normalized, settle, responseDelivery, + cleanup: () => signal.removeEventListener("abort", abort), + }); + signal.addEventListener("abort", abort, { once: true }); + if (signal.aborted) { abort(); return; } + emit("runtime.permission_requested", { + requestId, kind: normalized.kind, title: normalized.title, + toolCallId: normalized.toolCallId, choices: normalized.choices, + }, activeTurnId); + }); +} + async function waitForInput( activeTurnId: string, request: AcpElicitationRequest, @@ -710,38 +807,42 @@ async function waitForInput( ); return { action: "cancel" }; } - const requestId = stableRequestId( - activeTurnId, - ++requestSequence, - context.requestId, - ); - emit( - "runtime.input_requested", - { - requestId, - questionSet: normalized.questionSet, - origin: { - adapter: "acpx-runtime-sidecar", - provider: openParams?.agent ?? initializedAgent ?? "unknown", - method: "elicitation/create", - }, - }, - activeTurnId, - ); + return await waitForExtensionInput(activeTurnId, { + method: "elicitation/create", questionSet: normalized.questionSet, + resolve: resolution => elicitationResponse(normalized, resolution) as unknown as Record, + cancel: () => ({ action: "cancel" }), + }, context) as unknown as AcpElicitationResponse; +} + +async function waitForExtensionInput( + activeTurnId: string, + input: AcpxExtensionInput, + context: { requestId: string | number | null; signal: AbortSignal; responseDelivery?: Promise }, +): Promise> { + if (turnId !== activeTurnId || context.signal.aborted || inputs.size >= MAX_PENDING_INPUTS) return input.cancel(); + const responseDelivery = requireAcpxResponseDelivery(context); + const requestId = stableRequestId(activeTurnId, ++requestSequence, context.requestId); return await new Promise((settle) => { const abort = () => { const pending = inputs.get(requestId); if (!pending || !inputs.delete(requestId)) return; pending.cleanup(); - settle({ action: "cancel" }); + settle(input.cancel()); }; - context.signal.addEventListener("abort", abort, { once: true }); inputs.set(requestId, { - turnId: activeTurnId, - normalized, - settle, + turnId: activeTurnId, questionSet: input.questionSet, responseDelivery, + prepareResolution: resolution => { + const response = input.resolve(resolution); + return () => settle(response); + }, + cancel: () => settle(input.cancel()), cleanup: () => context.signal.removeEventListener("abort", abort), }); + context.signal.addEventListener("abort", abort, { once: true }); + emit("runtime.input_requested", { + requestId, questionSet: input.questionSet, + origin: { adapter: "acpx-runtime-sidecar", provider: openParams?.agent ?? initializedAgent ?? "unknown", method: input.method }, + }, activeTurnId); if (context.signal.aborted) abort(); }); } @@ -763,6 +864,11 @@ function elicitationResponse( } function rejectTurnWaiters(terminalTurnId: string, message: string): void { + for (const [requestId, pending] of permissions) { + if (pending.turnId !== terminalTurnId || !permissions.delete(requestId)) continue; + pending.cleanup(); + pending.settle({ outcome: "cancel" }); + } for (const [callId, pending] of tools) { if (pending.turnId !== terminalTurnId || !tools.delete(callId)) continue; pending.cleanup(); @@ -772,7 +878,7 @@ function rejectTurnWaiters(terminalTurnId: string, message: string): void { if (pending.turnId !== terminalTurnId || !inputs.delete(requestId)) continue; pending.cleanup(); - pending.settle({ action: "cancel" }); + pending.cancel(); } } @@ -1054,6 +1160,7 @@ function parseOpenParams( model, permissionMode: requiredPermissionMode(value.permissionMode), permissionModePinned: value.permissionModePinned === true, + ...(value.providerPolicy == null ? {} : { providerPolicy: parseProviderPolicy(value.providerPolicy) }), systemInstructions: boundedText( value.systemInstructions, "systemInstructions", @@ -1069,6 +1176,14 @@ function parseOpenParams( }; } +function parseProviderPolicy(value: unknown): { readOnly: boolean } { + const policy = record(value); + if (typeof policy.readOnly !== "boolean" || Object.keys(policy).some(key => key !== "readOnly")) { + throw new Error("providerPolicy requires only an explicit readOnly boolean"); + } + return { readOnly: policy.readOnly }; +} + function parseTools(value: unknown): Readonly>[] { if (!Array.isArray(value) || value.length > 512) { throw new Error("tools must be an array with at most 512 entries"); @@ -1208,8 +1323,8 @@ function requireHost( } function requireQualifiedAgent(value: unknown): QualifiedAcpxAgent { - if (value !== "codex" && value !== "claude" && value !== "grok") { - throw new Error("ACPX agent must be claude, codex, or grok"); + if (value !== "grok" && value !== "codex" && value !== "claude" && value !== "pi" && value !== "cursor" && value !== "copilot") { + throw new Error("ACPX agent must be claude, codex, grok, cursor, copilot, or pi"); } return value; } diff --git a/packages/paperclip-runner/src/cli/eval-session-contract.test.ts b/packages/paperclip-runner/src/cli/eval-session-contract.test.ts index 361a7d002d..7082be9ad5 100644 --- a/packages/paperclip-runner/src/cli/eval-session-contract.test.ts +++ b/packages/paperclip-runner/src/cli/eval-session-contract.test.ts @@ -1,8 +1,10 @@ -import { chmod, mkdtemp, readFile, rm, stat } from "node:fs/promises"; +import { createHash } from "node:crypto"; +import { chmod, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; +import { createCapabilityFixtureState } from "../mock-core/capability-control-plane-types.js"; import { parseNativeRuntimeContext } from "../contracts/runtime-context.js"; import type { CapabilityLiveSessionSnapshot } from "../live/live-session.js"; @@ -12,9 +14,11 @@ import { } from "./eval-session-contract.js"; import { boundedEvalSessionUsage, + runEvalSessionCli, evalRuntimeSystemInstructions, evalSessionProviderVersion, prepareEvalRuntimeContext, + parseEvalSessionCliArgs, } from "./eval-session.js"; function request(overrides: Record = {}): unknown { @@ -62,6 +66,25 @@ function agentCoreProfile(overrides: Record = {}) { } describe("eval-session request contract", () => { + it.each(["pi", "cursor", "copilot"] as const)("admits %s only with the matching CLI diagnostic opt-in", (agent) => { + const value = request({ provider: "acpx", acpxAgent: agent, model: "explicit-provider-model" }); + expect(() => parseEvalSessionRequest(value)).toThrow("--candidate-profile"); + expect(parseEvalSessionRequest(value, { candidateProfile: agent })).toMatchObject({ acpxAgent: agent, model: "explicit-provider-model" }); + expect(() => parseEvalSessionRequest(value, { candidateProfile: agent === "pi" ? "cursor" : "pi" })).toThrow("must match"); + expect(() => parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: agent, model: "" }), { candidateProfile: agent })).toThrow("request.model"); + expect(() => parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: "codex", session: { acpxAgent: agent } }))).toThrow("session.acpxAgent must match"); + expect(() => parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: agent, candidateProfile: agent }))).toThrow("--candidate-profile"); + }); + + it("accepts only known diagnostic flags and rejects ambiguous repeated arguments", () => { + const args = ["--request", "/tmp/request.json", "--output", "/tmp/result.json"]; + expect(parseEvalSessionCliArgs([...args, "--candidate-profile", "pi"])).toMatchObject({ candidateProfile: "pi" }); + expect(() => parseEvalSessionCliArgs([...args, "--candidate-profile", "codex"])).toThrow("must be pi, cursor, or copilot"); + expect(() => parseEvalSessionCliArgs([...args, "--candidate-profile"])).toThrow("missing"); + expect(() => parseEvalSessionCliArgs([...args, "--request", "/tmp/other.json"])).toThrow("duplicate"); + expect(() => parseEvalSessionRequest(request(), { candidateProfile: "pi" })).toThrow("must match"); + }); + it("materializes a production-v3 runtime context for direct live providers", async () => { const workspace = await mkdtemp(join(tmpdir(), "paperclip-eval-context-")); let instructionRoot: string | null = null; @@ -145,11 +168,11 @@ describe("eval-session request contract", () => { })))).toBe("17"); }); - it("rejects Pi and accepts both qualified remote provider profiles", () => { + it("requires explicit Pi diagnosis and accepts both qualified remote provider profiles", () => { expect(() => parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: "pi", - }))).toThrow("Pi ACPX profile is not available"); + }))).toThrow("--candidate-profile"); expect(parseEvalSessionRequest(request({ provider: "aws_agentcore", driver: "aws_agentcore_harness_api", @@ -226,7 +249,9 @@ describe("eval-session usage", () => { it("preserves Grok's missing usage as unknown instead of manufacturing zero tokens or cost", () => { const parsed = { ...parseEvalSessionRequest(request()), provider: "acpx" as const, acpxAgent: "grok" as const }; const turn = { turnId: "turn-1", status: "completed" as const, assistantText: "done", snapshot: { usageLedger: [] } as unknown as CapabilityLiveSessionSnapshot }; - expect(boundedEvalSessionUsage(parsed, turn)).toBeNull(); + expect(() => boundedEvalSessionUsage(parsed, turn)).toThrow("budget cost coverage is unavailable"); + expect(turn.status).toBe("completed"); + expect(turn.snapshot.usageLedger).toEqual([]); expect(() => boundedEvalSessionUsage({ ...parsed, provider: "codex", acpxAgent: undefined }, turn)).toThrow(); }); @@ -265,6 +290,60 @@ describe("eval-session usage", () => { })).toThrow("agent turn limit exceeded"); }); + it.each(["pi", "cursor", "copilot"] as const)("keeps %s oracle results when the current turn explicitly has unavailable usage", (agent) => { + const parsed = parseEvalSessionRequest(request({ + provider: "acpx", acpxAgent: agent, model: "exact-provider-model", + }), { candidateProfile: agent }); + const unavailable = { + turnId: "turn-candidate", attemptId: "attempt-1", agent, + reason: "provider_did_not_report_usage" as const, + tokenUsage: null, costNanodollars: null, observedAt: "2026-09-28T19:18:10.000Z", + }; + const snapshot = { + config: { provider: "acpx", acpxAgent: agent }, + usageLedger: [], usageUnavailable: [unavailable], + } as unknown as CapabilityLiveSessionSnapshot; + const turn = { turnId: unavailable.turnId, status: "completed" as const, assistantText: "Upgrade your plan to continue", snapshot }; + expect(() => boundedEvalSessionUsage(parsed, turn)).toThrow("budget cost coverage is unavailable"); + expect(turn.assistantText).toBe("Upgrade your plan to continue"); + expect(snapshot.usageLedger).toEqual([]); + expect(() => boundedEvalSessionUsage(parsed, { ...turn, turnId: "other-turn" })) + .toThrow("omitted usage accounting"); + expect(() => boundedEvalSessionUsage(parsed, { + ...turn, snapshot: { ...snapshot, usageUnavailable: [{ ...unavailable, agent: agent === "pi" ? "cursor" : "pi" }] }, + })).toThrow("omitted usage accounting"); + expect(() => boundedEvalSessionUsage(parsed, { + ...turn, snapshot: { ...snapshot, usageUnavailable: [unavailable, { ...unavailable, turnId: "second-turn" }] }, + })).toThrow("agent turn limit exceeded"); + }); + + it("counts prior unavailable candidate turns alongside a later measured turn without claiming a complete total", () => { + const parsed = parseEvalSessionRequest(request({ + provider: "acpx", acpxAgent: "cursor", model: "gpt-5.6-sol", + }), { candidateProfile: "cursor" }); + const unavailable = { + turnId: "prior-turn", attemptId: "attempt-1", agent: "cursor" as const, + reason: "provider_did_not_report_usage" as const, + tokenUsage: null, costNanodollars: null, observedAt: "2026-09-28T19:18:10.000Z", + }; + const snapshot = { + config: { provider: "acpx", acpxAgent: "cursor" }, + usageUnavailable: [unavailable, { ...unavailable }], + usageLedger: [{ + receiptId: "measured-receipt", turnId: "measured-turn", providerCalls: 1, + providerRequests: 1, inputTokens: 100, outputTokens: 10, + cachedInputTokens: 0, reasoningTokens: 0, costNanodollars: 0, + }], + } as unknown as CapabilityLiveSessionSnapshot; + const turn = { turnId: "measured-turn", status: "completed" as const, assistantText: "Done", snapshot }; + expect(() => boundedEvalSessionUsage(parsed, turn)).toThrow("agent turn limit exceeded"); + expect(() => boundedEvalSessionUsage({ ...parsed, limits: { ...parsed.limits, maxAgentTurns: 2 } }, turn)).toThrow("budget cost coverage is unavailable"); + expect(boundedEvalSessionUsage(parsed, { + ...turn, + snapshot: { ...snapshot, usageUnavailable: [{ ...unavailable, turnId: "measured-turn" }] }, + })).toMatchObject({ agentTurns: 1, inputTokens: 100, outputTokens: 10 }); + }); + it("deduplicates receipts and applies the versioned model price", () => { const receipt = { receiptId: "receipt-1", @@ -301,4 +380,94 @@ describe("eval-session usage", () => { { usageLedger: [] } as unknown as CapabilityLiveSessionSnapshot, )).toThrow("omitted usage accounting"); }); + + it.each(["pi", "cursor", "copilot"])("retains unpriced %s usage without inventing an invoice or substituting a model", (agent) => { + const snapshot = { + config: { provider: "acpx", acpxAgent: agent }, + usageLedger: [{ + receiptId: "candidate-turn-receipt", providerCalls: 1, providerRequests: 2, + inputTokens: 1_000, outputTokens: 100, cachedInputTokens: 400, + reasoningTokens: 50, costNanodollars: 0, + }], + } as unknown as CapabilityLiveSessionSnapshot; + expect(evalSessionUsage("exact-provider-model[context=272k]", snapshot)).toMatchObject({ + agentTurns: 1, providerRequests: 2, inputTokens: 1_000, outputTokens: 100, + providerReportedCostNanodollars: null, + providerReportedCostProvenance: "unavailable", + estimatedCostNanodollars: null, pricingVersion: null, ratesUsdPerMillionTokens: null, + costCoverage: "unpriced", + }); + const parsed = parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: agent, model: "exact-provider-model[context=272k]" }), { candidateProfile: agent as "pi" | "cursor" | "copilot" }); + expect(() => boundedEvalSessionUsage(parsed, { + turnId: "candidate-turn", status: "completed", assistantText: "Completed provider response", snapshot, + })).toThrow("budget cost coverage is unavailable"); + const priced = evalSessionUsage("openrouter/deepseek/deepseek-v4-flash-0731", snapshot); + expect(priced.providerReportedCostNanodollars).toBeNull(); + expect(priced.costCoverage).toBe("estimated"); + expect(priced.estimatedCostNanodollars).toBeGreaterThan(0); + expect(priced.pricingVersion).toBe("provider-list-prices-2026-09-07-openrouter"); + expect(priced.ratesUsdPerMillionTokens).toEqual({ input: 0.14, cachedInput: 0.028, output: 0.28 }); + expect(() => evalSessionUsage("unpriced-qualified-model", { + ...snapshot, config: { provider: "codex" }, + } as unknown as CapabilityLiveSessionSnapshot)).toThrow("model pricing unavailable"); + }); +}); + +describe("eval-session budget settlement", () => { + it.each(["no_receipt", "grok_no_receipt", "unpriced", "mixed", "over_limit"] as const)("retains the completed provider outcome while failing %s accounting", async (kind) => { + const workspace = await mkdtemp(join(tmpdir(), "eval-budget-settlement-")); + try { + const binary = join(workspace, "runnerd"); + await writeFile(binary, "unused fake runner"); + const agent = kind === "grok_no_receipt" ? "grok" : "copilot"; + const noReceipt = kind === "no_receipt" || kind === "grok_no_receipt"; + const model = kind === "grok_no_receipt" ? "grok-4.7" : kind === "unpriced" || kind === "no_receipt" ? "exact-unpriced-candidate" : "gpt-5.6-sol"; + const unavailable = { turnId: kind === "mixed" ? "prior-turn" : "turn-1", attemptId: "attempt-1", agent: "copilot", reason: "provider_did_not_report_usage", tokenUsage: null, costNanodollars: null, observedAt: "2026-09-28T19:00:00.000Z" }; + const state = JSON.stringify(createCapabilityFixtureState({})); + const snapshot = { + sessionId: "session-1", revision: 1, providerThreadId: "provider-1", providerSessionId: "provider-1", + providerModel: { id: model, provider: agent === "grok" ? "xai" : "github" }, status: "idle", activeTurnId: null, + createdAt: "2026-09-28T19:00:00.000Z", updatedAt: "2026-09-28T19:00:01.000Z", + authority: { companyId: "company-1", actorId: "actor-1", taskId: "task-1", runId: "run-1", sessionId: "session-1", scenarioId: "budget-test" }, + config: { provider: "acpx", acpxAgent: agent, driver: "acpx_runtime", seedState: state, workingDirectory: workspace, scenario: { id: "budget-test" }, capabilities: [], explicitClaims: [], turnTimeoutMs: 1000 }, + mockState: state, process: null, networkEvidence: { realPaperclipRequests: 0, childPaperclipEnvironmentKeys: [] }, + transcript: [{ id: "assistant-1", role: "assistant", text: "Retained actual provider response", turnId: "turn-1", at: "2026-09-28T19:00:01.000Z" }], + evidence: [], authorizationRecords: [], attempts: [], terminalTurns: [{ turnId: "turn-1", status: "completed" }], + usageLedger: noReceipt ? [] : [{ receiptId: "receipt-1", turnId: "turn-1", providerCalls: 1, providerRequests: 1, inputTokens: 100, outputTokens: kind === "over_limit" ? 1_000_000 : 10, cachedInputTokens: 0, reasoningTokens: 0, costNanodollars: 0 }], + usageUnavailable: kind === "no_receipt" || kind === "mixed" ? [unavailable] : [], + } as unknown as CapabilityLiveSessionSnapshot; + const sendMessage = vi.fn(async () => ({ turnId: "turn-1", status: "completed", assistantText: "Retained actual provider response", snapshot })); + const completeAttempt = vi.fn(async () => undefined); + const shutdown = vi.fn(async () => { snapshot.status = "closed"; }); + const input = request({ provider: "acpx", acpxAgent: agent, model, + runnerd: { path: binary, sha256: createHash("sha256").update("unused fake runner").digest("hex") }, + session: { workingDirectory: workspace }, limits: { turnTimeoutMs: 1000, maxAgentTurns: 2, maxEstimatedCostNanodollars: 100_000_000 }, + }); + const requestPath = join(workspace, "request.json"); + const outputPath = join(workspace, "output.json"); + await writeFile(requestPath, JSON.stringify(input)); + const exitCode = await runEvalSessionCli(["--request", requestPath, "--output", outputPath, ...(agent === "grok" ? [] : ["--candidate-profile", "copilot"])], { + serviceFactory: () => ({ create: async () => ({ sendMessage, completeAttempt, shutdown, snapshot: () => snapshot }) }) as never, + }); + const artifact = JSON.parse(await readFile(outputPath, "utf8")); + expect(exitCode).toBe(2); + expect(artifact).not.toHaveProperty("infrastructureError"); + expect(artifact.accountingFailure).toMatchObject({ class: kind === "over_limit" ? "provider_budget_reached" : "provider_budget_coverage_unknown", retryable: false }); + expect(artifact.turn).toMatchObject({ status: "completed", assistantText: "Retained actual provider response" }); + expect(artifact.snapshot.status).toBe("closed"); + expect(artifact.acpxProfile.agent).toBe(agent); + expect(artifact.devtools).toBeDefined(); + expect(artifact.issueThread).toBeDefined(); + if (noReceipt) expect(artifact).not.toHaveProperty("usage"); + else expect(artifact.usage.providerReportedCostNanodollars).toBeNull(); + expect(sendMessage).toHaveBeenCalledTimes(1); + expect(shutdown).toHaveBeenCalledTimes(1); + expect(completeAttempt).toHaveBeenCalledWith("failed", artifact.accountingFailure.class); + } finally { + for (const entry of await readdir(workspace)) { + if (entry.startsWith(".paperclip-eval-runtime-context-")) await chmod(join(workspace, entry, "instructions"), 0o700).catch(() => undefined); + } + await rm(workspace, { recursive: true, force: true }); + } + }); }); diff --git a/packages/paperclip-runner/src/cli/eval-session-contract.ts b/packages/paperclip-runner/src/cli/eval-session-contract.ts index 5e64ef43fc..d1446cb4ac 100644 --- a/packages/paperclip-runner/src/cli/eval-session-contract.ts +++ b/packages/paperclip-runner/src/cli/eval-session-contract.ts @@ -58,6 +58,8 @@ export interface EvalSessionAgentCoreProfile { timeoutSeconds: number; } +export type EvalCandidateProfile = "pi" | "cursor" | "copilot"; + export interface EvalSessionRequest { schema: typeof EVAL_SESSION_REQUEST_SCHEMA; attemptId: string; @@ -66,7 +68,7 @@ export interface EvalSessionRequest { provider?: EvalSessionProvider; driver?: EvalSessionDriver; opencodeVersion?: string; - acpxAgent?: Exclude; + acpxAgent?: QualifiedAcpxAgent; managedProfile?: EvalSessionManagedProfile; agentCoreProfile?: EvalSessionAgentCoreProfile; runnerd: { path: string; sha256: string }; @@ -81,14 +83,19 @@ export interface EvalSessionRequest { includeCollaborationModeInstructions?: true; } -export interface EvalSessionUsage extends EstimatedModelCost { +export interface EvalSessionUsage { agentTurns: number; providerRequests: number; inputTokens: number; outputTokens: number; cachedInputTokens: number; reasoningTokens: number; - providerReportedCostNanodollars: number; + providerReportedCostNanodollars: number | null; + providerReportedCostProvenance: "legacy_runner_usage_ledger" | "unavailable"; + estimatedCostNanodollars: number | null; + pricingVersion: EstimatedModelCost["pricingVersion"] | null; + ratesUsdPerMillionTokens: EstimatedModelCost["ratesUsdPerMillionTokens"] | null; + costCoverage: "estimated" | "unpriced" | "legacy_ledger_and_estimate"; } function object(value: unknown, path: string): Record { @@ -211,7 +218,10 @@ function parseAgentCoreProfile(value: unknown): EvalSessionAgentCoreProfile { } /** Fail-closed validation for the executable boundary. */ -export function parseEvalSessionRequest(value: unknown): EvalSessionRequest { +export function parseEvalSessionRequest( + value: unknown, + options: { candidateProfile?: EvalCandidateProfile } = {}, +): EvalSessionRequest { const input = object(value, "request"); if (input.schema !== EVAL_SESSION_REQUEST_SCHEMA) { throw new Error("unsupported request schema"); @@ -237,17 +247,24 @@ export function parseEvalSessionRequest(value: unknown): EvalSessionRequest { // options as JSON null. Preserve compatibility with those immutable request // artifacts while continuing to reject non-null values for the wrong lane. const acpxAgent = input.acpxAgent === null ? undefined : input.acpxAgent; - if (acpxAgent === "pi") throw new Error("The Pi ACPX profile is not available"); if ( acpxAgent !== undefined && acpxAgent !== "codex" && - acpxAgent !== "claude" && acpxAgent !== "grok" + acpxAgent !== "claude" && acpxAgent !== "grok" && + acpxAgent !== "pi" && acpxAgent !== "cursor" && acpxAgent !== "copilot" ) { - throw new Error("eval-session acpxAgent must be codex, claude, or grok"); + throw new Error("eval-session acpxAgent must be a registered ACPX profile"); } if (provider !== "acpx" && acpxAgent !== undefined) { throw new Error("eval-session acpxAgent requires provider acpx"); } + const candidate = acpxAgent === "pi" || acpxAgent === "cursor" || acpxAgent === "copilot"; + if (options.candidateProfile !== undefined && (provider !== "acpx" || acpxAgent !== options.candidateProfile || !candidate)) { + throw new Error("--candidate-profile must match the request's registered candidate ACPX agent"); + } + if (candidate && options.candidateProfile !== acpxAgent) { + throw new Error("Candidate ACPX profiles require an explicit matching --candidate-profile diagnostic flag"); + } const managedProfileInput = input.managedProfile === null ? undefined : input.managedProfile; @@ -306,8 +323,8 @@ export function parseEvalSessionRequest(value: unknown): EvalSessionRequest { ) { throw new Error("request.session.requestedModel must match request.model"); } - if (session.acpxAgent === "pi") { - throw new Error("The Pi ACPX profile is not available"); + if (session.acpxAgent !== undefined && session.acpxAgent !== acpxAgent) { + throw new Error("request.session.acpxAgent must match request.acpxAgent"); } return { @@ -376,8 +393,28 @@ export function evalSessionUsage( if (unique.size === 0) { throw new Error("completed turn omitted usage accounting"); } + const candidate = snapshot.config?.provider === "acpx" + && ["pi", "cursor", "copilot"].includes(snapshot.config.acpxAgent ?? ""); + let estimate: EstimatedModelCost | null; + try { + estimate = estimateModelCostNanodollars(model, totals); + } catch (error) { + // An exact candidate model can be advertised before our pricing catalog + // contains it. Preserve unpriced usage for billing reconciliation instead + // of substituting a model or converting unknown spend into zero. + if (!candidate || !(error instanceof Error) || !error.message.startsWith("model pricing unavailable for ")) throw error; + estimate = null; + } return { ...totals, - ...estimateModelCostNanodollars(model, totals), + // Candidate ACP adapters do not supply authenticated USD receipts. The + // shared legacy ledger fills absent cost with zero, so its numeric value + // cannot establish an invoice amount for these profiles. + providerReportedCostNanodollars: candidate ? null : totals.providerReportedCostNanodollars, + providerReportedCostProvenance: candidate ? "unavailable" : "legacy_runner_usage_ledger", + estimatedCostNanodollars: estimate?.estimatedCostNanodollars ?? null, + pricingVersion: estimate?.pricingVersion ?? null, + ratesUsdPerMillionTokens: estimate?.ratesUsdPerMillionTokens ?? null, + costCoverage: candidate ? (estimate === null ? "unpriced" : "estimated") : "legacy_ledger_and_estimate", }; } diff --git a/packages/paperclip-runner/src/cli/eval-session-entrypoint.test.ts b/packages/paperclip-runner/src/cli/eval-session-entrypoint.test.ts new file mode 100644 index 0000000000..cbc7f5dc3d --- /dev/null +++ b/packages/paperclip-runner/src/cli/eval-session-entrypoint.test.ts @@ -0,0 +1,31 @@ +import { execFile } from "node:child_process"; +import { mkdtemp, realpath, rm, symlink } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; + +import { expect, it } from "vitest"; + +const execute = promisify(execFile); +const tsx = fileURLToPath(new URL("../../node_modules/.bin/tsx", import.meta.url)); + +it("runs the eval CLI parser through both canonical and symlinked paths", async () => { + const cli = await realpath(fileURLToPath(new URL("./eval-session.ts", import.meta.url))); + const root = await mkdtemp(join(tmpdir(), "paperclip-eval-cli-entrypoint-")); + try { + const alias = join(root, "cli-alias"); + await symlink(dirname(cli), alias, "dir"); + for (const path of [cli, join(alias, "eval-session.ts")]) { + // A skipped main guard silently exits zero. Rejecting the invalid option + // proves actual CLI execution without loading credentials or a provider. + await expect(execute(tsx, [path, "--unknown", "value"], { timeout: 15_000 })) + .rejects.toMatchObject({ code: 1, stderr: "unknown argument: --unknown\n" }); + } + const imported = await execute(tsx, ["--eval", `import(${JSON.stringify(cli)}).then(() => console.log("imported"))`], { timeout: 15_000 }); + expect(imported.stdout.trim()).toBe("imported"); + expect(imported.stderr).toBe(""); + } finally { + await rm(root, { recursive: true, force: true }); + } +}, 45_000); diff --git a/packages/paperclip-runner/src/cli/eval-session.ts b/packages/paperclip-runner/src/cli/eval-session.ts index 33c56f7962..1915315944 100644 --- a/packages/paperclip-runner/src/cli/eval-session.ts +++ b/packages/paperclip-runner/src/cli/eval-session.ts @@ -1,5 +1,6 @@ #!/usr/bin/env node import { createHash } from "node:crypto"; +import { realpathSync } from "node:fs"; import { chmod, mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -30,6 +31,7 @@ import { expectedEvalSessionDriver, parseEvalSessionRequest, type EvalSessionRequest, + type EvalCandidateProfile, type EvalSessionUsage, } from "./eval-session-contract.js"; import { evalProviderTransportOptions } from "./eval-provider-runtime.js"; @@ -37,6 +39,7 @@ import { evalProviderTransportOptions } from "./eval-provider-runtime.js"; interface EvalSessionCliOptions { requestPath: string; outputPath: string; + candidateProfile?: EvalCandidateProfile; } function argument(args: string[], name: string): string { @@ -47,14 +50,23 @@ function argument(args: string[], name: string): string { } export function parseEvalSessionCliArgs(args: string[]): EvalSessionCliOptions { - const allowed = new Set(["--request", "--output"]); + const allowed = new Set(["--request", "--output", "--candidate-profile"]); + const seen = new Set(); for (let index = 0; index < args.length; index += 2) { if (!allowed.has(args[index] ?? "")) { throw new Error(`unknown argument: ${args[index] ?? ""}`); } - if (args[index + 1] === undefined) throw new Error(`missing ${args[index]}`); + if (seen.has(args[index]!)) throw new Error(`duplicate argument: ${args[index]}`); + seen.add(args[index]!); + if (args[index + 1] === undefined || args[index + 1]!.startsWith("--")) throw new Error(`missing ${args[index]}`); + } + const candidateIndex = args.indexOf("--candidate-profile"); + const candidateProfile = candidateIndex < 0 ? undefined : args[candidateIndex + 1]; + if (candidateProfile !== undefined && candidateProfile !== "pi" && candidateProfile !== "cursor" && candidateProfile !== "copilot") { + throw new Error("--candidate-profile must be pi, cursor, or copilot"); } return { + ...(candidateProfile === undefined ? {} : { candidateProfile }), requestPath: argument(args, "--request"), outputPath: argument(args, "--output"), }; @@ -180,12 +192,22 @@ export function evalSessionProviderVersion( return null; } +class EvalSessionBudgetError extends Error { + constructor(message: string, readonly coverageUnknown = false) { + super(message); + this.name = "EvalSessionBudgetError"; + } +} + function failureClass(error: unknown): { class: string; category: string; retryable: boolean; diagnostics: Record; } { + if (error instanceof EvalSessionBudgetError && error.coverageUnknown) { + return { class: "provider_budget_coverage_unknown", category: "provider_budget", retryable: false, diagnostics: {} }; + } const message = error instanceof Error ? error.message : String(error); if (/timed? ?out|timeout/i.test(message)) { return { @@ -235,25 +257,48 @@ export function boundedEvalSessionUsage( request: EvalSessionRequest, turn: CapabilityLiveTurnResult, ): EvalSessionUsage | null { - if (turn.status !== "completed" || - (request.provider === "acpx" && request.acpxAgent === "grok" && !turn.snapshot.usageLedger?.length)) { + if (turn.status !== "completed") { return usageIfAvailable(request, turn.snapshot); } - const usage = evalSessionUsage(request.model, turn.snapshot); - if (usage.agentTurns > request.limits.maxAgentTurns) { - throw new Error("agent turn limit exceeded"); + if (request.provider === "acpx" && request.acpxAgent === "grok" && !turn.snapshot.usageLedger?.length) { + throw new EvalSessionBudgetError("budget cost coverage is unavailable for the completed Grok turn", true); + } + const candidate = request.provider === "acpx" + && request.acpxAgent !== undefined + && ["pi", "cursor", "copilot"].includes(request.acpxAgent) + && turn.snapshot.config.provider === "acpx" + && turn.snapshot.config.acpxAgent === request.acpxAgent; + const measuredTurns = new Set(turn.snapshot.usageLedger?.map((entry) => entry.turnId)); + const unavailableTurnIds = new Set(candidate + ? turn.snapshot.usageUnavailable?.filter((entry) => entry.agent === request.acpxAgent + && entry.reason === "provider_did_not_report_usage" + && entry.tokenUsage === null && entry.costNanodollars === null + && !measuredTurns.has(entry.turnId)).map((entry) => entry.turnId) + : []); + const unavailable = unavailableTurnIds.size > 0; + const usage = unavailable && (unavailableTurnIds.has(turn.turnId) || turn.snapshot.usageLedger?.length) + ? usageIfAvailable(request, turn.snapshot) + : evalSessionUsage(request.model, turn.snapshot); + const unavailableTurns = unavailableTurnIds.size; + if ((usage?.agentTurns ?? 0) + unavailableTurns > request.limits.maxAgentTurns) { + throw new EvalSessionBudgetError("agent turn limit exceeded"); } if ( + usage !== null && usage.estimatedCostNanodollars !== null && usage.estimatedCostNanodollars > request.limits.maxEstimatedCostNanodollars ) { - throw new Error("estimated cost limit exceeded"); + throw new EvalSessionBudgetError("estimated cost limit exceeded"); } if ( + usage !== null && usage.providerReportedCostNanodollars !== null && usage.providerReportedCostNanodollars > request.limits.maxEstimatedCostNanodollars ) { - throw new Error("provider-reported cost limit exceeded"); + throw new EvalSessionBudgetError("provider-reported cost limit exceeded"); + } + if (unavailable || usage === null || (usage.estimatedCostNanodollars === null && usage.providerReportedCostNanodollars === null)) { + throw new EvalSessionBudgetError("budget cost coverage is unavailable for one or more completed turns", true); } return usage; } @@ -277,6 +322,7 @@ export async function runEvalSessionCli( const cli = parseEvalSessionCliArgs(args); const request = parseEvalSessionRequest( JSON.parse(await readFile(cli.requestPath, "utf8")), + { candidateProfile: cli.candidateProfile }, ); const runnerdPath = resolve(request.runnerd.path); const actualDigest = await sha256(runnerdPath); @@ -300,6 +346,7 @@ export async function runEvalSessionCli( transportOptions: { ...evalProviderTransportOptions(requestedProvider, request.limits.turnTimeoutMs), runnerBinary: runnerdPath, + ...(cli.candidateProfile === undefined ? {} : { acpxCandidateProfile: cli.candidateProfile }), runtimeContext, baseInstructions: evalRuntimeSystemInstructions(runtimeContext), // The transport performs the provider-specific allowlisting. Supplying @@ -338,10 +385,21 @@ export async function runEvalSessionCli( } as unknown as CreateCapabilityLiveSessionInput; session = await service.create(createInput); turn = await session.sendMessage(request.prompt); - const usage = boundedEvalSessionUsage(request, turn); + let usage: EvalSessionUsage | null; + let accountingError: EvalSessionBudgetError | null = null; + try { + usage = boundedEvalSessionUsage(request, turn); + } catch (error) { + if (!(error instanceof EvalSessionBudgetError)) throw error; + // A completed provider outcome remains inspectable even when its budget + // cannot be verified. Never turn unknown cost into a successful CLI run. + accountingError = error; + usage = usageIfAvailable(request, turn.snapshot); + } await session.completeAttempt( - turn.status === "completed" ? "succeeded" : "failed", - turn.status === "completed" ? null : `provider_turn_${turn.status}`, + accountingError === null && turn.status === "completed" ? "succeeded" : "failed", + accountingError !== null ? failureClass(accountingError).class + : turn.status === "completed" ? null : `provider_turn_${turn.status}`, ); await closeSession(session, "eval session complete"); snapshot = session.snapshot(); @@ -352,6 +410,7 @@ export async function runEvalSessionCli( build: PAPERCLIP_RUNNER_BUILD_METADATA, runnerd: { path: "[withheld]", sha256: `sha256:${actualDigest}` }, requestedModel: request.model, + ...(cli.candidateProfile === undefined ? {} : { diagnosticCandidateProfile: cli.candidateProfile }), provider: requestedProvider, driver: requestedDriver, providerVersion: requestedProviderVersion, @@ -379,6 +438,10 @@ export async function runEvalSessionCli( : {}), turn, snapshot, + ...(accountingError === null ? {} : { + accountingError: accountingError.message, + accountingFailure: failureClass(accountingError), + }), devtools: projectCapabilityDevtools(snapshot), issueThread: projectCapabilityIssueThread({ snapshot, @@ -392,7 +455,7 @@ export async function runEvalSessionCli( durationMs: Date.now() - startedAtMs, }, }, null, 2)}\n`); - return 0; + return accountingError === null ? 0 : 2; } catch (error) { if (session !== null) { snapshot = session.snapshot(); @@ -425,6 +488,7 @@ export async function runEvalSessionCli( build: PAPERCLIP_RUNNER_BUILD_METADATA, runnerd: { path: "[withheld]", sha256: `sha256:${actualDigest}` }, requestedModel: request.model, + ...(cli.candidateProfile === undefined ? {} : { diagnosticCandidateProfile: cli.candidateProfile }), provider: requestedProvider, driver: requestedDriver, providerVersion: requestedProviderVersion, @@ -453,10 +517,18 @@ export async function runEvalSessionCli( } } -if ( - process.argv[1] !== undefined && - resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url)) -) { +function isEvalSessionEntrypoint(): boolean { + if (process.argv[1] === undefined) return false; + try { + // Node resolves module URLs through symlinks, including /tmp on macOS and + // package-manager bin links. Compare the same physical paths on both sides. + return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); + } catch { + return false; + } +} + +if (isEvalSessionEntrypoint()) { void runEvalSessionCli(process.argv.slice(2)) .then((code) => { process.exitCode = code; diff --git a/packages/paperclip-runner/src/contracts/harness-driver.ts b/packages/paperclip-runner/src/contracts/harness-driver.ts index 3400b419c9..b6bbe6103b 100644 --- a/packages/paperclip-runner/src/contracts/harness-driver.ts +++ b/packages/paperclip-runner/src/contracts/harness-driver.ts @@ -2,7 +2,7 @@ import type { PrpEvent, PrpStructuredRunResult, } from "../protocol/replay-contract.js"; -import type { NativeSessionCapabilities, NativeUserMessage } from "./types.js"; +import type { NativeSessionCapabilities, NativeTurnControlCapabilities, NativeUserMessage } from "./types.js"; import { PAPERCLIP_RUNTIME_REQUEST_SCHEMA_V2, parsePaperclipQuestionResponse, @@ -495,6 +495,7 @@ export interface HarnessSessionRecoveryResult { } export interface HarnessSession { + turnControlCapabilities?(): NativeTurnControlCapabilities | null; ids(): { driverSessionId: string; providerSessionId?: string | null; @@ -512,6 +513,8 @@ export interface HarnessSession { effectiveCollaborationMode?: "default" | "plan"; }>; steer?(input: { + /** Queued follow-ups remain distinct from active-turn steering. */ + mode?: "steer" | "follow_up"; turnId: string; message: NativeUserMessage; correlationId?: string; diff --git a/packages/paperclip-runner/src/contracts/native-execution.test.ts b/packages/paperclip-runner/src/contracts/native-execution.test.ts index 8d1ddee0a1..6599724252 100644 --- a/packages/paperclip-runner/src/contracts/native-execution.test.ts +++ b/packages/paperclip-runner/src/contracts/native-execution.test.ts @@ -280,7 +280,7 @@ describe("NativeExecutionInputV1", () => { })).toThrow("eventExpiryDays"); }); - it("accepts only a closed ACPX profile matching the driver and agent", () => { + it.each([1, 2, 3, 4, 5] as const)("accepts only a closed ACPX profile matching the driver and agent at profile version %s", (agentProfileVersion) => { const provider = { kind: "acpx", agent: "pi", @@ -291,7 +291,7 @@ describe("NativeExecutionInputV1", () => { protocolVersion: 1, acpxVersion: "0.13.1", agent: "pi", - agentProfileVersion: 1, + agentProfileVersion, agentServerPackage: "pi-acp", agentServerVersion: "0.0.33", agentRuntimePackage: "@earendil-works/pi-coding-agent", @@ -312,6 +312,13 @@ describe("NativeExecutionInputV1", () => { profile: provider.profile, }); expect(parseNativeExecutionInput(parsed)).toEqual(parsed); + for (const unsupportedVersion of [0, 6, 1.5, "5", null]) { + expect(() => parseNativeExecutionInput({ + ...input, + session: { ...input.session, driverKind: "acpx_runtime" }, + provider: { ...provider, profile: { ...provider.profile, agentProfileVersion: unsupportedVersion } }, + })).toThrow("qualified ACPX v1 profile"); + } expect(buildNativeModelEnvelope(parsed).workspace).toEqual({ cwd: "/safe/workspace" }); expect(() => parseNativeExecutionInput({ ...input, diff --git a/packages/paperclip-runner/src/contracts/native-execution.ts b/packages/paperclip-runner/src/contracts/native-execution.ts index c4f1715f31..2d1ec54a8b 100644 --- a/packages/paperclip-runner/src/contracts/native-execution.ts +++ b/packages/paperclip-runner/src/contracts/native-execution.ts @@ -80,7 +80,7 @@ export interface NativeAwsAgentCoreProfileSnapshot { eventExpiryDays: 90; } -export type NativeAcpxAgent = "pi" | "claude" | "codex" | "grok"; +export type NativeAcpxAgent = "pi" | "claude" | "codex" | "grok" | "cursor" | "copilot"; export type NativeCodexApprovalPolicy = "never" | "on-request" | "untrusted"; export type NativeOpenCodePermissionMode = "allow" | "ask" | "deny"; export type NativeAcpxPermissionMode = "approve-all" | "approve-paperclip" | "approve-reads" | "deny-all"; @@ -90,7 +90,7 @@ export interface NativeAcpxProfileSnapshot { protocolVersion: 1; acpxVersion: "0.13.1"; agent: NativeAcpxAgent; - agentProfileVersion: 1; + agentProfileVersion: 1 | 2 | 3 | 4 | 5; agentServerPackage: string; agentServerVersion: string; agentRuntimePackage: string | null; @@ -568,8 +568,8 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput if (providerModel === null) { throw new NativeExecutionInputError("input.provider.model is required for acpx"); } - if (provider.agent !== "pi" && provider.agent !== "claude" && provider.agent !== "codex" && provider.agent !== "grok") { - throw new NativeExecutionInputError("input.provider.agent must be pi, claude, codex, or grok"); + if (provider.agent !== "pi" && provider.agent !== "claude" && provider.agent !== "codex" && provider.agent !== "grok" && provider.agent !== "cursor" && provider.agent !== "copilot") { + throw new NativeExecutionInputError("input.provider.agent must be pi, claude, codex, grok, cursor, or copilot"); } if (isV4) { if (provider.permissionMode !== "approve-all" && provider.permissionMode !== "approve-paperclip" && provider.permissionMode !== "approve-reads" && provider.permissionMode !== "deny-all") { @@ -596,7 +596,7 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput || profile.protocolVersion !== 1 || profile.acpxVersion !== "0.13.1" || profile.agent !== provider.agent - || profile.agentProfileVersion !== 1 + || (profile.agentProfileVersion !== 1 && profile.agentProfileVersion !== 2 && profile.agentProfileVersion !== 3 && profile.agentProfileVersion !== 4 && profile.agentProfileVersion !== 5) ) { throw new NativeExecutionInputError("input.provider.profile does not match the qualified ACPX v1 profile"); } @@ -617,7 +617,7 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput protocolVersion: 1, acpxVersion: "0.13.1", agent: provider.agent, - agentProfileVersion: 1, + agentProfileVersion: profile.agentProfileVersion, agentServerPackage: text(profile.agentServerPackage, "input.provider.profile.agentServerPackage"), agentServerVersion: text(profile.agentServerVersion, "input.provider.profile.agentServerVersion"), agentRuntimePackage: runtimePackage, diff --git a/packages/paperclip-runner/src/contracts/native-session-backend.ts b/packages/paperclip-runner/src/contracts/native-session-backend.ts index 0bad29e171..3fb4f928e5 100644 --- a/packages/paperclip-runner/src/contracts/native-session-backend.ts +++ b/packages/paperclip-runner/src/contracts/native-session-backend.ts @@ -154,6 +154,7 @@ export interface NativeSession { effectiveCollaborationMode?: "default" | "plan"; }>; steer?(input: { + mode?: "steer" | "follow_up"; turnId: string; message: NativeUserMessage; correlationId?: string; diff --git a/packages/paperclip-runner/src/contracts/question-set.ts b/packages/paperclip-runner/src/contracts/question-set.ts index 34156a87cf..5021b3ef97 100644 --- a/packages/paperclip-runner/src/contracts/question-set.ts +++ b/packages/paperclip-runner/src/contracts/question-set.ts @@ -262,8 +262,8 @@ export function parsePaperclipQuestionSet(value: unknown): PaperclipQuestionSet ...(optionalText(candidate.title, "/input/title", 1_000) !== undefined ? { title: optionalText(candidate.title, "/input/title", 1_000) } : {}), - ...(optionalText(candidate.description, "/input/description") !== undefined - ? { description: optionalText(candidate.description, "/input/description") } + ...(optionalText(candidate.description, "/input/description", 100_000) !== undefined + ? { description: optionalText(candidate.description, "/input/description", 100_000) } : {}), ...(optionalText(candidate.submitLabel, "/input/submitLabel", 200) !== undefined ? { submitLabel: optionalText(candidate.submitLabel, "/input/submitLabel", 200) } diff --git a/packages/paperclip-runner/src/contracts/types.ts b/packages/paperclip-runner/src/contracts/types.ts index 811e922aa1..0363782822 100644 --- a/packages/paperclip-runner/src/contracts/types.ts +++ b/packages/paperclip-runner/src/contracts/types.ts @@ -28,6 +28,7 @@ export interface NativeSessionCapabilities { typedEvents: boolean; typedEventFamilies?: TypedEventFamilyCapability[]; steering: boolean; + queuedFollowUp?: boolean; interruption: boolean; structuredResult: boolean; read?: boolean; @@ -47,3 +48,9 @@ export interface NativeUserMessage { text: string; } import type { TypedEventFamilyCapability } from "../provider-events.js"; + +/** Live provider handshake; queued follow-up is distinct from active steering. */ +export interface NativeTurnControlCapabilities { + steering: boolean; + queuedFollowUp: boolean; +} diff --git a/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.test.ts b/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.test.ts index 47f958febd..283429f55d 100644 --- a/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.test.ts +++ b/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.test.ts @@ -20,7 +20,8 @@ import { resolve } from "node:path"; import { describe, expect, it, vi } from "vitest"; import { NativeSessionProtocolIntegrityError } from "../contracts/native-session-backend.js"; -import { createCapabilityRunnerdCodexTransport } from "../live/runnerd-codex-transport.js"; +import { createCapabilityRunnerdCodexTransport, createCapabilityRunnerdProviderEnvironment } from "../live/runnerd-codex-transport.js"; +import { ACPX_CREDENTIAL_BINDING_ENV, createAcpxSidecarHostEnvironment } from "../drivers/acpx/environment.js"; import { validatePrpEvent } from "../protocol/replay-contract.js"; import { digestPaperclipSemanticContent } from "../semantic-tools/receipts.js"; import { @@ -533,6 +534,53 @@ it("preserves the controller-selected ACPX provider package root", () => { expect(launches[0]!.environment.NODE_PATH).toBeUndefined(); }); +it.each([ + ["pi", "OPENROUTER_API_KEY"], + ["cursor", "CURSOR_API_KEY"], + ["cursor", "CURSOR_AUTH_TOKEN"], + ["copilot", "COPILOT_GITHUB_TOKEN"], +] as const)("preserves explicit %s %s binding through the actual runner launch boundary", (agent, key) => { + const launches: RunnerProcessLaunchSpec[] = []; + vi.stubEnv(key, "ambient-must-not-cross"); + vi.stubEnv(ACPX_CREDENTIAL_BINDING_ENV, "ambient-forged-marker"); + try { + for (const explicit of [true, false]) { + const environment = createCapabilityRunnerdProviderEnvironment({ + provider: "acpx", identity, codexHome: "/fixture/home", + runtimeContextPath: "/fixture/context.json", hasRuntimeContext: false, + options: { acpxAgent: agent, + environment: explicit ? { [key]: "explicit-fixture-credential", [ACPX_CREDENTIAL_BINDING_ENV]: "caller-forged-marker", DATABASE_URL: "must-not-cross" } : undefined }, + }); + const handle = spawnRunner({ + connection: { mode: "connect", connectUrl: "ws://127.0.0.1:43127" }, + stateDirectory: "/tmp/paperclip-runner-test", identity, + ticket: "bootstrap-ticket", maxOutboxBytes: 256 * 1024, p0ReserveBytes: 64 * 1024, + runnerVersion: expectedRunnerVersion, runnerDigest: expectedRunnerDigest, environment, + processLauncher: spec => { + launches.push(spec); + return { child: { pid: 42, exitCode: null, signalCode: null, kill: () => true }, + completion: Promise.resolve({ code: 0, signal: null, stdout: "", stderr: "" }) }; + }, + }); + handle.restart("replacement-ticket"); + for (const launch of launches.splice(0)) { + expect(launch.environment[key]).toBe(explicit ? "explicit-fixture-credential" : undefined); + const receipt = launch.environment[ACPX_CREDENTIAL_BINDING_ENV]; + expect(receipt).toBeDefined(); + expect(JSON.parse(receipt!)).toEqual({ schema: "paperclip.acpx_credential_binding.v1", agent, + sessionId: identity.normalizedSessionId, names: explicit ? [key] : [] }); + expect(receipt).not.toContain("fixture-credential"); + expect(launch.environment.DATABASE_URL).toBeUndefined(); + const provider = createAcpxSidecarHostEnvironment(launch.environment, agent, identity.normalizedSessionId); + expect(provider[key]).toBe(explicit ? "explicit-fixture-credential" : undefined); + expect(provider[ACPX_CREDENTIAL_BINDING_ENV]).toBeUndefined(); + expect(() => createAcpxSidecarHostEnvironment(launch.environment, agent, "stale-session")) + .toThrow("explicit matching session binding"); + } + } + } finally { vi.unstubAllEnvs(); } +}); + it("preserves file-backed AWS workload identity at the runner spawn boundary", () => { const launches: RunnerProcessLaunchSpec[] = []; spawnRunner({ diff --git a/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts b/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts index 5e4d9631f9..ad3a029c7a 100644 --- a/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts +++ b/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts @@ -29,6 +29,7 @@ import type { Duplex } from "node:stream"; import { fileURLToPath } from "node:url"; import { NativeSessionProtocolIntegrityError } from "../contracts/native-session-backend.js"; +import { ACPX_CREDENTIAL_BINDING_ENV, ACPX_CREDENTIAL_NAMES } from "../drivers/acpx/environment.js"; import { githubCredentialEnvironment } from "../github-credential-environment.js"; import { validatePrpEvent, @@ -3277,7 +3278,10 @@ const runnerPlatformEnvironmentKeys = [ ] as const; const runnerExplicitProviderEnvironmentKeys = [ - "OPENROUTER_API_KEY", + ...ACPX_CREDENTIAL_NAMES.pi, + ...ACPX_CREDENTIAL_NAMES.cursor, + ...ACPX_CREDENTIAL_NAMES.copilot, + ACPX_CREDENTIAL_BINDING_ENV, "ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN", "OPENAI_API_KEY", diff --git a/packages/paperclip-runner/src/drivers/acpx/acp-permission-adapter.test.ts b/packages/paperclip-runner/src/drivers/acpx/acp-permission-adapter.test.ts new file mode 100644 index 0000000000..07fdbc4c3e --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/acp-permission-adapter.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import type { AcpPermissionRequest } from "acpx/runtime"; +import { normalizeAcpxPermission } from "./acp-permission-adapter.js"; + +function request(kinds = ["allow_once", "allow_always", "reject_once"]): AcpPermissionRequest { + return { sessionId: "session", inferredKind: "edit", raw: { + sessionId: "session", toolCall: { toolCallId: "call", title: "Edit source" }, + options: kinds.map((kind, i) => ({ kind, optionId: `option-${i}`, name: kind })), + } } as AcpPermissionRequest; +} +describe("ACP permission normalization", () => { + it("keeps requests answerable when a provider omits its operation title", () => { + for (const title of [undefined, "", " "]) { + const value = request(); + value.raw.toolCall.title = title; + expect(normalizeAcpxPermission(value).title).toBe("Approve provider operation"); + } + }); + it("offers only native choices and preserves cancellation", () => { + const value = normalizeAcpxPermission(request(["allow_once"])); + expect(value.choices.map(c => c.key)).toEqual(["accept", "cancel"]); + expect(value.resolve({ action: "accept" })).toEqual({ outcome: "allow_once" }); + expect(value.resolve({ action: "cancel" })).toEqual({ outcome: "cancel" }); + expect(() => value.resolve({ action: "accept_for_session" })).toThrow("offered choice"); + expect(() => value.resolve({ action: "submit", content: { accept: true } })).toThrow("offered choice"); + }); + it("maps once, session and denial distinctly", () => { + const value = normalizeAcpxPermission(request(), { allowAlwaysScope: "session" }); + expect(value.resolve({ action: "accept_for_session" })).toEqual({ outcome: "allow_always" }); + expect(value.resolve({ action: "decline" })).toEqual({ outcome: "reject_once" }); + }); + it("does not label an unverified persistent allowance as session-only", () => { + const value = normalizeAcpxPermission(request()); + expect(value.choices.map(choice => choice.key)).toEqual(["accept", "decline", "cancel"]); + expect(() => value.resolve({ action: "accept_for_session" })).toThrow("offered choice"); + }); + it("does not turn a one-time denial into permanent rejection", () => { + expect(normalizeAcpxPermission(request(["reject_always"])).choices).toEqual([{ key: "cancel", label: "Cancel" }]); + }); + it("rejects ambiguous provider choices before any resolution", () => { + expect(() => normalizeAcpxPermission(request(["allow_once", "allow_once"]))).toThrow("ambiguous"); + const duplicate = request(); duplicate.raw.options[1]!.optionId = duplicate.raw.options[0]!.optionId; + expect(() => normalizeAcpxPermission(duplicate)).toThrow("ambiguous"); + }); +}); diff --git a/packages/paperclip-runner/src/drivers/acpx/acp-permission-adapter.ts b/packages/paperclip-runner/src/drivers/acpx/acp-permission-adapter.ts new file mode 100644 index 0000000000..6eac1f3c6e --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/acp-permission-adapter.ts @@ -0,0 +1,63 @@ +import type { AcpPermissionDecision, AcpPermissionRequest } from "acpx/runtime"; +import type { HarnessRuntimeRequestResolution } from "../../contracts/harness-driver.js"; + +export type AcpxPermissionAction = "accept" | "accept_for_session" | "decline" | "cancel"; +export interface NormalizedAcpxPermission { + title: string; + kind: string; + toolCallId: string; + choices: Array<{ key: AcpxPermissionAction; label: string }>; + resolve(resolution: HarnessRuntimeRequestResolution): AcpPermissionDecision; +} + +/** Provider options describe choices, never authority to execute an operation. */ +export function normalizeAcpxPermission(request: AcpPermissionRequest, options: { allowAlwaysScope?: "session" } = {}): NormalizedAcpxPermission { + const raw = request.raw; + if (!raw || !Array.isArray(raw.options) || raw.options.length > 32) { + throw new Error("ACP permission request has invalid options"); + } + const byKind = new Map(); + const ids = new Set(); + for (const option of raw.options) { + if (!option || !["allow_once", "allow_always", "reject_once", "reject_always"].includes(option.kind) + || typeof option.optionId !== "string" || !option.optionId || option.optionId.length > 160 + || typeof option.name !== "string" || !option.name || option.name.length > 500 + || ids.has(option.optionId) || byKind.has(option.kind)) { + // ACPX resolves by kind. Ambiguous kinds must not select a different option. + throw new Error("ACP permission request has ambiguous or unsupported options"); + } + ids.add(option.optionId); + byKind.set(option.kind, { optionId: option.optionId, name: option.name }); + } + const bindings = new Map(); + const choices: NormalizedAcpxPermission["choices"] = []; + for (const [key, kind, label] of [ + ["accept", "allow_once", "Allow once"], + ["accept_for_session", "allow_always", "Allow for session"], + ["decline", "reject_once", "Deny"], + ] as const) { + if (kind === "allow_always" && options.allowAlwaysScope !== "session") continue; + if (byKind.has(kind)) { bindings.set(key, kind); choices.push({ key, label }); } + } + // A permanent rejection is not silently substituted for a one-time denial. + bindings.set("cancel", "cancel"); + choices.push({ key: "cancel", label: "Cancel" }); + const call = raw.toolCall; + if (!call || typeof call.toolCallId !== "string" || !call.toolCallId || call.toolCallId.length > 240) { + throw new Error("ACP permission request omitted its tool identity"); + } + return { + title: typeof call.title === "string" && call.title.trim() + ? call.title.slice(0, 4_000) : "Approve provider operation", + kind: request.inferredKind ?? "other", + toolCallId: call.toolCallId, + choices, + resolve(resolution) { + const outcome = bindings.get(resolution.action as AcpxPermissionAction); + if (!outcome || "answers" in resolution || "content" in resolution || "response" in resolution) { + throw new Error("ACP permission resolution is not an offered choice"); + } + return { outcome } as AcpPermissionDecision; + }, + }; +} diff --git a/packages/paperclip-runner/src/drivers/acpx/capability-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/capability-profiles.ts new file mode 100644 index 0000000000..4a70c22e25 --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/capability-profiles.ts @@ -0,0 +1,67 @@ +import type { QualifiedAcpxAgent } from "./qualified-profiles.js"; + +export interface AcpxCapabilityProfile { + readonly displayName: string; + readonly qualification: "qualified" | "pending"; + readonly models: "explicit-provider-verified" | "exact-qualified"; + readonly permissions: "runner-policy" | "interactive"; + readonly questions: "form" | "cursor-extension" | "not-exposed"; + readonly plans: "native" | "cursor-decision" | "semantic-only"; + readonly tools: "authenticated-mcp" | "owned-extension"; + readonly recovery: "session-load" | "unverified"; + readonly usage: "reported" | "unverified"; + readonly steering: "unsupported" | "owned-extension-pending"; + readonly followUp: "controller-queue" | "owned-extension-pending"; + readonly artifacts: "policy_disabled" | "references-pending"; + readonly extensionRequests: readonly string[]; + readonly extensionNotifications: readonly string[]; +} + +/** These are runner integration claims, not a proxy for everything a harness can do. */ +export const ACPX_CAPABILITY_PROFILES: Readonly> = { + claude: { + displayName: "Claude", qualification: "qualified", models: "explicit-provider-verified", + permissions: "interactive", questions: "form", plans: "native", tools: "authenticated-mcp", + recovery: "session-load", usage: "reported", steering: "unsupported", followUp: "controller-queue", + artifacts: "policy_disabled", extensionRequests: [], extensionNotifications: [], + }, + codex: { + displayName: "Codex", qualification: "qualified", models: "exact-qualified", + permissions: "runner-policy", questions: "form", plans: "native", tools: "authenticated-mcp", + recovery: "session-load", usage: "reported", steering: "unsupported", followUp: "controller-queue", + artifacts: "policy_disabled", extensionRequests: [], extensionNotifications: [], + }, + grok: { + displayName: "Grok Build", qualification: "qualified", models: "explicit-provider-verified", + permissions: "interactive", questions: "form", plans: "native", tools: "authenticated-mcp", + recovery: "session-load", usage: "unverified", steering: "unsupported", followUp: "controller-queue", + artifacts: "policy_disabled", extensionRequests: [], extensionNotifications: [], + }, + cursor: { + displayName: "Cursor", qualification: "pending", models: "explicit-provider-verified", + permissions: "interactive", questions: "cursor-extension", plans: "cursor-decision", tools: "authenticated-mcp", + recovery: "session-load", usage: "unverified", steering: "unsupported", followUp: "controller-queue", + artifacts: "references-pending", + extensionRequests: ["cursor/ask_question", "cursor/create_plan", "cursor/update_todos", "cursor/task", "cursor/generate_image"], + extensionNotifications: ["cursor/update_todos", "cursor/task", "cursor/generate_image"], + }, + copilot: { + displayName: "GitHub Copilot", qualification: "pending", models: "explicit-provider-verified", + permissions: "interactive", questions: "not-exposed", plans: "native", tools: "authenticated-mcp", + recovery: "session-load", usage: "reported", steering: "unsupported", followUp: "controller-queue", + artifacts: "references-pending", extensionRequests: [], + extensionNotifications: ["github.com/copilot/sessionEvent"], + }, + pi: { + displayName: "Pi", qualification: "pending", models: "exact-qualified", + permissions: "interactive", questions: "form", plans: "semantic-only", tools: "owned-extension", + recovery: "session-load", usage: "reported", steering: "owned-extension-pending", followUp: "owned-extension-pending", + artifacts: "references-pending", extensionRequests: [], extensionNotifications: [], + }, +}; +for (const profile of Object.values(ACPX_CAPABILITY_PROFILES)) { + Object.freeze(profile.extensionRequests); + Object.freeze(profile.extensionNotifications); + Object.freeze(profile); +} +Object.freeze(ACPX_CAPABILITY_PROFILES); diff --git a/packages/paperclip-runner/src/drivers/acpx/codex-acpx-driver.test.ts b/packages/paperclip-runner/src/drivers/acpx/codex-acpx-driver.test.ts index 077249018b..8bdc507d8a 100644 --- a/packages/paperclip-runner/src/drivers/acpx/codex-acpx-driver.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/codex-acpx-driver.test.ts @@ -14,6 +14,7 @@ import { type CodexAcpxDriverOptions, } from "./codex-acpx-driver.js"; import type { + AcpxRuntimeSteeringCapability, AcpxRuntimeTurnInput, AcpxRuntimeTurn, OpenAcpxRuntimeHostOptions, @@ -21,6 +22,47 @@ import type { import type { AcpxRecoveryWorkspaceLease } from "./runtime-sandbox.js"; describe("Codex ACPX harness driver", () => { + it("delivers negotiated steering and follow-up distinctly, once, for the active turn", async () => { + const fixture = driverFixture({ agent: "pi", model: "openrouter/deepseek/deepseek-v4-flash-0731", providerPolicy: { readOnly: true } }); + fixture.host.steeringCapability.mockReturnValue({ steering: true, queuedFollowUp: true }); + const session = await fixture.driver.openSession({ runId: "run-controls", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); + expect(fixture.hostOptions?.providerPolicy).toEqual({ readOnly: true }); + expect(session.turnControlCapabilities?.()).toEqual({ steering: true, queuedFollowUp: true }); + const { turnId } = await session.startTurn({ message: { text: "Work" } }); + const turnInput = fixture.host.startTurn.mock.calls[0]![0]; + const context = { requestId: 0, signal: new AbortController().signal }; + await expect(turnInput.onExtensionRequest!("provider/request", { sessionId: "agent-1" }, context)).rejects.toThrow(/session mismatch/); + await expect(turnInput.onExtensionRequest!("provider/request", { sessionId: "backend-1" }, context)).rejects.toThrow(/adapter is unavailable/); + await session.steer!({ turnId, correlationId: "control-1", message: { text: "Change focus" } }); + await session.steer!({ turnId, correlationId: "control-2", mode: "follow_up", message: { text: "Then validate" } }); + expect(fixture.host.steerActiveTurn).toHaveBeenCalledExactlyOnceWith("Change focus", `run-controls:${turnId}`); + expect(fixture.host.queueFollowUp).toHaveBeenCalledExactlyOnceWith("Then validate", `run-controls:${turnId}`); + expect(fixture.host.interruptActiveTurn).not.toHaveBeenCalled(); + await expect(session.steer!({ turnId, correlationId: "control-2", message: { text: "Duplicate" } })).rejects.toThrow(/already attempted/); + await expect(session.steer!({ turnId: "wrong-turn", message: { text: "Stale" } })).rejects.toThrow(); + fixture.finishTurn({ status: "completed" }); + const events = await collectUntil(session.events(), "turn.completed"); + expect(events.filter(event => event.payload.kind === "steering_acknowledgement").map(event => event.payload.mode)).toEqual(["steer", "follow_up"]); + for (const event of events) expect(validatePrpEvent(event).ok).toBe(true); + await expect(session.steer!({ turnId, message: { text: "Already settled" } })).rejects.toThrow(); + await session.close({ reason: "controls verified" }); + }); + + it("rejects unnegotiated controls and retains ambiguous delivery attempts", async () => { + const fixture = driverFixture({ agent: "pi", model: "openrouter/deepseek/deepseek-v4-flash-0731", providerPolicy: { readOnly: true } }); + const session = await fixture.driver.openSession({ runId: "run-controls", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); + const { turnId } = await session.startTurn({ message: { text: "Work" } }); + await expect(session.steer!({ turnId, message: { text: "No handshake" } })).rejects.toThrow(/did not negotiate/); + expect(fixture.host.steerActiveTurn).not.toHaveBeenCalled(); + fixture.host.steeringCapability.mockReturnValue({ steering: true, queuedFollowUp: false }); + fixture.host.steerActiveTurn.mockRejectedValueOnce(new Error("delivery acknowledgement lost")); + const input = { turnId, correlationId: "ambiguous", message: { text: "Maybe delivered" } }; + await expect(session.steer!(input)).rejects.toThrow(/acknowledgement lost/); + await expect(session.steer!(input)).rejects.toThrow(/already attempted/); + expect(fixture.host.steerActiveTurn).toHaveBeenCalledOnce(); + await session.close({ reason: "ambiguous control verified" }); + }); + it.each([ ["claude", "claude-sonnet-5"], ["codex", "gpt-5.6-sol"], ] as const)("launches %s in full auto when no mode is supplied", async (agent, model) => { @@ -1459,6 +1501,73 @@ describe("Codex ACPX harness driver", () => { }); }); + it("delivers only offered permission choices and rejects stale or duplicate answers", async () => { + const fixture = driverFixture({ agent: "claude", model: "claude-sonnet-5" }); + const session = await fixture.driver.openSession({ + runId: "run-permission", normalizedSessionId: "session-1", workingDirectory: "/workspace", + }); + const created = collectUntil(session.events(), "runtime_request.created"); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Run validation." } }); + const callback = fixture.host.startTurn.mock.calls[0]![0].onPermissionRequest!; + const response = callback({ + inferredKind: "execute", raw: { + sessionId: "agent-session-1", toolCall: { toolCallId: "tool-permission", title: "Run validation" }, + options: [{ optionId: "once", kind: "allow_once", name: "Allow once" }], + }, + } as Parameters[0], { signal: new AbortController().signal, responseDelivery: Promise.resolve() }); + const events = await created; + const request = session.pendingRuntimeRequests!()[0]!; + expect(events.at(-1)?.payload).toMatchObject({ request: { + type: "permission", choices: [{ key: "accept" }, { key: "cancel" }], + origin: { method: "session/request_permission" }, + } }); + await expect(session.resolveRuntimeRequest!({ requestId: request.requestId, turnId: "old-turn", + resolution: { action: "accept" } })).rejects.toThrow(); + await expect(session.resolveRuntimeRequest!({ requestId: request.requestId, turnId, + resolution: { action: "accept_for_session" } })).rejects.toThrow("offered choice"); + expect(session.pendingRuntimeRequests!()).toHaveLength(1); + await session.resolveRuntimeRequest!({ requestId: request.requestId, turnId, resolution: { action: "accept" } }); + await expect(response).resolves.toEqual({ outcome: "allow_once" }); + await expect(session.resolveRuntimeRequest!({ requestId: request.requestId, turnId, + resolution: { action: "accept" } })).rejects.toThrow("no longer pending"); + fixture.finishTurn({ status: "completed", stopReason: "end_turn" }); + await collectUntil(session.events(), "turn.completed"); + await session.close({ reason: "permission verified" }); + }); + + it.each(["written", "failed"] as const)("waits for the exact provider permission reply receipt: %s", async outcome => { + const fixture = driverFixture({ agent: "copilot", model: "explicit-test-model", providerPolicy: { readOnly: false } }); + const session = await fixture.driver.openSession({ runId: "run-receipt", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); + const created = collectUntil(session.events(), "runtime_request.created"); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Approve with receipt." } }); + const callback = fixture.host.startTurn.mock.calls[0]![0].onPermissionRequest!; + const receipt = deferred(); + const providerResponse = callback({ inferredKind: "execute", raw: { + sessionId: "agent-session-1", toolCall: { toolCallId: "receipt-tool", title: "Run validation" }, + options: [{ optionId: "session", kind: "allow_always", name: "Allow for session" }], + } } as Parameters[0], { signal: new AbortController().signal, responseDelivery: receipt.promise }); + await created; + const request = session.pendingRuntimeRequests!()[0]!; + expect(request.details).toMatchObject({ choices: [{ key: "accept_for_session" }, { key: "cancel" }] }); + let acknowledged = false; + const emitted = collectUntil(session.events(), outcome === "written" ? "runtime_request.resolved" : "runtime_request.expired"); + const resolution = session.resolveRuntimeRequest!({ requestId: request.requestId, turnId, resolution: { action: "accept_for_session" } }) + .then(() => { acknowledged = true; }); + await expect(providerResponse).resolves.toEqual({ outcome: "allow_always" }); + expect(acknowledged).toBe(false); + if (outcome === "written") { receipt.resolve(); await resolution; } + else { + const rejected = expect(resolution).rejects.toThrow("pipe failed"); + receipt.reject(new Error("pipe failed")); await rejected; + } + const events = await emitted; + expect(events.filter(event => event.eventType === "runtime_request.resolved")).toHaveLength(outcome === "written" ? 1 : 0); + if (outcome === "failed") expect(events.at(-1)?.payload).toMatchObject({ replayAllowed: false, reason: "response_delivery_failed" }); + expect(session.pendingRuntimeRequests!()).toHaveLength(0); + if (outcome === "written") fixture.finishTurn({ status: "completed", stopReason: "end_turn" }); + await session.close({ reason: "receipt checked" }); + }); + it("round-trips a provider-neutral ACP form through the runtime request boundary", async () => { const fixture = driverFixture(); const session = await fixture.driver.openSession({ @@ -1493,7 +1602,7 @@ describe("Codex ACPX harness driver", () => { }, }, }, - { requestId: "rpc-question-1", signal: controller.signal }, + { requestId: "rpc-question-1", signal: controller.signal, responseDelivery: Promise.resolve() }, ); const events = await createdEvent; const request = session.pendingRuntimeRequests!()[0]!; @@ -1575,7 +1684,7 @@ describe("Codex ACPX harness driver", () => { }, { requestId: "rpc-question-created-pressure", - signal: new AbortController().signal, + signal: new AbortController().signal, responseDelivery: Promise.resolve(), }, ), ).resolves.toEqual({ action: "cancel" }); @@ -1609,7 +1718,7 @@ describe("Codex ACPX harness driver", () => { properties: { value: { type: "string" } }, }, }, - { requestId: "rpc-question-abort", signal: controller.signal }, + { requestId: "rpc-question-abort", signal: controller.signal, responseDelivery: Promise.resolve() }, ); await createdEvent; const cancelledEvent = collectUntil( @@ -1658,7 +1767,7 @@ describe("Codex ACPX harness driver", () => { }, { requestId: "rpc-question-stream-failure", - signal: new AbortController().signal, + signal: new AbortController().signal, responseDelivery: Promise.resolve(), }, ); await vi.waitFor(() => { @@ -1704,7 +1813,7 @@ describe("Codex ACPX harness driver", () => { }, { requestId: "rpc-question-handoff", - signal: new AbortController().signal, + signal: new AbortController().signal, responseDelivery: Promise.resolve(), }, ); await createdEvent; @@ -1772,7 +1881,7 @@ describe("Codex ACPX harness driver", () => { }, { requestId: "rpc-question-handoff-pressure", - signal: new AbortController().signal, + signal: new AbortController().signal, responseDelivery: Promise.resolve(), }, ); await vi.waitFor(() => { @@ -2724,6 +2833,9 @@ function fakeHost(createTurn: () => AcpxRuntimeTurn, onClose: () => void) { }, })), startTurn: vi.fn((_input: AcpxRuntimeTurnInput) => createTurn()), + steeringCapability: vi.fn<() => AcpxRuntimeSteeringCapability | null>(() => null), + steerActiveTurn: vi.fn(async (_text: string, _requestId?: string) => undefined), + queueFollowUp: vi.fn(async (_text: string, _requestId?: string) => undefined), interruptActiveTurn: vi.fn(async () => undefined), close: vi.fn(async () => { onClose(); diff --git a/packages/paperclip-runner/src/drivers/acpx/codex-acpx-driver.ts b/packages/paperclip-runner/src/drivers/acpx/codex-acpx-driver.ts index 065b72d66d..6647d1555f 100644 --- a/packages/paperclip-runner/src/drivers/acpx/codex-acpx-driver.ts +++ b/packages/paperclip-runner/src/drivers/acpx/codex-acpx-driver.ts @@ -1,3 +1,5 @@ +import { requireAcpxResponseDelivery } from "./response-delivery.js"; +import { acpxProfileClientCapabilities, bindAcpxExtensionTurn, validateAcpxRichEvent, createAcpxProfileExtensionAdapter, type AcpxExtensionInput } from "./profile-extensions.js"; import { createHash, randomBytes } from "node:crypto"; import type { @@ -5,6 +7,8 @@ import type { AcpElicitationRequest, AcpElicitationResponse, AcpRuntimeEvent, + AcpPermissionRequest, + AcpPermissionDecision, } from "acpx/runtime"; import { @@ -52,12 +56,14 @@ import { DEFAULT_CODEX_ACPX_RUNTIME_SHUTDOWN_BOUND_MS, openCodexAcpxRuntime, } from "./codex-runtime-adapter.js"; +import { normalizeAcpxPermission } from "./acp-permission-adapter.js"; import { normalizeAcpFormElicitation, type NormalizedAcpForm, } from "./acp-question-adapter.js"; import { acpxDriverDescriptor, + acpxCapabilities, validateAcpxDriverConfig, } from "./driver-profile.js"; import { @@ -76,6 +82,9 @@ import { type AcpxRecoveryWorkspaceLease, } from "./runtime-sandbox.js"; +import { AcpxTurnControlLedger, parseAcpxTurnControl, type AcpxTurnControlMode } from "./turn-controls.js"; +import { acpxUsageEstimateNotice, persistedAcpxTurnUsage } from "./usage-accounting.js"; + const MAX_BUFFERED_EVENTS = 512; const TERMINAL_EVENT_RESERVE = 3; const TURN_START_EVENT_COUNT = 3; @@ -109,8 +118,9 @@ const QUARANTINED_HOST_ADMISSION_GRACE_MS = interface PendingAcpxRuntimeRequest { request: HarnessRuntimeRequest; - normalized: NormalizedAcpForm; - settle(response: AcpElicitationResponse): void; + responseDelivery: Promise; + prepareResolution(resolution: HarnessRuntimeRequestResolution): () => void; + cancel(): void; cleanup(): void; settling: boolean; } @@ -130,6 +140,7 @@ export interface CodexAcpxDriverOptions { runtimeDirectory: string; model: string; permissionMode?: NativeAcpxPermissionMode; + providerPolicy?: { readOnly: boolean }; systemInstructions?: string; environment?: NodeJS.ProcessEnv; managedCodexCredentialSourcePath?: string; @@ -147,6 +158,9 @@ interface CodexAcpxHost { startTurn( input: Parameters[0], ): AcpxRuntimeTurn; + steeringCapability?: AcpxRuntimeHost["steeringCapability"]; + steerActiveTurn?: AcpxRuntimeHost["steerActiveTurn"]; + queueFollowUp?: AcpxRuntimeHost["queueFollowUp"]; interruptActiveTurn(reason: string): Promise; close(input: { reason: string }): Promise; } @@ -200,6 +214,7 @@ export async function probeQualifiedAcpxEnvironment( agent: options.agent, model: options.model, permissionMode: "deny-all", + providerPolicy: { readOnly: true }, systemInstructions: "Paperclip Runner environment qualification probe. Do not execute a provider turn.", ...(options.environment === undefined @@ -259,14 +274,13 @@ export class CodexAcpxDriver implements HarnessDriver { options: CodexAcpxDriverOptions, dependencies: CodexAcpxDriverDependencies = {}, ) { - if (options.agent === "pi") { - throw new Error( - "Pi ACPX driver is unavailable until descriptor-confined verified launch is implemented", - ); + if (["pi", "cursor", "copilot"].includes(options.agent ?? "codex") && typeof options.providerPolicy?.readOnly !== "boolean") { + throw new Error("ACP candidate requires an explicit provider read-only policy"); } this.#options = { ...options, agent: options.agent ?? "codex", + ...(options.providerPolicy ? { providerPolicy: { readOnly: options.providerPolicy.readOnly } } : {}), ...(options.environment ? { environment: { ...options.environment } } : {}), @@ -303,7 +317,7 @@ export class CodexAcpxDriver implements HarnessDriver { resume: true, runtimeRequestResolution: true, runtimeRequestHandoff: true, - unsupported: ["steering", "goals", "threadLineage"], + unsupported: descriptor.capabilities.unsupported, }, }; } @@ -435,8 +449,10 @@ export class CodexAcpxDriver implements HarnessDriver { normalizedSessionId: input.normalizedSessionId, workingDirectory: input.workingDirectory, agent: this.#options.agent ?? "codex", + clientCapabilities: acpxProfileClientCapabilities(this.#options.agent ?? "codex"), model: this.#options.model, permissionMode: this.#options.permissionMode ?? "approve-all", + providerPolicy: this.#options.providerPolicy, systemInstructions: this.#options.systemInstructions, environment: this.#options.environment, managedCodexCredentialSourcePath: @@ -761,6 +777,7 @@ class CodexAcpxSession implements HarnessSession { payload: Record; } | null = null; #runtimeRequestSequence = 0; + readonly #turnControls = new AcpxTurnControlLedger(); constructor(input: { host: CodexAcpxHost; @@ -828,6 +845,11 @@ class CodexAcpxSession implements HarnessSession { return this.#events; } + turnControlCapabilities() { + const capability = acpxCapabilities(this.#agent, this.#host.steeringCapability?.()); + return { steering: capability.steering, queuedFollowUp: capability.queuedFollowUp === true }; + } + async startTurn(input: { message: NativeUserMessage; }): Promise<{ turnId: string }> { @@ -853,16 +875,37 @@ class CodexAcpxSession implements HarnessSession { } const turnId = `turn-${randomBytes(12).toString("hex")}`; this.#activeTurnId = turnId; + this.#turnControls.begin(turnId); this.#assistantText = ""; this.#assistantMessageId = null; this.#emit("turn.submitted", { text: input.message.text }, { turnId }); this.#emit("turn.accepted", { turnId }, { turnId }); this.#emit("turn.started", { status: "inProgress" }, { turnId }); + const extensions = bindAcpxExtensionTurn({ + adapter: createAcpxProfileExtensionAdapter(this.#agent, { + workspacePath: this.#input.workingDirectory, sessionId: this.#host.identity().backendSessionId, turnId, + }), + active: () => this.#activeTurnId === turnId && !this.#closingStarted, + sessionId: this.#host.identity().backendSessionId, + waitForInput: (input, context) => this.#handleExtensionInput(turnId, input, context), + emit: event => { + if (!this.#emit(event.eventType, event.payload, { turnId, itemId: event.itemId })) { + throw new Error("ACP rich activity could not be retained"); + } + }, + }); let turn: AcpxRuntimeTurn; + const usageBefore = await readUsageStatus(this.#host); try { + this.#assertOpen(); + if (this.#activeTurnId !== turnId) throw new HarnessStaleTurnError(turnId); turn = this.#host.startTurn({ text: input.message.text, requestId: `${safeId(this.#input.runId, "run")}:${turnId}`, + onExtensionRequest: extensions.onExtensionRequest, + onExtensionNotification: extensions.onExtensionNotification, + onPermissionRequest: (request, context) => + this.#handlePermission(turnId, request, context), onElicitation: (request, context) => this.#handleElicitation(turnId, request, context), }); @@ -875,7 +918,7 @@ class CodexAcpxSession implements HarnessSession { ); throw error; } - const pump = this.#pumpTurn(turnId, turn); + const pump = this.#pumpTurn(turnId, turn, extensions.drain, usageBefore); this.#activePump = pump; void pump .finally(() => { @@ -885,6 +928,25 @@ class CodexAcpxSession implements HarnessSession { return { turnId }; } + async steer(input: { turnId: string; message: NativeUserMessage; correlationId?: string; mode?: AcpxTurnControlMode }): Promise { + this.#assertOpen(); + if (input.turnId !== this.#activeTurnId || this.#pendingTerminal) throw new HarnessStaleTurnError(input.turnId); + const mode = input.mode ?? "steer"; + const capability = this.turnControlCapabilities(); + const operation = mode === "steer" ? this.#host.steerActiveTurn : this.#host.queueFollowUp; + if (!(mode === "steer" ? capability?.steering : capability?.queuedFollowUp) || !operation) { + throw new HarnessCapabilityUnavailableError(mode, "the ACP provider did not negotiate this turn control"); + } + const control = parseAcpxTurnControl({ turnId: input.turnId, message: input.message.text, mode, + controlId: input.correlationId ?? `control-${randomBytes(12).toString("hex")}` }); + this.#turnControls.reserve(control, this.#activeTurnId); + await operation.call(this.#host, control.message, `${safeId(this.#input.runId, "run")}:${control.turnId}`); + if (this.#activeTurnId !== control.turnId || this.#closingStarted) throw new HarnessStaleTurnError(control.turnId); + this.#emit("item.completed", { kind: "steering_acknowledgement", mode, + text: mode === "steer" ? "Steering acknowledged for the active turn." : "Follow-up queued after the active work.", status: "acknowledged" }, + { turnId: control.turnId, itemId: stableId("acpx-control", `${control.turnId}:${control.controlId}`) }); + } + async interrupt(input: { turnId?: string; reason?: string }): Promise { this.#assertOpen(); if (input.turnId && input.turnId !== this.#activeTurnId) { @@ -937,18 +999,19 @@ class CodexAcpxSession implements HarnessSession { ); } pending.settling = true; + let dispatched = false; try { const resolution = parseHarnessRuntimeRequestResolution( pending.request.requestKind, input.resolution, pending.request.input, ); - const providerResponse = acpElicitationResponse( - pending.normalized, - resolution, - ); + const deliver = pending.prepareResolution(resolution); if (!this.#pendingRuntimeRequests.delete(input.requestId)) return; pending.cleanup(); + dispatched = true; + deliver(); + await pending.responseDelivery; this.#emit( "runtime_request.resolved", harnessRuntimeRequestOutcome(pending.request, { @@ -959,9 +1022,14 @@ class CodexAcpxSession implements HarnessSession { }), { turnId: input.turnId, itemId: pending.request.itemId }, ); - pending.settle(providerResponse); } catch (error) { - pending.settling = false; + if (!dispatched) { pending.settling = false; throw error; } + this.#emit("runtime_request.expired", { + ...harnessRuntimeRequestOutcome(pending.request, { reason: "response_delivery_failed" }), + replayAllowed: false, + }, { turnId: input.turnId, itemId: pending.request.itemId }); + try { await this.close({ reason: "ACP response delivery failed" }); } + catch (cleanupError) { throw new AggregateError([error, cleanupError], "ACP response delivery and provider cleanup failed"); } throw error; } } @@ -1003,7 +1071,7 @@ class CodexAcpxSession implements HarnessSession { ); } pending.cleanup(); - pending.settle({ action: "cancel" }); + pending.cancel(); const cleanup = Promise.resolve() .then(() => this.#host.interruptActiveTurn( @@ -1353,7 +1421,7 @@ class CodexAcpxSession implements HarnessSession { .catch(() => undefined); } - async #pumpTurn(turnId: string, turn: AcpxRuntimeTurn): Promise { + async #pumpTurn(turnId: string, turn: AcpxRuntimeTurn, drainExtensions: () => Promise, usageBefore: unknown): Promise { try { let index = 0; const normalizeToolEvent = @@ -1364,6 +1432,13 @@ class CodexAcpxSession implements HarnessSession { this.#mapRuntimeEvent(normalizeMessage(normalizeToolEvent(event)), turnId, ++index); } const result = await turn.result; + await drainExtensions(); + const receipt = persistedAcpxTurnUsage(usageBefore, await readUsageStatus(this.#host), turn.requestId, this.#agent); + if (receipt) { + this.#mapRuntimeEvent(receipt as unknown as AcpRuntimeEvent, turnId, ++index); + const estimate = acpxUsageEstimateNotice(receipt, `${turnId}:usage-estimate`); + if (estimate) { validateAcpxRichEvent(estimate); this.#emit(estimate.eventType, estimate.payload, { turnId, itemId: estimate.itemId }); } + } this.#cancelPendingRuntimeRequests("provider turn settled", turnId); if (this.#terminalTurns.has(turnId)) return; if (result.status === "completed") { @@ -1587,6 +1662,96 @@ class CodexAcpxSession implements HarnessSession { } } + async #handleExtensionInput( + turnId: string, + input: AcpxExtensionInput, + context: { requestId: string | number; signal: AbortSignal; responseDelivery?: Promise }, + ): Promise> { + if (this.#closed || this.#activeTurnId !== turnId || context.signal.aborted + || this.#pendingRuntimeRequests.size >= MAX_PENDING_RUNTIME_REQUESTS) return input.cancel(); + const responseDelivery = requireAcpxResponseDelivery(context); + const requestId = stableId("acpx-request", `${turnId}:${++this.#runtimeRequestSequence}:${typeof context.requestId}:${context.requestId}`); + const request: HarnessRuntimeRequest = { + requestId, requestKind: "elicitation", method: input.method, turnId, itemId: requestId, + status: "pending", prompt: boundedText(input.questionSet.title ?? "Provider needs input", 1_000), + details: input.details ?? {}, input: structuredClone(input.questionSet), + origin: { adapter: "acpx-runtime", provider: this.#agent, method: input.method }, + }; + return await new Promise((settle) => { + const cancel = () => { + const pending = this.#pendingRuntimeRequests.get(requestId); + if (!pending || pending.settling || !this.#pendingRuntimeRequests.delete(requestId)) return; + pending.cleanup(); + this.#emit("runtime_request.cancelled", harnessRuntimeRequestOutcome(request, { + action: "cancel", reason: "provider request aborted", + }), { turnId, itemId: requestId }); + settle(input.cancel()); + }; + this.#pendingRuntimeRequests.set(requestId, { + request, responseDelivery, + prepareResolution: resolution => { + const response = input.resolve(resolution); + return () => settle(response); + }, + cancel: () => settle(input.cancel()), + cleanup: () => context.signal.removeEventListener("abort", cancel), + settling: false, + }); + context.signal.addEventListener("abort", cancel, { once: true }); + if (!this.#emit("runtime_request.created", { request: runtimeInputProtocolPayload(request) }, + { turnId, itemId: requestId }) || context.signal.aborted) cancel(); + }); + } + + async #handlePermission( + turnId: string, + request: AcpPermissionRequest, + context: { signal: AbortSignal; responseDelivery?: Promise }, + ): Promise { + const { signal } = context; + if (this.#closed || this.#activeTurnId !== turnId || signal.aborted + || this.#pendingRuntimeRequests.size >= MAX_PENDING_RUNTIME_REQUESTS) { + return { outcome: "cancel" }; + } + const responseDelivery = requireAcpxResponseDelivery(context); + const normalized = normalizeAcpxPermission(request, ["pi", "copilot"].includes(this.#agent) ? { allowAlwaysScope: "session" } : {}); + const requestId = stableId("acpx-permission", `${turnId}:${++this.#runtimeRequestSequence}:${normalized.toolCallId}`); + const runtimeRequest: HarnessRuntimeRequest = { + requestId, requestKind: "permission_approval", method: "session/request_permission", + turnId, itemId: requestId, status: "pending", prompt: normalized.title, + details: { choices: normalized.choices, toolCallId: normalized.toolCallId, kind: normalized.kind }, + origin: { adapter: "acpx-runtime", provider: this.#agent, method: "session/request_permission" }, + }; + return await new Promise((settle) => { + const cancel = () => { + const pending = this.#pendingRuntimeRequests.get(requestId); + if (!pending || pending.settling || !this.#pendingRuntimeRequests.delete(requestId)) return; + pending.cleanup(); + this.#emit("runtime_request.cancelled", harnessRuntimeRequestOutcome(runtimeRequest, { + action: "cancel", reason: "provider request aborted", + }), { turnId, itemId: requestId }); + settle({ outcome: "cancel" }); + }; + this.#pendingRuntimeRequests.set(requestId, { + request: runtimeRequest, responseDelivery, + prepareResolution: (resolution) => { + const response = normalized.resolve(resolution); + return () => settle(response); + }, + cancel: () => settle({ outcome: "cancel" }), + cleanup: () => signal.removeEventListener("abort", cancel), + settling: false, + }); + signal.addEventListener("abort", cancel, { once: true }); + if (!this.#emit("runtime_request.created", { request: { + schema: PAPERCLIP_RUNTIME_REQUEST_SCHEMA_V2, requestKind: "permission_approval", + requestId, type: "permission", status: "pending", prompt: normalized.title, + choices: normalized.choices, details: runtimeRequest.details, + origin: runtimeRequest.origin, turnId, itemId: requestId, + } }, { turnId, itemId: requestId }) || signal.aborted) cancel(); + }); + } + async #handleElicitation( turnId: string, request: AcpElicitationRequest, @@ -1655,6 +1820,7 @@ class CodexAcpxSession implements HarnessSession { ); return { action: "cancel" }; } + const responseDelivery = requireAcpxResponseDelivery(context); const requestId = stableId( "acpx-request", `${turnId}:${++this.#runtimeRequestSequence}:${typeof context.requestId}:${String(context.requestId)}`, @@ -1707,9 +1873,12 @@ class CodexAcpxSession implements HarnessSession { }; context.signal.addEventListener("abort", cancel, { once: true }); this.#pendingRuntimeRequests.set(requestId, { - request: runtimeRequest, - normalized, - settle, + request: runtimeRequest, responseDelivery, + prepareResolution: (resolution) => { + const response = acpElicitationResponse(normalized, resolution); + return () => settle(response); + }, + cancel: () => settle({ action: "cancel" }), cleanup: () => context.signal.removeEventListener("abort", cancel), settling: false, }); @@ -1733,7 +1902,7 @@ class CodexAcpxSession implements HarnessSession { itemId: pending.request.itemId, }, ); - pending.settle({ action: "cancel" }); + pending.cancel(); } } @@ -2310,3 +2479,15 @@ async function settlesWithin( if (timer) clearTimeout(timer); } } + +/** Accounting failure cannot replace the provider's terminal result. */ +async function readUsageStatus(host: CodexAcpxHost): Promise { + let timer: ReturnType | undefined; + try { + return await Promise.race([ + Promise.resolve().then(() => host.status()), + new Promise(resolve => { timer = setTimeout(() => resolve(undefined), 1_000); timer.unref?.(); }), + ]); + } catch { return undefined; } + finally { if (timer) clearTimeout(timer); } +} diff --git a/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.test.ts b/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.test.ts index 2f4598c894..f79a3e291e 100644 --- a/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.test.ts @@ -28,6 +28,124 @@ const HANDLE: AcpRuntimeHandle = { }; describe("Codex ACPX runtime adapter", () => { + it("rejects forged permission session identifiers before delegating or applying full-auto policy", async () => { + const pending = pendingExtensionTurn("turn-1"); + const runtime = fakeRuntime(); vi.mocked(runtime.startTurn).mockReturnValue(pending.turn); + let created!: AcpRuntimeOptions; + const options = openOptions(fakeCommand()); + const port = await openCodexAcpxRuntime(options, { + createRegistry: () => registry(), createStore: () => store(), createRuntime: (value) => { created = value; return runtime; }, + }); + const handler = vi.fn(async () => ({ outcome: "allow_once" as const })); + const turn = port.startTurn({ text: "Write", requestId: "turn-1", onPermissionRequest: handler }); + const signal = new AbortController().signal; + for (const [sessionId, rawSessionId] of [["forged", "backend-1"], ["backend-1", "forged"]]) { + const request = { sessionId, raw: { sessionId: rawSessionId }, inferredKind: "edit" as const }; + await expect(created.onPermissionRequest!(request as never, { signal })).resolves.toEqual({ outcome: "reject_once" }); + } + expect(handler).not.toHaveBeenCalled(); + const responseDelivery = Promise.resolve(); + await expect(created.onPermissionRequest!({ sessionId: "backend-1", raw: { sessionId: "backend-1" }, inferredKind: "edit" } as never, { signal, responseDelivery })).resolves.toEqual({ outcome: "allow_once" }); + expect(handler).toHaveBeenCalledOnce(); + expect(handler).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ responseDelivery })); + options.permissionMode = "approve-all"; + await expect(created.onPermissionRequest!({ sessionId: "forged", raw: {}, inferredKind: "edit" } as never, { signal })).resolves.toEqual({ outcome: "reject_once" }); + pending.settle(); await turn.result; await port.close({ reason: "session checks complete" }); + }); + it("routes only profile-allowed extensions to the owning turn and expires late responses", async () => { + const runtime = fakeRuntime(); + const first = pendingExtensionTurn("turn-1"); + const second = pendingExtensionTurn("turn-2"); + vi.mocked(runtime.startTurn).mockReturnValueOnce(first.turn).mockReturnValueOnce(second.turn); + let created!: AcpRuntimeOptions; + const options = openOptions(fakeCommand()); + options.profile = { ...options.profile, agent: "cursor" }; + options.clientCapabilities = { _meta: { cursor: { test: true } } }; + const port = await openCodexAcpxRuntime(options, { + createRegistry: () => registry(), createStore: () => store(), + createRuntime: (value) => { created = value; return runtime; }, + }); + expect(created.clientCapabilities).toEqual(options.clientCapabilities); + expect(JSON.stringify(vi.mocked(runtime.ensureSession).mock.calls)).not.toContain("clientCapabilities"); + const notification = vi.fn(); + const request = vi.fn(async (_method, _params, context) => ({ requestId: context.requestId })); + const admittedFirst = port.startTurn({ text: "First", requestId: "turn-1", onExtensionRequest: request, onExtensionNotification: notification }); + const signal = new AbortController().signal; + await expect(created.onExtensionRequest!("cursor/ask_question", { sessionId: "backend-1" }, { requestId: 0, signal })).resolves.toEqual({ requestId: 0 }); + const sessionless = { toolCallId: "native-cursor-plan", plan: "Review the complete plan" }; + const responseDelivery = Promise.resolve(); + await expect(created.onExtensionRequest!("cursor/create_plan", sessionless, { requestId: "plan", signal, responseDelivery })).resolves.toEqual({ requestId: "plan" }); + expect(request).toHaveBeenLastCalledWith("cursor/create_plan", { ...sessionless, sessionId: "backend-1" }, expect.objectContaining({ requestId: "plan", responseDelivery })); + expect(sessionless).not.toHaveProperty("sessionId"); + await expect(created.onExtensionRequest!("pi/steer", {}, { requestId: 1, signal })).rejects.toThrow("admitted active turn"); + await expect(created.onExtensionRequest!("cursor/ask_question", { sessionId: "other" }, { requestId: 1, signal })).rejects.toThrow("admitted active turn"); + created.onExtensionNotification!("cursor/task", { sessionId: "backend-1", taskId: "t" }); + created.onExtensionNotification!("cursor/task", { sessionId: "other" }); + created.onExtensionNotification!("cursor/ask_question", { sessionId: "backend-1" }); + expect(notification).toHaveBeenCalledExactlyOnceWith("cursor/task", { sessionId: "backend-1", taskId: "t" }); + created.onExtensionNotification!("cursor/task", { toolCallId: "native-child" }); + expect(notification).toHaveBeenLastCalledWith("cursor/task", { sessionId: "backend-1", toolCallId: "native-child" }); + created.onExtensionNotification!("cursor/task", { sessionId: "agent-1" }); + expect(notification).toHaveBeenCalledTimes(2); + request.mockImplementationOnce(() => new Promise(() => undefined)); + const pending = created.onExtensionRequest!("cursor/ask_question", {}, { requestId: 2, signal }); + const rejected = expect(pending).rejects.toThrow("expired"); + first.settle(); await admittedFirst.result; await rejected; + const nextRequest = vi.fn(async () => ({ accepted: true })); + const admittedSecond = port.startTurn({ text: "Second", requestId: "turn-2", onExtensionRequest: nextRequest }); + expect(nextRequest).not.toHaveBeenCalled(); + await admittedSecond.cancel({ reason: "cancelled" }); + await expect(created.onExtensionRequest!("cursor/ask_question", {}, { requestId: 3, signal })).rejects.toThrow("admitted active turn"); + second.settle(); await admittedSecond.result; await port.close({ reason: "test finished" }); + }); + + it("requires the Pi capability handshake and acknowledgments, preserves follow-up order, and never starts a second prompt", async () => { + const pending = pendingExtensionTurn("turn-1"); + const runtime = Object.assign(fakeRuntime(), { requestExtension: vi.fn(async () => ({ accepted: true })) }); + vi.mocked(runtime.startTurn).mockReturnValue(pending.turn); + let created!: AcpRuntimeOptions & { onAgentInitialize?: (result: unknown) => void }; + const options = openOptions(fakeCommand()); + options.profile = { ...options.profile, agent: "pi" }; + const port = await openCodexAcpxRuntime(options, { + createRegistry: () => registry(), createStore: () => store(), + createRuntime: (value) => { created = value; return runtime; }, + }); + const turn = port.startTurn({ text: "Initial", requestId: "turn-1" }); + await expect(port.steerActiveTurn!("Update", "turn-1")).rejects.toThrow("supported active turn"); + created.onAgentInitialize!({ agentCapabilities: { _meta: { paperclipPi: { version: 1, steering: true, queuedFollowUp: true } } } }); + expect(port.steeringCapability!()).toEqual({ steering: true, queuedFollowUp: true }); + await expect(port.steerActiveTurn!("Update", "wrong")).rejects.toThrow("supported active turn"); + await port.steerActiveTurn!("Update", "turn-1"); + await Promise.all([port.queueFollowUp!("One", "turn-1"), port.queueFollowUp!("Two", "turn-1")]); + expect(runtime.requestExtension.mock.calls.map((call) => call[0])).toEqual([ + { handle: HANDLE, method: "pi/steer", params: { sessionId: "backend-1", message: "Update" }, sessionMode: "persistent" }, + { handle: HANDLE, method: "pi/follow_up", params: { sessionId: "backend-1", message: "One" }, sessionMode: "persistent" }, + { handle: HANDLE, method: "pi/follow_up", params: { sessionId: "backend-1", message: "Two" }, sessionMode: "persistent" }, + ]); + expect(runtime.startTurn).toHaveBeenCalledTimes(1); + runtime.requestExtension.mockResolvedValueOnce({ accepted: false }); + await expect(port.steerActiveTurn!("Denied", "turn-1")).rejects.toThrow("not acknowledged"); + runtime.requestExtension.mockImplementationOnce(() => new Promise(() => undefined)); + const stale = port.queueFollowUp!("Stale", "turn-1"); + const rejected = expect(stale).rejects.toThrow("expired"); + pending.settle(); await turn.result; await rejected; + await expect(port.queueFollowUp!("Late", "turn-1")).rejects.toThrow("supported active turn"); + await port.close({ reason: "test finished" }); + }); + + it("does not grant steering to Copilot or Cursor when an untrusted initialize result claims Pi support", async () => { + const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "copilot" }; + const runtime = fakeRuntime(); + const port = await openCodexAcpxRuntime(options, { + createRegistry: () => registry(), createStore: () => store(), + createRuntime: (created) => { + (created as AcpRuntimeOptions & { onAgentInitialize: (result: unknown) => void }).onAgentInitialize({ agentCapabilities: { _meta: { paperclipPi: { version: 1, steering: true, queuedFollowUp: true } } } }); + return runtime; + }, + }); + expect(port.steeringCapability!()).toBeNull(); + await port.close({ reason: "test finished" }); + }); it.each(["approve-reads", "approve-paperclip"] as const)("%s stops unassigned operations when approval has no handler", async (permissionMode) => { const runtime = fakeRuntime(); let runtimeOptions: AcpRuntimeOptions | undefined; @@ -1681,7 +1799,7 @@ describe("Codex ACPX runtime adapter", () => { await expect( runtimeOptions?.onPermissionRequest?.( { - sessionId: "session-1", + sessionId: "backend-1", inferredKind: "write", raw: {}, }, @@ -2828,6 +2946,17 @@ function fakeRuntime(handle: AcpRuntimeHandle = HANDLE): AcpRuntime { }; } +function pendingExtensionTurn(requestId: string) { + let settle!: () => void; + const result = new Promise((resolve) => { settle = resolve; }); + return { settle, turn: { + requestId, promptStarted: Promise.resolve(), + events: (async function* () { await result; })(), + result: result.then(() => ({ status: "completed" as const })), + cancel: async () => undefined, closeStream: async () => undefined, + } }; +} + function runtimeWithProvider( runtime: AcpRuntime, options: AcpRuntimeOptions, diff --git a/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts b/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts index 1444b9d420..fcc480f74f 100644 --- a/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts +++ b/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts @@ -16,6 +16,7 @@ import { import type { AcpxRuntimeGoalCapability, AcpxRuntimeGoalSnapshot, + AcpxRuntimeSteeringCapability, AcpxRuntimePort, AcpxRuntimePortIdentity, AcpxRuntimePortOpenOptions, @@ -28,6 +29,7 @@ import { } from "./installation-integrity.js"; import type { AcpxModelStatus } from "./model-verification.js"; import { AcpxApprovalRequiredError, decideAcpxPermission } from "./permission-policy.js"; +import { ACPX_CAPABILITY_PROFILES } from "./capability-profiles.js"; const VERIFIED_COMMAND_SENTINEL = "paperclip-verified-acpx-command"; const DEFAULT_RUNTIME_CLOSE_TIMEOUT_MS = 2_000; @@ -80,6 +82,22 @@ interface AcpxRuntimeGoalState { observedSnapshot: boolean; } +interface AcpxRuntimeExtensionTurn { + requestId: string; + sessionId: string; + controller: AbortController; + signal: AbortSignal; + promptStarted: Promise; + onRequest?: AcpRuntimeOptions["onExtensionRequest"]; + onNotification?: AcpRuntimeOptions["onExtensionNotification"]; +} + +interface AcpxRuntimeExtensionBoundary { + active: AcpxRuntimeExtensionTurn | null; + sessionIds: Set; + steering: AcpxRuntimeSteeringCapability | null; +} + class AcpxRuntimeCloseTimeoutError extends Error { constructor() { super("ACPX runtime close timed out"); @@ -245,7 +263,19 @@ export async function openQualifiedAcpxRuntime( .filter((server) => server.runnerOwned) .map((server) => server.name), ); - const permissionBoundary: { active: AbortController | null } = { active: null }; + const permissionBoundary: { + active: AbortController | null; + handler?: AcpRuntimeOptions["onPermissionRequest"]; + } = { active: null }; + const extensionProfile = ACPX_CAPABILITY_PROFILES[options.profile.agent]; + const extensionRequests = new Set(extensionProfile.extensionRequests); + const extensionNotifications = new Set(extensionProfile.extensionNotifications); + const extensionBoundary: AcpxRuntimeExtensionBoundary = { + active: null, sessionIds: new Set(), steering: null, + }; + const ownsExtensionTurn = (active: AcpxRuntimeExtensionTurn, params: Record): boolean => + extensionBoundary.active === active && !active.signal.aborted + && (params.sessionId === undefined || (typeof params.sessionId === "string" && extensionBoundary.sessionIds.has(params.sessionId))); const goalState: AcpxRuntimeGoalState = { capability: null, snapshot: null, @@ -283,6 +313,27 @@ export async function openQualifiedAcpxRuntime( ? "approve-reads" : options.permissionMode, elicitationModes: ["form"], + ...(options.clientCapabilities === undefined ? {} : { clientCapabilities: structuredClone(options.clientCapabilities) }), + extensionMethods: [...new Set([...extensionRequests, ...extensionNotifications])], + onExtensionRequest: async (method, params, context) => { + const active = extensionBoundary.active; + if (!extensionRequests.has(method) || !active?.onRequest || !ownsExtensionTurn(active, params) || context.signal.aborted) { + throw new Error("ACPX extension request does not own an admitted active turn"); + } + const signal = AbortSignal.any([active.signal, context.signal]); + // Cursor's native extensions omit sessionId. Only after admission may + // that omission inherit this exact prompt's ACP wire session identity. + const boundParams = params.sessionId === undefined ? { ...params, sessionId: active.sessionId } : params; + const response = await abortableExtensionResult(active.onRequest(method, boundParams, { requestId: context.requestId, signal, responseDelivery: context.responseDelivery }), signal); + signal.throwIfAborted(); + if (!ownsExtensionTurn(active, params)) throw new Error("ACPX extension request turn expired"); + return response; + }, + onExtensionNotification: (method, params) => { + const active = extensionBoundary.active; + if (!extensionNotifications.has(method) || !active?.onNotification || !ownsExtensionTurn(active, params)) return; + active.onNotification(method, params.sessionId === undefined ? { ...params, sessionId: active.sessionId } : params); + }, nonInteractivePermissions: "fail", permissionPolicy: { ...options.permissionPolicy, @@ -301,7 +352,12 @@ export async function openQualifiedAcpxRuntime( { name: "Authorization", value: `Bearer ${server.bearerToken}` }, ], })), - onPermissionRequest: async (request) => { + onPermissionRequest: async (request, context) => { + const rawSessionId = objectRecord(request.raw).sessionId; + if (!extensionBoundary.sessionIds.has(request.sessionId) + || (rawSessionId !== undefined && rawSessionId !== request.sessionId)) { + return { outcome: "reject_once" }; + } const disposition = decideAcpxPermission( options.profile.agent, options.permissionMode, @@ -314,6 +370,20 @@ export async function openQualifiedAcpxRuntime( }, ); if (disposition === "delegate") { + const active = permissionBoundary.active; + const handler = permissionBoundary.handler; + if (active && handler && !active.signal.aborted && !context.signal.aborted) { + // Capture this turn's callback before awaiting. A session-lifetime + // callback must never acquire the next turn's approval authority. + const decision = await handler(request, { + signal: AbortSignal.any([active.signal, context.signal]), + responseDelivery: context.responseDelivery, + }); + if (permissionBoundary.active !== active || active.signal.aborted || context.signal.aborted) { + return { outcome: "cancel" }; + } + return decision ?? { outcome: "cancel" }; + } // This runtime has no interactive approval bridge. Stop the active // turn instead of asking the model to recover from an unexplained // denial or wait for an approval that nobody can answer. @@ -325,6 +395,12 @@ export async function openQualifiedAcpxRuntime( onAgentInitialize: (result) => { const capability = goalCapabilityFromAcpMessage({ result }); if (capability) goalState.capability = capability; + // Only the runner-owned Pi wrapper has an admitted steering contract. + // Never infer it from generic ACP prompt support or native-only features. + const pi = objectRecord(objectRecord(objectRecord(objectRecord(result).agentCapabilities)._meta).paperclipPi); + extensionBoundary.steering = options.profile.agent === "pi" && pi.version === 1 + ? { steering: pi.steering === true, queuedFollowUp: pi.queuedFollowUp === true } + : null; }, onSessionNotification: acceptGoalNotification, spawnEnvironment: () => ({ @@ -454,6 +530,7 @@ export async function openQualifiedAcpxRuntime( goalState, commandLaunches, permissionBoundary, + extensionBoundary, ); } catch (error) { const cleanupReason = "ACPX runtime identity validation failed"; @@ -881,8 +958,37 @@ function runtimePort( runtimeCloseTimeoutMs: number, goalState: AcpxRuntimeGoalState, commandLaunches: { count: number; refreshConsumedCommand?: () => Promise }, - permissionBoundary: { active: AbortController | null }, + permissionBoundary: { active: AbortController | null; handler?: AcpRuntimeOptions["onPermissionRequest"] }, + extensionBoundary: AcpxRuntimeExtensionBoundary, ): AcpxRuntimePort { + extensionBoundary.sessionIds = new Set([identity.backendSessionId]); + let extensionControls: Promise = Promise.resolve(); + const controlActiveTurn = (method: "pi/steer" | "pi/follow_up", text: string, requestId: string): Promise => { + const active = extensionBoundary.active; + const supported = method === "pi/steer" + ? extensionBoundary.steering?.steering : extensionBoundary.steering?.queuedFollowUp; + if (!supported || !runtime.requestExtension || !active || active.requestId !== requestId || active.signal.aborted) { + return Promise.reject(new Error("ACPX extension control does not own a supported active turn")); + } + if (!text.trim() || Buffer.byteLength(text, "utf8") > 65_536 || text.includes("\0")) { + return Promise.reject(new Error("ACPX extension control message is invalid")); + } + const send = async (): Promise => { + await abortableExtensionResult(active.promptStarted, active.signal); + active.signal.throwIfAborted(); + if (extensionBoundary.active !== active) throw new Error("ACPX extension control turn expired"); + const response = await abortableExtensionResult(runtime.requestExtension!({ + handle, method, params: { sessionId: identity.backendSessionId, message: text }, sessionMode: "persistent", + }), active.signal); + active.signal.throwIfAborted(); + if (extensionBoundary.active !== active) throw new Error("ACPX extension control turn expired"); + if (response.accepted !== true) throw new Error("ACPX extension control was not acknowledged"); + }; + const delivery = extensionControls.then(send); + // Preserve caller ordering without letting one rejection deadlock later calls. + extensionControls = delivery.catch(() => undefined); + return delivery; + }; type RuntimeCloseAttempt = { readonly outcome: Promise; readonly reconciliationGeneration: number; @@ -1026,6 +1132,7 @@ function runtimePort( attemptNumber: number; }; }): Promise { + extensionBoundary.active?.controller.abort(new Error("ACPX runtime is closing")); if (runtimeClosed) return; if ( runtimeCloseAttempt?.origin.kind === "reconciliation" && @@ -1112,6 +1219,11 @@ function runtimePort( async getStatus() { return await persistedRuntimeStatus(sessionStore, handle, identity); }, + steeringCapability() { + return extensionBoundary.steering === null ? null : structuredClone(extensionBoundary.steering); + }, + steerActiveTurn: (text, requestId) => controlActiveTurn("pi/steer", text, requestId), + queueFollowUp: (text, requestId) => controlActiveTurn("pi/follow_up", text, requestId), goalCapability() { return goalState.capability === null ? null @@ -1205,8 +1317,22 @@ function runtimePort( } : {}), startTurn(input) { + if (extensionBoundary.active) throw new Error("ACPX runtime already has an active turn"); const approval = new AbortController(); + const controller = new AbortController(); + const extensionTurn: AcpxRuntimeExtensionTurn = { + requestId: input.requestId, sessionId: identity.backendSessionId, controller, + signal: input.signal ? AbortSignal.any([controller.signal, input.signal, approval.signal]) : AbortSignal.any([controller.signal, approval.signal]), + promptStarted: Promise.resolve(), + onRequest: input.onExtensionRequest, onNotification: input.onExtensionNotification, + }; + extensionBoundary.active = extensionTurn; + const releaseExtensionTurn = (): void => { + controller.abort(new Error("ACPX extension turn expired")); + if (extensionBoundary.active === extensionTurn) extensionBoundary.active = null; + }; permissionBoundary.active = approval; + permissionBoundary.handler = input.onPermissionRequest; const finishOwnershipAdmission = children.beginLifetimeOwnershipAdmission(); let turn: AcpxRuntimeTurn; @@ -1224,11 +1350,13 @@ function runtimePort( : {}), }); } catch (error) { - if (permissionBoundary.active === approval) permissionBoundary.active = null; + releaseExtensionTurn(); + if (permissionBoundary.active === approval) { permissionBoundary.active = null; permissionBoundary.handler = undefined; } void finishOwnershipAdmission().catch(() => undefined); throw error; } const guarded = turnWithVerifiedLifetimeOwnership(turn, finishOwnershipAdmission); + extensionTurn.promptStarted = guarded.promptStarted; const result = guarded.result.then( (value) => { approval.signal.throwIfAborted(); @@ -1241,12 +1369,21 @@ function runtimePort( }, (error: unknown) => { approval.signal.throwIfAborted(); throw error; }, ).finally(() => { - if (permissionBoundary.active === approval) permissionBoundary.active = null; + releaseExtensionTurn(); + if (permissionBoundary.active === approval) { permissionBoundary.active = null; permissionBoundary.handler = undefined; approval.abort(); } }); void result.catch(() => undefined); return { ...guarded, result, + cancel: (input) => { + controller.abort(new Error("ACPX extension turn cancelled")); + return guarded.cancel(input); + }, + closeStream: (input) => { + controller.abort(new Error("ACPX extension stream closed")); + return guarded.closeStream(input); + }, events: (async function* () { try { for await (const event of guarded.events) { @@ -1802,6 +1939,17 @@ function objectRecord(value: unknown): Record { : {}; } +async function abortableExtensionResult(result: Promise, signal: AbortSignal): Promise { + signal.throwIfAborted(); + let rejectAbort: (() => void) | undefined; + const cancellation = new Promise((_resolve, reject) => { + rejectAbort = () => reject(signal.reason ?? new Error("ACPX extension turn expired")); + signal.addEventListener("abort", rejectAbort, { once: true }); + }); + try { return await Promise.race([result, cancellation]); } + finally { if (rejectAbort) signal.removeEventListener("abort", rejectAbort); } +} + function goalCapabilityFromAcpMessage( message: unknown, ): AcpxRuntimeGoalCapability | null { diff --git a/packages/paperclip-runner/src/drivers/acpx/driver-profile.test.ts b/packages/paperclip-runner/src/drivers/acpx/driver-profile.test.ts index 054956788c..a325f1e29d 100644 --- a/packages/paperclip-runner/src/drivers/acpx/driver-profile.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/driver-profile.test.ts @@ -7,6 +7,14 @@ import { } from "./driver-profile.js"; describe("ACPX driver profile", () => { + it("enables only negotiated Pi controls and keeps the static profile conservative", () => { + expect(acpxCapabilities("pi")).toMatchObject({ steering: false, queuedFollowUp: false }); + expect(acpxCapabilities("pi", { steering: true, queuedFollowUp: false })).toMatchObject({ steering: true, queuedFollowUp: false }); + expect(acpxCapabilities("pi", { steering: false, queuedFollowUp: true })).toMatchObject({ steering: false, queuedFollowUp: true }); + for (const agent of ["cursor", "copilot", "codex", "claude"] as const) { + expect(acpxCapabilities(agent, { steering: true, queuedFollowUp: true })).toMatchObject({ steering: false, queuedFollowUp: false }); + } + }); it.each([ ["codex", "available"], ["grok", "available"], @@ -86,7 +94,7 @@ describe("ACPX driver profile", () => { }), ).toMatchObject({ ok: false, - issues: [{ path: "agent", code: "invalid_agent" }], + issues: [{ path: "agent", code: "qualification_pending" }], }); expect( validateAcpxDriverConfig({ diff --git a/packages/paperclip-runner/src/drivers/acpx/driver-profile.ts b/packages/paperclip-runner/src/drivers/acpx/driver-profile.ts index 559be9b349..259f547b17 100644 --- a/packages/paperclip-runner/src/drivers/acpx/driver-profile.ts +++ b/packages/paperclip-runner/src/drivers/acpx/driver-profile.ts @@ -3,7 +3,7 @@ import type { HarnessDriverDescriptor, } from "../../contracts/harness-driver.js"; import type { NativeAcpxPermissionMode } from "../../contracts/native-execution.js"; -import type { NativeSessionCapabilities } from "../../contracts/types.js"; +import type { NativeSessionCapabilities, NativeTurnControlCapabilities } from "../../contracts/types.js"; import { providerFamilyCapabilities } from "../../provider-events.js"; import { ACPX_DRIVER_KIND, @@ -12,7 +12,9 @@ import { type QualifiedAcpxAgent, } from "./qualified-profiles.js"; -const ACPX_AGENTS = ["claude", "codex", "grok"] as const; +import { ACPX_CAPABILITY_PROFILES } from "./capability-profiles.js"; + +const ACPX_AGENTS = ["claude", "codex", "grok", "pi", "cursor", "copilot"] as const; const ACPX_PERMISSION_MODES = [ "approve-all", "approve-paperclip", @@ -29,30 +31,34 @@ export interface ValidatedAcpxDriverConfig extends Record { export function acpxCapabilities( agent: QualifiedAcpxAgent, + negotiated?: NativeTurnControlCapabilities | null, ): NativeSessionCapabilities { + const controls = agent === "pi" ? negotiated : null; + const profile = ACPX_CAPABILITY_PROFILES[agent]; return { - resume: true, + resume: profile.recovery === "session-load", typedEvents: true, typedEventFamilies: providerFamilyCapabilities({ - plan: agent === "pi" ? "unsupported" : "available", + plan: profile.plans === "semantic-only" ? "unsupported" : "available", tool_execution: "available", model_identity: "available", review: agent === "grok" ? "unsupported" : "available", provider_notice: agent === "grok" ? "unsupported" : "available", artifact: "policy_disabled", }), - steering: false, + steering: controls?.steering === true, + queuedFollowUp: controls?.queuedFollowUp === true, interruption: true, structuredResult: true, read: true, reconciliation: true, - usage: agent !== "grok", + usage: profile.usage === "reported", dynamicTools: true, - runtimeRequestResolution: true, + runtimeRequestResolution: profile.permissions === "interactive" || profile.questions === "form", runtimeRequestHandoff: true, goals: false, threadLineage: false, - unsupported: ["steering", "goals", "threadLineage"], + unsupported: [...(controls?.steering ? [] : ["steering"]), "goals", "threadLineage"], }; } @@ -96,9 +102,12 @@ export function validateAcpxDriverConfig( return invalid( "agent", "invalid_agent", - "ACPX agent must be claude, codex, or grok.", + "ACPX agent must be claude, codex, grok, cursor, copilot, or pi.", ); } + if (ACPX_CAPABILITY_PROFILES[agent].qualification !== "qualified") { + return invalid("agent", "qualification_pending", `${ACPX_CAPABILITY_PROFILES[agent].displayName} requires local and Daytona qualification before use.`); + } const model = text(config.model); try { resolveQualifiedAcpxProfile(agent, model); @@ -144,10 +153,7 @@ function isPermissionMode(value: string): value is NativeAcpxPermissionMode { } function displayAgent(agent: QualifiedAcpxAgent): string { - if (agent === "grok") return "Grok Build"; - if (agent === "pi") return "Pi"; - if (agent === "claude") return "Claude"; - return "Codex"; + return ACPX_CAPABILITY_PROFILES[agent].displayName; } function record(value: unknown): Record | null { diff --git a/packages/paperclip-runner/src/drivers/acpx/environment.test.ts b/packages/paperclip-runner/src/drivers/acpx/environment.test.ts index 03e7d00aae..d1e749a519 100644 --- a/packages/paperclip-runner/src/drivers/acpx/environment.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/environment.test.ts @@ -1,6 +1,8 @@ -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; -import { createSanitizedAcpxSpawnInput } from "./environment.js"; +afterEach(() => vi.unstubAllEnvs()); + +import { ACPX_CREDENTIAL_BINDING_ENV, createAcpxCredentialBinding, createAcpxSidecarHostEnvironment, createSanitizedAcpxSpawnInput } from "./environment.js"; describe("ACPX launch environment", () => { it("projects only the selected agent's credentials and runtime allowlist", () => { @@ -45,6 +47,64 @@ describe("ACPX launch environment", () => { expect(Object.isFrozen(codex.env)).toBe(true); }); + it.each([ + ["pi", "OPENROUTER_API_KEY"], ["cursor", "CURSOR_API_KEY"], + ["cursor", "CURSOR_AUTH_TOKEN"], ["copilot", "COPILOT_GITHUB_TOKEN"], + ] as const)("requires an explicit %s credential binding for %s", (agent, name) => { + vi.stubEnv(name, "ambient-secret"); + expect(createSanitizedAcpxSpawnInput(undefined, agent).env[name]).toBeUndefined(); + expect(createSanitizedAcpxSpawnInput({ [name]: "bound-secret" }, agent).env[name]).toBe("bound-secret"); + }); + + it("drops alternate credentials, provider injection and configuration overrides", () => { + const source = { + CURSOR_API_KEY: "cursor-key", CURSOR_AUTH_TOKEN: "cursor-token", COPILOT_GITHUB_TOKEN: "copilot-key", + GH_TOKEN: "gh-key", GITHUB_TOKEN: "github-key", COPILOT_PROVIDER_BASE_URL: "https://unapproved.invalid", + COPILOT_MODEL: "ambient-model", CURSOR_CONFIG_DIR: "/ambient/cursor", PI_CODING_AGENT_DIR: "/ambient/pi", + PAPERCLIP_PI_ENTRYPOINT: "/unverified/pi.js", NODE_OPTIONS: "--import=/unverified/hook.js", + COPILOT_PKG_CACHE_HOME: "/unverified/cache", COPILOT_ALLOW_ALL: "true", + }; + expect(createSanitizedAcpxSpawnInput(source, "cursor").env).toEqual({ CURSOR_API_KEY: "cursor-key", CURSOR_AUTH_TOKEN: "cursor-token" }); + expect(createSanitizedAcpxSpawnInput(source, "copilot").env).toEqual({ COPILOT_GITHUB_TOKEN: "copilot-key" }); + expect(createSanitizedAcpxSpawnInput(source, "pi").env).toEqual({}); + }); + + it.each([ + ["pi", "OPENROUTER_API_KEY"], ["cursor", "CURSOR_API_KEY"], + ["cursor", "CURSOR_AUTH_TOKEN"], ["copilot", "COPILOT_GITHUB_TOKEN"], + ] as const)("admits only session-bound %s sidecar credentials for %s", (agent, name) => { + const source = { [name]: "task-secret" }; + const binding = createAcpxCredentialBinding(source, agent, "session-1"); + expect(binding).not.toContain("task-secret"); + const explicit = createAcpxSidecarHostEnvironment({ ...source, [ACPX_CREDENTIAL_BINDING_ENV]: binding }, agent, "session-1"); + expect(createSanitizedAcpxSpawnInput(explicit, agent).env).toEqual(source); + expect(explicit[ACPX_CREDENTIAL_BINDING_ENV]).toBeUndefined(); + expect(() => createAcpxSidecarHostEnvironment(source, agent, "session-1")).toThrow("explicit matching session binding"); + expect(() => createAcpxSidecarHostEnvironment({ ...source, [ACPX_CREDENTIAL_BINDING_ENV]: binding }, agent, "stale-session")).toThrow("explicit matching session binding"); + }); + + it("rejects malformed, wrong-provider, duplicate and missing sidecar credential bindings", () => { + const source = { CURSOR_API_KEY: "do-not-include-in-errors" }; + const valid = JSON.parse(createAcpxCredentialBinding(source, "cursor", "session-1")!); + for (const binding of ["invalid-json", JSON.stringify({ ...valid, agent: "copilot" }), + JSON.stringify({ ...valid, names: ["COPILOT_GITHUB_TOKEN"] }), + JSON.stringify({ ...valid, names: ["CURSOR_API_KEY", "CURSOR_API_KEY"] }), + JSON.stringify({ ...valid, names: [] }), JSON.stringify({ ...valid, extra: true })]) { + expect(() => createAcpxSidecarHostEnvironment({ ...source, [ACPX_CREDENTIAL_BINDING_ENV]: binding }, "cursor", "session-1")) + .toThrow(/^Candidate ACPX credentials require an explicit matching session binding$/); + } + expect(() => createAcpxSidecarHostEnvironment({ [ACPX_CREDENTIAL_BINDING_ENV]: JSON.stringify(valid) }, "cursor", "session-1")).toThrow("explicit matching session binding"); + expect(() => createAcpxSidecarHostEnvironment({ ...source, CURSOR_AUTH_TOKEN: "unbound", [ACPX_CREDENTIAL_BINDING_ENV]: JSON.stringify(valid) }, "cursor", "session-1")).toThrow("explicit matching session binding"); + }); + + it("does not reinterpret legacy profile credential environments", () => { + const source = { ANTHROPIC_API_KEY: "legacy", OPENAI_API_KEY: "legacy", [ACPX_CREDENTIAL_BINDING_ENV]: "irrelevant" }; + for (const agent of ["claude", "codex"] as const) { + expect(createAcpxCredentialBinding(source, agent, "session-1")).toBeUndefined(); + expect(createAcpxSidecarHostEnvironment(source, agent, "session-1")).toBe(source); + } + }); + it("rejects unsafe or unbounded retained values", () => { expect(() => createSanitizedAcpxSpawnInput({ PATH: "bad\0path" }, "codex"), diff --git a/packages/paperclip-runner/src/drivers/acpx/environment.ts b/packages/paperclip-runner/src/drivers/acpx/environment.ts index 0e1ff50b80..a78fdcbe26 100644 --- a/packages/paperclip-runner/src/drivers/acpx/environment.ts +++ b/packages/paperclip-runner/src/drivers/acpx/environment.ts @@ -1,5 +1,67 @@ import type { QualifiedAcpxAgent } from "./qualified-profiles.js"; +export const ACPX_CREDENTIAL_BINDING_ENV = "PAPERCLIP_ACPX_CREDENTIAL_BINDING"; +export const ACPX_CREDENTIAL_NAMES: Readonly> = { + grok: ["XAI_API_KEY"], + pi: ["OPENROUTER_API_KEY"], + cursor: ["CURSOR_API_KEY", "CURSOR_AUTH_TOKEN"], + copilot: ["COPILOT_GITHUB_TOKEN"], + claude: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"], + codex: ["OPENAI_API_KEY", "CODEX_API_KEY"], +}; +const isCandidate = (agent: QualifiedAcpxAgent) => agent === "pi" || agent === "cursor" || agent === "copilot"; + +/** Mint only at the controller's explicit task-environment boundary, never by copying a marker. */ +export function createAcpxCredentialBinding( + environment: NodeJS.ProcessEnv | undefined, + agent: QualifiedAcpxAgent, + sessionId: string, +): string | undefined { + if (!isCandidate(agent)) return undefined; + return JSON.stringify({ + schema: "paperclip.acpx_credential_binding.v1", agent, sessionId, + names: ACPX_CREDENTIAL_NAMES[agent].filter(name => environment !== undefined + && Object.hasOwn(environment, name) && Boolean(environment[name]?.trim())), + }); +} + +/** process.env at the sidecar is not itself proof of a task credential binding. */ +export function createAcpxSidecarHostEnvironment( + environment: NodeJS.ProcessEnv, + agent: QualifiedAcpxAgent, + sessionId: string, +): NodeJS.ProcessEnv { + if (!isCandidate(agent)) return environment; + const invalid = () => new Error("Candidate ACPX credentials require an explicit matching session binding"); + const raw = environment[ACPX_CREDENTIAL_BINDING_ENV]; + let names: string[] = []; + if (raw !== undefined) { + if (Buffer.byteLength(raw) > 4_096) throw invalid(); + let binding: unknown; + try { binding = JSON.parse(raw); } catch { throw invalid(); } + if (binding === null || typeof binding !== "object" || Array.isArray(binding)) throw invalid(); + const value = binding as Record; + if (Object.keys(value).sort().join(",") !== "agent,names,schema,sessionId" + || value.schema !== "paperclip.acpx_credential_binding.v1" || value.agent !== agent + || value.sessionId !== sessionId || !Array.isArray(value.names) + || value.names.length > ACPX_CREDENTIAL_NAMES[agent].length + || value.names.some(name => typeof name !== "string" || !ACPX_CREDENTIAL_NAMES[agent].includes(name)) + || new Set(value.names).size !== value.names.length) throw invalid(); + names = value.names as string[]; + } + const result = { ...environment }; + delete result[ACPX_CREDENTIAL_BINDING_ENV]; + for (const name of ACPX_CREDENTIAL_NAMES[agent]) { + if (names.includes(name)) { + if (!Object.hasOwn(environment, name) || !environment[name]?.trim()) throw invalid(); + } else { + if (environment[name]?.trim()) throw invalid(); + delete result[name]; + } + } + return result; +} + declare const sanitizedAcpxSpawnInputBrand: unique symbol; /** @@ -23,18 +85,9 @@ export function createSanitizedAcpxSpawnInput( agent: QualifiedAcpxAgent, ): SanitizedAcpxSpawnInput { const source = environment ?? process.env; + const candidate = isCandidate(agent); const result: NodeJS.ProcessEnv = {}; - const credentialNames = - agent === "grok" - ? ["XAI_API_KEY"] - : agent === "pi" - ? ["OPENROUTER_API_KEY"] - : agent === "claude" - ? ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"] - : [ - "OPENAI_API_KEY", - "CODEX_API_KEY", - ]; + const credentialNames = ACPX_CREDENTIAL_NAMES[agent]; const allowed = new Set([ "PATH", "LANG", @@ -62,6 +115,9 @@ export function createSanitizedAcpxSpawnInput( let retainedBytes = 0; for (const [key, value] of Object.entries(source)) { if (typeof value !== "string") continue; + // Candidate credentials must be explicitly bound to this run. Locale and + // transport settings may come from the host; login state never does. + if (candidate && environment === undefined && credentialNames.includes(key)) continue; if (!allowed.has(key) && !/^LC_[A-Z0-9_]{1,32}$/.test(key)) continue; if (key.includes("\0") || value.includes("\0")) { throw new Error("ACPX environment contains a null byte"); diff --git a/packages/paperclip-runner/src/drivers/acpx/generated-sidecar-contract.ts b/packages/paperclip-runner/src/drivers/acpx/generated-sidecar-contract.ts index 9f7ac89235..ed4bfbae44 100644 --- a/packages/paperclip-runner/src/drivers/acpx/generated-sidecar-contract.ts +++ b/packages/paperclip-runner/src/drivers/acpx/generated-sidecar-contract.ts @@ -7,6 +7,7 @@ export const GENERATED_ACPX_SIDECAR_COMMANDS = [ "run.attach", "turn.start", "turn.cancel", + "turn.steer", "permission.resolve", "input.resolve", "tool.resolve", @@ -23,6 +24,7 @@ export type GeneratedAcpxSidecarCommand = export const GENERATED_ACPX_SIDECAR_EVENT_TYPES = [ "runtime.event", + "runtime.rich_event", "runtime.permission_requested", "runtime.input_requested", "runtime.tool_called", diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts index b4a954fb07..237de315b4 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts @@ -1,4 +1,6 @@ import { MAX_ACPX_RUNTIME_EXECUTABLE_BYTES, ACPX_PRIVATE_SNAPSHOT_ENV, createAcpxPrivateSnapshot, type AcpxPrivateSnapshot } from "./private-snapshot.js"; +import { createNativeAcpxDistributionSnapshot, NATIVE_ACPX_BOOTSTRAP_NAME, readNativeAcpxDistributionEntries, type NativeAcpxDistributionInput } from "./native-distribution-integrity.js"; +export type { NativeAcpxDistributionInput } from "./native-distribution-integrity.js"; import { createHash } from "node:crypto"; import { spawn as spawnChildProcess, @@ -407,6 +409,43 @@ export interface VerifiedAcpxInstallation { openCommand(): Promise; } +/** + * Native candidate distribution primitive. This verifies bytes and lifetime + * ownership; it does not qualify or enable a provider profile. The caller must + * source every expected value from its trusted, versioned profile declaration. + */ +export async function verifyNativeAcpxInstallation( + input: NativeAcpxDistributionInput, +): Promise { + const declaration: NativeAcpxDistributionInput = Object.freeze({ + ...input, fixedArguments: Object.freeze([...input.fixedArguments]), + }); + const entries = await readNativeAcpxDistributionEntries(declaration); + return Object.freeze({ + commandDigest: `sha256:${declaration.expectedClosureSha256}`, + agentServerPackageJsonPath: declaration.manifestPath, + agentRuntimePackageJsonPath: null, + async openCommand(): Promise { + const native = await createNativeAcpxDistributionSnapshot(declaration, entries); + const lease = commandLease( + native.snapshot.roots[0]!, NATIVE_ACPX_BOOTSTRAP_NAME, "commonjs", + native.bootstrap, native.commandDirectory, [], 0, "commonjs", [], + null, null, native.snapshot, + ); + return { + spawn(args = [], options = {}, lifetime) { + if (args.length !== 0) throw new Error("Native ACPX distribution accepts only its fixed profile arguments"); + return lease.spawn([], { ...options, env: { + ...(options.env ?? process.env), + PAPERCLIP_ACPX_NATIVE_GUARDED: lifetime === undefined ? "0" : "1", + } }, lifetime); + }, + close: () => lease.close(), + }; + }, + }); +} + export interface VerifiedAcpxCommandLease { /** * Launch with a Linux descriptor-backed entry identity. Callers must not diff --git a/packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.test.ts b/packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.test.ts new file mode 100644 index 0000000000..d140717ae3 --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.test.ts @@ -0,0 +1,233 @@ +import { createHash } from "node:crypto"; +import { once } from "node:events"; +import { chmod, copyFile, mkdtemp, open, readFile, readdir, rm, stat, symlink, writeFile, type FileHandle } from "node:fs/promises"; +import { createServer, type Server } from "node:net"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import type { ChildProcess } from "node:child_process"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { awaitVerifiedAcpxProviderExit, awaitVerifiedAcpxProviderOwnership, verifyNativeAcpxInstallation } from "./installation-integrity.js"; +import { createNativeAcpxDistributionSnapshot, readNativeAcpxDistributionEntries, parseNativeAcpxDistributionEntries, type NativeAcpxDistributionInput, type NativeAcpxDistributionEntry } from "./native-distribution-integrity.js"; + +vi.mock("node:fs/promises", async (importOriginal) => { + const original = await importOriginal(); + return { ...original, rm: vi.fn(original.rm) }; +}); + +const roots: string[] = []; +const hash = (value: string | Buffer) => createHash("sha256").update(value).digest("hex"); +afterEach(async () => { vi.restoreAllMocks(); await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); }); + +async function fixture(options: { node?: boolean; script?: string } = {}): Promise { + const root = await mkdtemp(join(tmpdir(), "native-acpx-test-")); roots.push(root); + const entries: NativeAcpxDistributionEntry[] = []; + if (options.node) { + await copyFile(process.execPath, join(root, "runtime")); await chmod(join(root, "runtime"), 0o700); + const bytes = await readFile(join(root, "runtime")); entries.push({ path: "runtime", sha256: hash(bytes), size: bytes.length, executable: true }); + // Homebrew's test-host Node uses an @rpath libnode dylib. Include that + // dependency in this fixture's closure instead of relying on its old path. + const libraryRoot = join(dirname(process.execPath), "..", "lib"); + for (const name of (await readdir(libraryRoot).catch(() => [] as string[])).filter(name => /^libnode\..*\.dylib$/.test(name))) { + await copyFile(join(libraryRoot, name), join(root, name)); await chmod(join(root, name), 0o600); + const library = await readFile(join(root, name)); entries.push({ path: name, sha256: hash(library), size: library.length, executable: false }); + } + const script = options.script ?? 'console.log("qualified-entry");'; + await writeFile(join(root, "entry.cjs"), script, { mode: 0o600 }); + entries.push({ path: "entry.cjs", sha256: hash(script), size: Buffer.byteLength(script), executable: false }); + } else { + const script = options.script ?? '#!/bin/sh\nprintf "native:%s:%s" "$1" "${COPILOT_PKG_CACHE_HOME:-none}"\n'; + await writeFile(join(root, "runtime"), script, { mode: 0o700 }); + entries.push({ path: "runtime", sha256: hash(script), size: Buffer.byteLength(script), executable: true }); + } + entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); + const manifestPath = join(root, "closure.json"); + await writeFile(manifestPath, JSON.stringify({ entries })); + return { distributionRoot: root, manifestPath, expectedClosureSha256: hash(JSON.stringify(entries)), executable: "runtime", ...(options.node ? { entrypoint: "entry.cjs" } : {}), fixedArguments: options.node ? [] : ["fixed"] }; +} +async function manyFileFixture(sizes: number[]) { + const declaration = await fixture(); + const entries = await readNativeAcpxDistributionEntries(declaration); + for (let index = 0; index < sizes.length; index++) { + const path = `file-${String(index).padStart(2, "0")}`; + const bytes = Buffer.alloc(sizes[index]!, index + 1); + await writeFile(join(declaration.distributionRoot, path), bytes, { mode: 0o600 }); + entries.push({ path, sha256: hash(bytes), size: bytes.length, executable: false }); + } + entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); + await writeFile(declaration.manifestPath, JSON.stringify({ entries })); + return { declaration: { ...declaration, expectedClosureSha256: hash(JSON.stringify(entries)) }, entries }; +} +async function filePrototype(path: string): Promise { + const probe = await open(path, "r"); const prototype = Object.getPrototypeOf(probe) as FileHandle; + await probe.close(); return prototype; +} +function gate() { + let release!: () => void; + const promise = new Promise((resolve) => { release = resolve; }); + return { promise, release }; +} + +async function output(child: ChildProcess): Promise<{ text: string; error: string; code: number | null }> { + let text = ""; let error = ""; + child.stdout!.on("data", value => { text += String(value); }); + child.stderr!.on("data", value => { error += String(value); }); + const [code] = await once(child, "close") as [number | null]; + return { text, error, code }; +} + +describe("native ACPX execution closure", () => { + it("rejects paths, ordering, oversized files and altered manifest pins", () => { + const entry = { path: "runtime", sha256: "a".repeat(64), size: 10, executable: true }; + for (const entries of [[{ ...entry, path: "../escape" }], [{ ...entry, path: "/absolute" }], [entry, entry], [{ ...entry, size: 2 ** 40 }], [{ ...entry, unknown: 1 }]]) { + expect(() => parseNativeAcpxDistributionEntries({ entries }, hash(JSON.stringify(entries)))).toThrow(); + } + expect(() => parseNativeAcpxDistributionEntries({ entries: [entry] }, "b".repeat(64))).toThrow("manifest digest mismatch"); + }); + it("checks every file digest and refuses links at admission", async () => { + const declaration = await fixture(); const installation = await verifyNativeAcpxInstallation(declaration); + await writeFile(join(declaration.distributionRoot, "runtime"), "altered"); + await expect(installation.openCommand()).rejects.toThrow(); + await rm(join(declaration.distributionRoot, "runtime")); + await symlink("closure.json", join(declaration.distributionRoot, "runtime")); + await expect(installation.openCommand()).rejects.toThrow("symbolic link"); + }); + it("launches only fixed arguments from a frozen snapshot after installed files change", async () => { + const declaration = await fixture(); const lease = await (await verifyNativeAcpxInstallation(declaration)).openCommand(); + expect(() => lease.spawn(["--untrusted-override"])).toThrow("fixed profile arguments"); + await writeFile(join(declaration.distributionRoot, "runtime"), "changed after lease"); + const result = await output(lease.spawn()); + expect(result).toEqual({ text: "native:fixed:none", error: "", code: 0 }); + expect(() => lease.spawn()).toThrow("closed"); + await lease.close(); + }); + it("gives packaged executables a fresh private extraction cache each launch", async () => { + const declaration = { ...await fixture(), isolatedCacheEnvironmentName: "COPILOT_PKG_CACHE_HOME" as const }; + const install = await verifyNativeAcpxInstallation(declaration); + const first = await output((await install.openCommand()).spawn([], { env: { COPILOT_PKG_CACHE_HOME: "/ambient/cache" } })); + const second = await output((await install.openCommand()).spawn()); + expect(first.text).toMatch(/^native:fixed:.*paperclip-acpx-native-.*\/state$/); + expect(first.text).not.toBe(second.text); + }); + it("loads a pinned Node entrypoint while rejecting unqualified external modules", async () => { + const declaration = await fixture({ node: true }); + const result = await output((await (await verifyNativeAcpxInstallation(declaration)).openCommand()).spawn()); + expect(result.code, result.error).toBe(0); expect(result.text).toBe("qualified-entry\n"); + const outside = join(declaration.distributionRoot, "outside.cjs"); await writeFile(outside, "module.exports='ambient';"); + const evil = await fixture({ node: true, script: `require(${JSON.stringify(outside)});` }); + const denied = await output((await (await verifyNativeAcpxInstallation(evil)).openCommand()).spawn()); + expect(denied.code).not.toBe(0); expect(denied.error).toContain("escaped its closed distribution"); + }, 30_000); + it("copies concurrently within its descriptor bound and retains canonical manifest order", async () => { + const { declaration, entries } = await manyFileFixture(Array.from({ length: 12 }, (_, index) => 100 + index)); + const prototype = await filePrototype(join(declaration.distributionRoot, "runtime")); + const originalRead = prototype.read; + const hold = gate(); const sizes: number[] = []; let active = 0; let peak = 0; + vi.spyOn(prototype, "read").mockImplementation(async function (this: FileHandle, ...args: any[]): Promise { + sizes.push(args[0].length); active++; peak = Math.max(peak, active); + try { await hold.promise; return await originalRead.apply(this, args as never); } + finally { active--; } + }); + const creating = createNativeAcpxDistributionSnapshot(declaration, entries); + try { + await vi.waitFor(() => expect(sizes).toHaveLength(8)); + expect(sizes.toSorted()).toEqual(Array.from({ length: 8 }, (_, index) => 100 + index)); + } finally { hold.release(); } + const created = await creating; + try { + expect(peak).toBe(8); + expect(Object.keys(created.snapshot.digests).slice(0, entries.length)).toEqual(entries.map(entry => join(created.snapshot.roots[0]!, entry.path))); + for (const entry of entries) expect(hash(await readFile(join(created.snapshot.roots[0]!, entry.path)))).toBe(entry.sha256); + } finally { await created.commandDirectory.close(); await created.snapshot.close(); } + }); + it("bounds simultaneous buffers and gives an oversized admitted file exclusive capacity", async () => { + const mib = 1024 * 1024; + const { declaration, entries } = await manyFileFixture([17 * mib, 17 * mib, 33 * mib]); + const prototype = await filePrototype(join(declaration.distributionRoot, "runtime")); + const originalRead = prototype.read; + const retained = new Map(); const observed: number[] = []; + vi.spyOn(prototype, "read").mockImplementation(async function (this: FileHandle, ...args: any[]): Promise { + if (!retained.has(this)) { + const file = this; const originalClose = file.close.bind(file); + file.close = async () => { try { await originalClose(); } finally { retained.delete(file); } }; + } + retained.set(this, args[0].length); + const total = [...retained.values()].reduce((sum, size) => sum + size, 0); observed.push(total); + expect(total).toBeLessThanOrEqual(Math.max(32 * mib, args[0].length)); + if (args[0].length > 32 * mib) expect(retained.size).toBe(1); + return originalRead.apply(this, args as never); + }); + const created = await createNativeAcpxDistributionSnapshot(declaration, entries); + try { expect(Math.max(...observed)).toBe(33 * mib); } + finally { await created.commandDirectory.close(); await created.snapshot.close(); } + }); + it("drains every admitted copy before failed-snapshot cleanup and schedules no later batch", async () => { + const { declaration, entries } = await manyFileFixture(Array.from({ length: 10 }, (_, index) => 91 + index)); + await writeFile(join(declaration.distributionRoot, "file-00"), Buffer.alloc(91, 99)); + const prototype = await filePrototype(join(declaration.distributionRoot, "runtime")); + const originalRead = prototype.read; + const hold = gate(); const entered = gate(); const invalidClosed = gate(); + const readSizes: number[] = []; const removalStart = vi.mocked(rm).mock.calls.length; + vi.spyOn(prototype, "read").mockImplementation(async function (this: FileHandle, ...args: any[]): Promise { + const size = args[0].length; readSizes.push(size); + if (size === 91) { + const originalClose = this.close.bind(this); + this.close = async () => { await originalClose(); invalidClosed.release(); }; + await entered.promise; + } + if (size === 92) { entered.release(); await hold.promise; } + return originalRead.apply(this, args as never); + }); + const creating = createNativeAcpxDistributionSnapshot(declaration, entries); + let settled = false; + void creating.then(() => { settled = true; }, () => { settled = true; }); + try { + await entered.promise; await invalidClosed.promise; + expect(settled).toBe(false); + } finally { hold.release(); } + await expect(creating).rejects.toThrow("digest mismatch: file-00"); + expect(readSizes).not.toContain(99); + const removals = vi.mocked(rm).mock.calls.slice(removalStart).map(([path]) => String(path)).filter(path => /paperclip-acpx-native-/.test(path)); + expect(removals).toHaveLength(1); + await new Promise(resolve => setImmediate(resolve)); + await expect(stat(removals[0]!)).rejects.toMatchObject({ code: "ENOENT" }); + }); + it("rejects a source mutation while another file is being copied", async () => { + const { declaration, entries } = await manyFileFixture([101, 102]); + const prototype = await filePrototype(join(declaration.distributionRoot, "runtime")); + const originalRead = prototype.read; const entered = gate(); const hold = gate(); + vi.spyOn(prototype, "read").mockImplementation(async function (this: FileHandle, ...args: any[]): Promise { + if (args[0].length === 101) { entered.release(); await hold.promise; } + return originalRead.apply(this, args as never); + }); + const creating = createNativeAcpxDistributionSnapshot(declaration, entries); + const rejected = expect(creating).rejects.toThrow("changed while read"); + try { + await entered.promise; + await writeFile(join(declaration.distributionRoot, "file-00"), Buffer.alloc(101, 7)); + } finally { hold.release(); } + await rejected; + }); + it("uses the existing guardian ownership and provider-exit proof for native children", async () => { + const declaration = await fixture({ script: '#!/bin/sh\nprintf "ready"\nwhile :; do sleep 1; done\n' }); + const fences = await Promise.all([listen(), listen()]); + const fds = fences.map(server => (server as Server & { _handle?: { fd?: number } })._handle!.fd!); + const owners: number[] = []; + const child = (await (await verifyNativeAcpxInstallation(declaration)).openCommand()).spawn([], {}, { + credentialFenceFds: [fds[0]!, fds[1]!], activateCredentialFenceOwner: async pid => { owners.push(pid); }, + }); + let stderr = ""; child.stderr!.on("data", value => { stderr += String(value); }); + try { + await awaitVerifiedAcpxProviderOwnership(child); + expect(owners).toEqual([child.pid]); + const [chunk] = await once(child.stdout!, "data"); expect(String(chunk), stderr).toBe("ready"); + const exited = once(child, "exit"); child.kill(); await exited; + await awaitVerifiedAcpxProviderExit(child); + } finally { + child.kill(); await Promise.all(fences.map(server => new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())))); + } + }, 15_000); +}); + +async function listen(): Promise { + const server = createServer(); server.listen(0, "127.0.0.1"); await once(server, "listening"); return server; +} diff --git a/packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.ts b/packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.ts new file mode 100644 index 0000000000..d8afb68cb7 --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.ts @@ -0,0 +1,200 @@ +import { createHash } from "node:crypto"; +import { constants } from "node:fs"; +import { chmod, lstat, mkdir, mkdtemp, open, realpath, rm, writeFile, type FileHandle } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, isAbsolute, join, relative, resolve } from "node:path"; +import type { AcpxPrivateSnapshot } from "./private-snapshot.js"; + +export interface NativeAcpxDistributionEntry { path: string; sha256: string; size: number; executable: boolean } +export interface NativeAcpxDistributionInput { + distributionRoot: string; + manifestPath: string; + /** Trusted profile pin, never taken from the installed manifest itself. */ + expectedClosureSha256: string; + executable: string; + /** When present, execute this JS entry with the distribution's bundled Node. */ + entrypoint?: string; + fixedArguments: readonly string[]; + isolatedCacheEnvironmentName?: "COPILOT_PKG_CACHE_HOME"; +} +export interface NativeAcpxDistributionSnapshot { + snapshot: AcpxPrivateSnapshot; + commandDirectory: FileHandle; + bootstrap: Buffer; +} + +const MAX_NATIVE_TREE_BYTES = 1024 * 1024 * 1024; +const MAX_NATIVE_FILE_BYTES = 384 * 1024 * 1024; +const MAX_NATIVE_MANIFEST_BYTES = 4 * 1024 * 1024; +// Bound both file descriptors and buffers. A single larger admitted file runs +// alone and remains subject to MAX_NATIVE_FILE_BYTES. +const NATIVE_COPY_CONCURRENCY = 8; +const NATIVE_COPY_BUFFER_BYTES = 32 * 1024 * 1024; +const BOOTSTRAP = ".paperclip-native-entry.cjs"; +const GUARD = ".paperclip-native-module-guard.cjs"; +export const NATIVE_ACPX_BOOTSTRAP_NAME = BOOTSTRAP; +const sha256 = (value: string | Buffer) => createHash("sha256").update(value).digest("hex"); +const validPath = (value: unknown): value is string => typeof value === "string" && value.length > 0 && value.length <= 4_096 && !isAbsolute(value) && !/[\u0000-\u001f\u007f\\]/.test(value) && value.split("/").every(part => part.length > 0 && part !== "." && part !== ".."); + +/** Parse a closed, canonical execution inventory before opening any file in it. */ +export function parseNativeAcpxDistributionEntries(value: unknown, expectedSha256: string): NativeAcpxDistributionEntry[] { + if (!/^[a-f0-9]{64}$/.test(expectedSha256)) throw new Error("Native ACPX closure pin is invalid"); + const entries = value !== null && typeof value === "object" ? (value as { entries?: unknown }).entries : undefined; + if (!Array.isArray(entries) || entries.length === 0 || entries.length > 30_000) throw new Error("Native ACPX closure inventory is invalid"); + let total = 0; + let previous = ""; + const parsed = entries.map(raw => { + if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Native ACPX closure entry is invalid"); + const entry = raw as Record; + if (Object.keys(entry).some(key => !["path", "sha256", "size", "executable"].includes(key)) || !validPath(entry.path) || entry.path <= previous || entry.path === BOOTSTRAP || entry.path === GUARD || entry.path === "manifest.json" || entry.path.startsWith(".paperclip-")) throw new Error("Native ACPX closure paths must be safe, unique and sorted"); + if (typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(entry.sha256) || !Number.isSafeInteger(entry.size) || Number(entry.size) < 0 || Number(entry.size) > MAX_NATIVE_FILE_BYTES || typeof entry.executable !== "boolean") throw new Error("Native ACPX closure file metadata is invalid"); + previous = entry.path; + total += Number(entry.size); + if (total > MAX_NATIVE_TREE_BYTES) throw new Error("Native ACPX closure exceeds its byte bound"); + return { path: entry.path, sha256: entry.sha256, size: Number(entry.size), executable: entry.executable }; + }); + if (sha256(JSON.stringify(parsed)) !== expectedSha256) throw new Error("Native ACPX closure manifest digest mismatch"); + return parsed; +} + +export async function readNativeAcpxDistributionEntries(input: NativeAcpxDistributionInput): Promise { + if (!validPath(input.executable) || (input.entrypoint !== undefined && !validPath(input.entrypoint)) || input.fixedArguments.length > 128 || input.fixedArguments.some(arg => typeof arg !== "string" || arg.length > 16_384 || arg.includes("\0")) || (input.isolatedCacheEnvironmentName !== undefined && input.isolatedCacheEnvironmentName !== "COPILOT_PKG_CACHE_HOME")) throw new Error("Native ACPX launch declaration is invalid"); + const file = await open(input.manifestPath, constants.O_RDONLY | constants.O_NOFOLLOW); + try { + const before = await file.stat({ bigint: true }); + if (!before.isFile() || before.size < 1n || before.size > BigInt(MAX_NATIVE_MANIFEST_BYTES)) throw new Error("Native ACPX manifest must be a bounded regular file"); + const bytes = await file.readFile(); + if (!same(before, await file.stat({ bigint: true })) || bytes.length !== Number(before.size)) throw new Error("Native ACPX manifest changed while read"); + const entries = parseNativeAcpxDistributionEntries(JSON.parse(bytes.toString("utf8")), input.expectedClosureSha256); + if (!entries.some(entry => entry.path === input.executable && entry.executable && entry.size > 0) || (input.entrypoint !== undefined && !entries.some(entry => entry.path === input.entrypoint && entry.size > 0))) throw new Error("Native ACPX executable or entrypoint is absent from its closure"); + return entries; + } finally { await file.close(); } +} + +/** + * Freeze only manifest-admitted bytes. Native trees deliberately have a separate + * bound; the tighter JavaScript/npm snapshot limits remain unchanged. + */ +export async function createNativeAcpxDistributionSnapshot(input: NativeAcpxDistributionInput, entries: NativeAcpxDistributionEntry[]): Promise { + // Callers cannot substitute entries between manifest validation and copying. + entries = parseNativeAcpxDistributionEntries({ entries }, input.expectedClosureSha256); + if (process.platform !== "linux" && process.platform !== "darwin") throw new Error("Native ACPX snapshots require Linux or macOS"); + const source = await realpath(input.distributionRoot); + const rootBefore = await lstat(source, { bigint: true }); + if (!rootBefore.isDirectory() || rootBefore.isSymbolicLink()) throw new Error("Native ACPX distribution must be a directory"); + const heldRoot = await open(source, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_DIRECTORY); + const privateRoot = await realpath(await mkdtemp(join(tmpdir(), "paperclip-acpx-native-"))); + const packageRoot = join(privateRoot, "distribution"); + const cacheRoot = join(privateRoot, "state"); + const directories = new Set([privateRoot, packageRoot]); + const digests: Record = {}; + const close = async () => { + for (const path of directories) await chmod(path, 0o700).catch(() => undefined); + await rm(privateRoot, { recursive: true, force: true }); + }; + let commandDirectory: FileHandle | undefined; + try { + if (!same(rootBefore, await heldRoot.stat({ bigint: true }))) throw new Error("Native ACPX distribution root changed before snapshot"); + await mkdir(packageRoot, { mode: 0o700 }); + if (input.isolatedCacheEnvironmentName) await mkdir(cacheRoot, { mode: 0o700 }); + const copyEntry = async (entry: NativeAcpxDistributionEntry): Promise => { + const path = join(source, ...entry.path.split("/")); + if (await realpath(path) !== path) throw new Error("Native ACPX closure contains a symbolic link"); + const before = await lstat(path, { bigint: true }); + if (!before.isFile() || before.nlink !== 1n || before.size !== BigInt(entry.size) || Boolean(before.mode & 0o111n) !== entry.executable) throw new Error("Native ACPX closure file identity is invalid"); + const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW); + try { + if (!same(before, await file.stat({ bigint: true }))) throw new Error("Native ACPX closure file changed before read"); + const bytes = Buffer.alloc(entry.size); + let offset = 0; + while (offset < bytes.length) { + const read = await file.read(bytes, offset, bytes.length - offset, offset); + if (read.bytesRead === 0) throw new Error("Native ACPX closure file ended during snapshot"); + offset += read.bytesRead; + } + if (!same(before, await file.stat({ bigint: true })) || !same(before, await lstat(path, { bigint: true })) || await realpath(path) !== path) throw new Error("Native ACPX closure file changed while read"); + if (sha256(bytes) !== entry.sha256) throw new Error(`Native ACPX closure file digest mismatch: ${entry.path}`); + const target = join(packageRoot, ...entry.path.split("/")); + await mkdir(dirname(target), { recursive: true, mode: 0o700 }); + let directory = dirname(target); + while (directory !== privateRoot) { directories.add(directory); directory = dirname(directory); } + await writeFile(target, bytes, { mode: entry.executable ? 0o500 : 0o400, flag: "wx" }); + } finally { await file.close(); } + }; + for (let start = 0; start < entries.length;) { + let end = start; + let bytes = 0; + while (end < entries.length && end - start < NATIVE_COPY_CONCURRENCY) { + const size = entries[end]!.size; + if (end > start && bytes + size > NATIVE_COPY_BUFFER_BYTES) break; + bytes += size; + end++; + } + const batch = entries.slice(start, end); + // Never clean the private root while another worker can still write or + // close a descriptor. Stop scheduling new batches after any rejection. + const copied = await Promise.allSettled(batch.map(copyEntry)); + const failure = copied.find((result) => result.status === "rejected"); + if (failure?.status === "rejected") throw failure.reason; + // Worker completion order must not change the module guard or manifest. + for (const entry of batch) digests[join(packageRoot, ...entry.path.split("/"))] = entry.sha256; + start = end; + } + if (!same(rootBefore, await heldRoot.stat({ bigint: true })) || !same(rootBefore, await lstat(source, { bigint: true }))) throw new Error("Native ACPX distribution root changed during snapshot"); + const executable = join(packageRoot, ...input.executable.split("/")); + const args = [...(input.entrypoint === undefined ? [] : ["--require", join(packageRoot, GUARD), join(packageRoot, ...input.entrypoint.split("/"))]), ...input.fixedArguments]; + if (input.entrypoint !== undefined) { + const guard = Buffer.from(nativeModuleGuard(digests)); + await writeFile(join(packageRoot, GUARD), guard, { mode: 0o400, flag: "wx" }); + digests[join(packageRoot, GUARD)] = sha256(guard); + } + const bootstrap = Buffer.from(nativeBootstrap(executable, digests[executable]!, args, input.isolatedCacheEnvironmentName ? { name: input.isolatedCacheEnvironmentName, value: cacheRoot } : undefined)); + await writeFile(join(packageRoot, BOOTSTRAP), bootstrap, { mode: 0o400, flag: "wx" }); + digests[join(packageRoot, BOOTSTRAP)] = sha256(bootstrap); + // executable is null: commandLease's separately-qualified provider runtime + // slot is unused; this distribution is spawned by the trusted bootstrap. + const manifest = Buffer.from(JSON.stringify({ roots: [packageRoot], executable: null, digests })); + const manifestPath = join(privateRoot, "manifest.json"); + await writeFile(manifestPath, manifest, { mode: 0o400, flag: "wx" }); + for (const directory of directories) await chmod(directory, 0o500); + commandDirectory = await open(packageRoot, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_DIRECTORY); + return { commandDirectory, bootstrap, snapshot: { roots: [packageRoot], executable: null, digests, handoff: { path: manifestPath, digest: sha256(manifest) }, close } }; + } catch (error) { + await commandDirectory?.close(); await close(); throw error; + } finally { await heldRoot.close(); } +} + +function nativeBootstrap(executable: string, executableDigest: string, args: string[], cache?: { name: string; value: string }): string { + return [ + 'const {spawn}=require("node:child_process");', + 'const fs=require("node:fs");', + // The commandLease's guarded bootstrap owns FDs 5 (guardian), 6 (exit), + // 7/8 (credential fences). Keep them open in the native process too. + 'const guarded=process.env.PAPERCLIP_ACPX_NATIVE_GUARDED==="1";', + 'const env={...process.env}; delete env.PAPERCLIP_ACPX_NATIVE_GUARDED; delete env.PAPERCLIP_ACPX_PRIVATE_SNAPSHOT; delete env.PAPERCLIP_VERIFIED_RUNTIME_EXECUTABLE;', + 'env.NODE_DISABLE_COMPILE_CACHE="1"; delete env.NODE_COMPILE_CACHE;', + ...(cache ? [`env[${JSON.stringify(cache.name)}]=${JSON.stringify(cache.value)};`] : []), + 'if(guarded)for(const fd of [5,6,7,8])fs.fstatSync(fd);', + `const executable=${JSON.stringify(executable)};const fd=fs.openSync(executable,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW);`, + `if(require("node:crypto").createHash("sha256").update(fs.readFileSync(fd)).digest("hex")!==${JSON.stringify(executableDigest)})throw new Error("Native ACPX executable changed before spawn");`, + 'const stdio=guarded?[0,1,2,5,6,7,8]:[0,1,2]; const childExecutable=process.platform==="linux"?"/proc/self/fd/"+stdio.length:executable;stdio.push(fd);', + `const child=spawn(childExecutable,${JSON.stringify(args)},{env,cwd:process.cwd(),shell:false,detached:false,stdio});fs.closeSync(fd);`, + 'child.once("error",()=>process.exit(1)); child.once("exit",(code,signal)=>process.exit(signal?1:code??1));', + 'for(const signal of ["SIGTERM","SIGINT","SIGHUP"])process.on(signal,()=>child.kill(signal));', + ].join("\n"); +} + +function nativeModuleGuard(digests: Record): string { + return [ + 'const {registerHooks,isBuiltin}=require("node:module");', + 'const {readFileSync,realpathSync}=require("node:fs");', + 'const {fileURLToPath}=require("node:url"); const {createHash}=require("node:crypto");', + `const digests=${JSON.stringify(digests)};`, + 'const verify=url=>{if(!url.startsWith("file:"))throw new Error("Native ACPX module must be a qualified file or builtin");const path=fileURLToPath(url);if(realpathSync(path)!==path||!Object.hasOwn(digests,path))throw new Error("Native ACPX module escaped its closed distribution");const bytes=readFileSync(path);if(createHash("sha256").update(bytes).digest("hex")!==digests[path])throw new Error("Native ACPX module digest mismatch");return bytes;};', + 'registerHooks({resolve(specifier,context,next){const result=next(specifier,context);if(!isBuiltin(specifier)&&!result.url.startsWith("node:"))verify(result.url);return result;},load(url,context,next){if(url.startsWith("node:"))return next(url,context);const bytes=verify(url);if(["module","commonjs","json"].includes(context.format))return {format:context.format,source:bytes,shortCircuit:true};return next(url,context);}});', + ].join("\n"); +} + +function same(a: { dev: bigint; ino: bigint; size: bigint; mtimeNs: bigint; ctimeNs: bigint }, b: typeof a): boolean { + return a.dev === b.dev && a.ino === b.ino && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs; +} diff --git a/packages/paperclip-runner/src/drivers/acpx/profile-extensions.test.ts b/packages/paperclip-runner/src/drivers/acpx/profile-extensions.test.ts new file mode 100644 index 0000000000..ac35ebed0d --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/profile-extensions.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it, vi } from "vitest"; +import { bindAcpxExtensionTurn, validateAcpxExtensionInput, validateAcpxRichEvent } from "./profile-extensions.js"; +import type { CanonicalProviderEvent } from "../../provider-events.js"; + +const event: CanonicalProviderEvent = { eventType: "plan.updated", itemId: "plan-1", payload: { + schema: "paperclip.plan.updated.v1", planId: "plan-1", revision: 1, + steps: [{ stepId: "one", body: "Validate", status: "pending" }], complete: false, syncStatus: "not_applicable", +} }; +const input = { method: "cursor/create_plan", questionSet: { + schema: "paperclip.question_set.v1" as const, description: "complete plan".repeat(1000), + questions: [{ id: "plan-revision", prompt: "Approve?", answerMode: "single_select" as const, required: true, + options: [{ id: "accept", label: "Accept" }, { id: "cancel", label: "Cancel" }] }], +}, resolve: () => ({ outcome: "accepted" }), cancel: () => ({ outcome: "cancelled" }) }; + +describe("ACP extension turn binding", () => { + it("preserves bounded full plans and rejects oversized documents", () => { + expect(() => validateAcpxExtensionInput(input)).not.toThrow(); + expect(() => validateAcpxExtensionInput({ ...input, questionSet: { ...input.questionSet, description: "a".repeat(100001) } })).toThrow(); + }); + it("admits canonical activity but cannot mint a terminal or semantic mutation", () => { + expect(() => validateAcpxRichEvent(event)).not.toThrow(); + expect(() => validateAcpxRichEvent({ ...event, eventType: "turn.completed" } as unknown as CanonicalProviderEvent)).toThrow(); + expect(() => validateAcpxRichEvent({ ...event, payload: { ...event.payload, extra: "unbounded" } })).toThrow(); + }); + it("waits for notification normalization before terminal settlement and preserves order", async () => { + let release!: () => void; + const gate = new Promise(resolve => { release = resolve; }); + const emitted: string[] = []; + const binding = bindAcpxExtensionTurn({ + adapter: { request: async () => ({ events: [event], response: {} }), + notification: async (method) => { if (method === "first") await gate; emitted.push(method); return [event]; } }, + sessionId: "session", active: () => true, waitForInput: vi.fn(), emit: vi.fn(), + }); + binding.onExtensionNotification("first", { sessionId: "session" }); + binding.onExtensionNotification("second", { sessionId: "session" }); + let drained = false; + const drain = binding.drain().then(() => { drained = true; }); + await Promise.resolve(); + expect(drained).toBe(false); + release(); await drain; + expect(emitted).toEqual(["first", "second"]); + }); + it("retains request ID zero and rejects cross-session or stale events", async () => { + let active = true; + const waitForInput = vi.fn(async () => ({ outcome: "accepted" })); + const binding = bindAcpxExtensionTurn({ + adapter: { request: async () => ({ input }), notification: async () => [event] }, + sessionId: "session", active: () => active, waitForInput, emit: vi.fn(), + }); + const context = { requestId: 0, signal: new AbortController().signal }; + await binding.onExtensionRequest("cursor/create_plan", { sessionId: "session" }, context); + expect(waitForInput).toHaveBeenCalledWith(input, context); + await expect(binding.onExtensionRequest("cursor/create_plan", { sessionId: "another" }, context)).rejects.toThrow("mismatch"); + active = false; + binding.onExtensionNotification("event", { sessionId: "session" }); + await expect(binding.drain()).rejects.toThrow("stale turn"); + }); +}); diff --git a/packages/paperclip-runner/src/drivers/acpx/profile-extensions.ts b/packages/paperclip-runner/src/drivers/acpx/profile-extensions.ts new file mode 100644 index 0000000000..070088aab8 --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/profile-extensions.ts @@ -0,0 +1,139 @@ +import type { HarnessRuntimeRequestResolution } from "../../contracts/harness-driver.js"; +import { parsePaperclipQuestionSet, type PaperclipQuestionSet } from "../../contracts/question-set.js"; +import { isCanonicalProviderEventType, type CanonicalProviderEvent } from "../../provider-events.js"; +import { validatePrpEvent } from "../../protocol/replay-contract.js"; +import type { QualifiedAcpxAgent } from "./qualified-profiles.js"; + +export const ACPX_CANONICAL_INPUT_METHODS = [ + "elicitation/create", "cursor/ask_question", "cursor/create_plan", +] as const; +export function isAcpxCanonicalInputMethod(method: string): boolean { + return (ACPX_CANONICAL_INPUT_METHODS as readonly string[]).includes(method); +} + +export interface AcpxExtensionInput { + method: string; + questionSet: PaperclipQuestionSet; + details?: Record; + resolve(resolution: HarnessRuntimeRequestResolution): Record; + cancel(): Record; +} +export type AcpxExtensionRequestResult = + | { input: AcpxExtensionInput } + | { events: CanonicalProviderEvent[]; response: Record }; +export interface AcpxProfileExtensionAdapter { + request(method: string, params: Record): Promise; + notification(method: string, params: Record): Promise; +} +export interface AcpxProfileExtensionContext { + workspacePath: string; + sessionId: string; + turnId: string; +} + +/** Provider branches install their closed, pinned adapters here after qualification research. */ +export function createAcpxProfileExtensionAdapter( + _agent: QualifiedAcpxAgent, + _context: AcpxProfileExtensionContext, +): AcpxProfileExtensionAdapter | null { + return null; +} +export function acpxProfileClientCapabilities(_agent: QualifiedAcpxAgent): Record { + return {}; +} + +/** Reject an oversized approval document; never silently approve a truncated revision. */ +export function validateAcpxExtensionInput(input: AcpxExtensionInput): void { + if (!isAcpxCanonicalInputMethod(input.method)) throw new Error("Unsupported ACP input method"); + parsePaperclipQuestionSet(input.questionSet); + if (Buffer.byteLength(JSON.stringify(input.questionSet)) > 196 * 1024) { + throw new Error("ACP input exceeds its complete-document byte limit"); + } +} + +/** Display-only extension channel cannot mint terminal events, tools, or approvals. */ +export function validateAcpxRichEvent(event: CanonicalProviderEvent): void { + if (!isCanonicalProviderEventType(event.eventType) || event.eventType === "harness.diagnostic" + || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/.test(event.itemId) + || Buffer.byteLength(JSON.stringify(event)) > 240 * 1024) { + throw new Error("Unsupported ACP rich activity event"); + } + if ((event.eventType === "artifact.generated" && event.payload.registered !== false) + || (event.eventType === "plan.updated" && (event.payload.syncStatus !== "not_applicable" + || (event.payload.documentRevision !== undefined && event.payload.documentRevision !== null)))) { + throw new Error("ACP display activity cannot claim a control-plane mutation"); + } + const validation = validatePrpEvent({ + schema: "paperclip.prp.event.v1", + sourceEventId: "validation:1", sourceSeq: 1, sourceInstanceId: "validation", + sourceKind: "runner", runId: "validation", normalizedSessionId: "validation", + turnId: "validation", itemId: event.itemId, eventType: event.eventType, + schemaVersion: 1, priority: 1, emittedAt: "2026-09-28T00:00:00.000Z", payload: event.payload, + }); + if (!validation.ok) throw new Error("ACP rich activity failed its canonical schema"); +} + +export function bindAcpxExtensionTurn(input: { + adapter: AcpxProfileExtensionAdapter | null; + active(): boolean; + sessionId: string; + waitForInput(input: AcpxExtensionInput, context: { requestId: string | number; signal: AbortSignal; responseDelivery?: Promise }): Promise>; + emit(event: CanonicalProviderEvent): void; +}) { + let queue = Promise.resolve(); + let pending = 0; + let failure: unknown; + const enqueue = (work: () => Promise): Promise => { + if (++pending > 64) { + --pending; + failure ??= new Error("ACP extension work queue exceeded its bound"); + return Promise.reject(failure); + } + const job = queue.then(async () => { + if (failure) throw failure; + if (!input.active()) throw new Error("ACP extension belongs to a stale turn"); + return work(); + }).finally(() => { --pending; }); + queue = job.then(() => {}, error => { failure ??= error; }); + return job; + }; + const emit = (events: CanonicalProviderEvent[]) => { + if (!Array.isArray(events) || events.length > 256) throw new Error("ACP extension event batch exceeds its bound"); + if (!input.active()) throw new Error("ACP extension belongs to a stale turn"); + for (const event of events) { + validateAcpxRichEvent(event); + input.emit(event); + } + }; + const assertSession = (params: Record) => { + if (params.sessionId !== input.sessionId) throw new Error("ACP extension session mismatch"); + if (!input.adapter) throw new Error("ACP extension adapter is unavailable"); + }; + return { + async onExtensionRequest(method: string, params: Record, context: { requestId: string | number; signal: AbortSignal; responseDelivery?: Promise }) { + assertSession(params); + if (context.signal.aborted) throw new Error("ACP extension was cancelled"); + const result = await enqueue(() => input.adapter!.request(method, params)); + if (context.signal.aborted || !input.active()) { + if ("input" in result) return result.input.cancel(); + throw new Error("ACP extension was cancelled"); + } + if ("input" in result) { + validateAcpxExtensionInput(result.input); + return input.waitForInput(result.input, context); + } + emit(result.events); + return result.response; + }, + onExtensionNotification(method: string, params: Record) { + void enqueue(async () => { + assertSession(params); + emit(await input.adapter!.notification(method, params)); + }).catch(() => {}); + }, + async drain() { + await queue; + if (failure) throw failure; + }, + }; +} diff --git a/packages/paperclip-runner/src/drivers/acpx/profile-installation.ts b/packages/paperclip-runner/src/drivers/acpx/profile-installation.ts new file mode 100644 index 0000000000..a78aa47c6c --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/profile-installation.ts @@ -0,0 +1,21 @@ +import type { QualifiedAcpxAgent, QualifiedAcpxProfile } from "./qualified-profiles.js"; +import { verifyQualifiedAcpxInstallation, type VerifiedAcpxInstallation } from "./installation-integrity.js"; + +/** Closed build-owned registry. Provider branches add their pinned installations here. */ +export async function verifyAcpxProfileInstallation(profile: QualifiedAcpxProfile): Promise { + if (profile.agent !== "claude" && profile.agent !== "codex" && profile.agent !== "grok") { + throw new Error(`ACPX ${profile.agent} verified candidate distribution is not installed in this build`); + } + return verifyQualifiedAcpxInstallation(profile); +} + +/** Provider policy admission is repeated immediately before each process launch. */ +export async function assertAcpxProfileWorkspace(_agent: QualifiedAcpxAgent, _workspace: string): Promise {} + +/** Candidate branches validate only explicitly bound, sanitized launch credentials. */ +export function assertAcpxProfileEnvironment(_agent: QualifiedAcpxAgent, _environment: Readonly): void {} + +/** Optional provider-specific classification; never changes whether admission succeeded. */ +export function classifyAcpxProfileError(_agent: QualifiedAcpxAgent, _error: unknown): Error | null { + return null; +} diff --git a/packages/paperclip-runner/src/drivers/acpx/provider-assets-root.test.ts b/packages/paperclip-runner/src/drivers/acpx/provider-assets-root.test.ts new file mode 100644 index 0000000000..f6c3cfeb5c --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/provider-assets-root.test.ts @@ -0,0 +1,47 @@ +import { mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; +import { afterEach, expect, it, vi } from "vitest"; +import { resolveRunnerProviderAssetsRoot } from "./provider-assets-root.js"; + +const roots: string[] = []; +afterEach(async () => { vi.unstubAllEnvs(); await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); }); +async function root() { const path = await mkdtemp(join(tmpdir(), "runner-provider-assets-")); roots.push(path); return path; } + +it("uses one fixed provider directory for source, compiled and bundled runner layouts", async () => { + vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT", undefined); + vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST", undefined); + const path = await root(); + for (const relative of ["src/drivers/acpx/cursor-installation.ts", "dist/drivers/acpx/cursor-installation.js", "dist/cli/acpx-runtime-sidecar.cjs"]) { + expect(resolveRunnerProviderAssetsRoot(pathToFileURL(join(path, relative)).href, "cursor")).toMatch(/provider-assets\/cursor$/); + } + expect(() => resolveRunnerProviderAssetsRoot(pathToFileURL(join(path, "workspace/arbitrary.ts")).href, "cursor")).toThrow("verified package layout"); +}); + +it("resolves descriptor sidecars only through the runner-bound canonical package manifest", async () => { + const path = await root(); + await writeFile(join(path, "package.json"), JSON.stringify({ name: "@paperclipai/paperclip-runner" })); + vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT", path); + vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST", join(path, "package.json")); + expect(resolveRunnerProviderAssetsRoot("file:///proc/self/fd/18", "pi")).toMatch(/provider-assets\/pi$/); + await writeFile(join(path, "package.json"), JSON.stringify({ name: "untrusted" })); + expect(() => resolveRunnerProviderAssetsRoot("file:///proc/self/fd/18", "pi")).toThrow("runner package"); + vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT", "relative"); + expect(() => resolveRunnerProviderAssetsRoot("file:///proc/self/fd/18", "pi")).toThrow("normalized absolute"); +}); + +it("rejects external manifests, links, asset escapes and incomplete authority", async () => { + const path = await root(); const outside = await root(); + await writeFile(join(outside, "package.json"), JSON.stringify({ name: "@paperclipai/paperclip-runner" })); + vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT", path); + vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST", join(outside, "package.json")); + expect(() => resolveRunnerProviderAssetsRoot("file:///proc/self/fd/18", "copilot")).toThrow("escapes"); + await writeFile(join(path, "package.json"), JSON.stringify({ name: "@paperclipai/paperclip-runner" })); + vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST", join(path, "package.json")); + await mkdir(join(path, "provider-assets")); + await symlink(outside, join(path, "provider-assets/copilot")); + expect(() => resolveRunnerProviderAssetsRoot("file:///proc/self/fd/18", "copilot")).toThrow("contained"); + vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT", undefined); + expect(() => resolveRunnerProviderAssetsRoot("file:///proc/self/fd/18", "copilot")).toThrow("no bound"); +}); diff --git a/packages/paperclip-runner/src/drivers/acpx/provider-assets-root.ts b/packages/paperclip-runner/src/drivers/acpx/provider-assets-root.ts new file mode 100644 index 0000000000..326740c00c --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/provider-assets-root.ts @@ -0,0 +1,64 @@ +import { constants, closeSync, fstatSync, lstatSync, openSync, readFileSync, realpathSync } from "node:fs"; +import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; +import { fileURLToPath } from "node:url"; + +type NativeProvider = "cursor" | "copilot" | "pi"; +const RUNNER_PACKAGE_NAME = "@paperclipai/paperclip-runner"; + +/** Resolve only runner-owned package assets, including descriptor-loaded sidecars. */ +export function resolveRunnerProviderAssetsRoot(moduleUrl: string, provider: NativeProvider): string { + if (!["cursor", "copilot", "pi"].includes(provider)) throw new Error("Unknown native ACP provider assets"); + const boundRoot = process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT; + const boundManifest = process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST; + let packageRoot: string; + if (boundRoot !== undefined) { + const root = normalizedAbsolute(boundRoot); + const manifest = normalizedAbsolute(boundManifest ?? join(root, "package.json")); + packageRoot = realpathSync(root); + const canonicalManifest = realpathSync(manifest); + if (!inside(packageRoot, canonicalManifest)) throw new Error("Runner provider manifest escapes its bound package root"); + // The authority comes from runnerd's selected provider pack, never provider + // environment. Verify the selected manifest itself before deriving assets. + const fd = openSync(manifest, constants.O_RDONLY | constants.O_NOFOLLOW); + try { + const before = fstatSync(fd, { bigint: true }); + if (!before.isFile() || before.size < 1n || before.size > 64n * 1024n) throw new Error("Runner provider manifest is not a bounded regular file"); + const bytes = readFileSync(fd); + const after = fstatSync(fd, { bigint: true }); + if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs + || bytes.length !== Number(before.size) || realpathSync(manifest) !== canonicalManifest) throw new Error("Runner provider manifest changed during admission"); + const value = JSON.parse(bytes.toString("utf8")) as { name?: unknown }; + if (value?.name !== RUNNER_PACKAGE_NAME) throw new Error("Runner provider manifest does not name the runner package"); + } finally { closeSync(fd); } + packageRoot = dirname(canonicalManifest); + } else { + if (boundManifest !== undefined) throw new Error("Runner provider manifest has no bound package root"); + const url = new URL(moduleUrl); + if (url.protocol !== "file:" || url.search || url.hash) throw new Error("Provider factory is outside a verified package layout"); + if (new RegExp(`/(?:src|dist)/drivers/acpx/${provider}-installation\\.(?:ts|js)$`).test(url.pathname)) packageRoot = fileURLToPath(new URL("../../../", url)); + else if (/\/dist\/cli\/acpx-runtime-sidecar\.(?:cjs|js)$/.test(url.pathname)) packageRoot = fileURLToPath(new URL("../../", url)); + else throw new Error("Provider factory is outside a verified package layout"); + packageRoot = realpathSync(packageRoot); + } + const assets = join(packageRoot, "provider-assets", provider); + // Missing materialization is diagnosed by the installation verifier. Existing + // directory components must not redirect discovery outside this package. + for (const path of [join(packageRoot, "provider-assets"), assets]) { + try { + const stat = lstatSync(path); + if (!stat.isDirectory() || stat.isSymbolicLink() || !inside(packageRoot, realpathSync(path))) throw new Error("Runner provider asset directory is not contained by its package"); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + } + return assets; +} + +function normalizedAbsolute(value: string): string { + if (!isAbsolute(value) || value.includes("\0") || resolve(value) !== value) throw new Error("Runner provider package authority must be a normalized absolute path"); + return value; +} +function inside(root: string, path: string): boolean { + const rel = relative(root, path); + return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel); +} diff --git a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts index 944fe433ac..b7b5f39898 100644 --- a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts +++ b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts @@ -11,7 +11,9 @@ export interface QualifiedAcpxProfile { readonly protocolVersion: typeof ACPX_DRIVER_PROTOCOL_VERSION; readonly acpxVersion: typeof QUALIFIED_ACPX_VERSION; readonly agent: QualifiedAcpxAgent; - readonly agentProfileVersion: 1; + readonly agentProfileVersion: 1 | 2 | 3 | 4 | 5; + readonly qualificationStatus?: "pending"; + readonly modelPolicy?: "explicit-provider-verified"; /** Wire identity: an npm package name or a runner-owned builtin: identifier. */ readonly agentServerPackage: string; readonly agentServerVersion: string; @@ -56,6 +58,28 @@ export const QUALIFIED_ACPX_PROFILES: Readonly< reportedModelId: "openrouter/deepseek/deepseek-v4-flash-0731", permissionPolicy: "interactive", }, + cursor: { + driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION, + acpxVersion: QUALIFIED_ACPX_VERSION, agent: "cursor", agentProfileVersion: 2, + agentServerPackage: "cursor-agent", agentServerVersion: "2026.09.26-dd393fe", + agentRuntimePackage: null, agentRuntimeVersion: null, + commandDigest: "sha256:1157a5d071abbd57ab132f22bace75c65e84cc47a045b0023475488755e14899", + // Authenticated discovery has not established a qualification model. Never + // turn this empty declaration into a default; callers must select an ID. + qualificationModel: "", reportedModelId: "", permissionPolicy: "interactive", + modelPolicy: "explicit-provider-verified", qualificationStatus: "pending", + }, + copilot: { + driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION, + acpxVersion: QUALIFIED_ACPX_VERSION, agent: "copilot", agentProfileVersion: 2, + agentServerPackage: "@github/copilot", agentServerVersion: "1.0.88", + agentRuntimePackage: null, agentRuntimeVersion: null, + commandDigest: "sha256:b18c01603dd0169d233140709cfaa8bf5304a03cf5de78ca4f625f30013e8457", + // Authenticated discovery has not established a qualification model. Never + // turn this empty declaration into a default; callers must select an ID. + qualificationModel: "", reportedModelId: "", permissionPolicy: "interactive", + modelPolicy: "explicit-provider-verified", qualificationStatus: "pending", + }, claude: { driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION, @@ -96,7 +120,7 @@ export function resolveQualifiedAcpxProfile( ): QualifiedAcpxProfile { const profile = QUALIFIED_ACPX_PROFILES[agent]; if (!requestedModel.trim()) throw new Error("ACPX model must not be empty"); - if (agent !== "claude" && agent !== "grok" && requestedModel !== profile.qualificationModel) { + if (agent !== "claude" && agent !== "grok" && profile.modelPolicy !== "explicit-provider-verified" && requestedModel !== profile.qualificationModel) { throw new Error( `ACPX ${agent} profile requires exact model ${profile.qualificationModel}; received ${requestedModel}`, ); diff --git a/packages/paperclip-runner/src/drivers/acpx/recovery-identity.test.ts b/packages/paperclip-runner/src/drivers/acpx/recovery-identity.test.ts index ca4b10e69a..def42e6c4f 100644 --- a/packages/paperclip-runner/src/drivers/acpx/recovery-identity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/recovery-identity.test.ts @@ -76,6 +76,21 @@ describe("ACPX recovery identity", () => { ).not.toThrow(); }); + it("rejects restoration across task execution-policy changes", async () => { + const fixture = await recoveryFixture(); + const readonly = await createAcpxRecoveryBinding({ ...fixture.input, providerPolicy: { readOnly: true } }); + const writable = await createAcpxRecoveryBinding({ ...fixture.input, providerPolicy: { readOnly: false } }); + const record = createAcpxIdentityRecord(fixture.expected, readonly); + expect(readonly.profileSessionKey).not.toBe(writable.profileSessionKey); + expect(readonly.profileDigest).not.toBe(writable.profileDigest); + expect(() => verifyExpectedAcpxIdentity(fixture.expected, writable, record)).toThrow("persisted runtime record"); + const changedRoots = await createAcpxRecoveryBinding({ ...fixture.input, + providerPolicy: { readOnly: true, protectedPaths: ["/another-protected-root"] } }); + expect(changedRoots.profileSessionKey).not.toBe(readonly.profileSessionKey); + await expect(createAcpxRecoveryBinding({ ...fixture.input, + providerPolicy: { readOnly: "yes" as never } })).rejects.toThrow("valid task execution policy"); + }); + it("uses collision-resistant roots and policy-bound provider keys", async () => { const fixture = await recoveryFixture(); const otherSession = await createAcpxRecoveryBinding({ diff --git a/packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts b/packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts index 61d65fe666..d3cef03177 100644 --- a/packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts +++ b/packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts @@ -43,8 +43,10 @@ export async function createAcpxRecoveryBinding(input: { profile: QualifiedAcpxProfile; requestedModel: string; permissionMode: NativeAcpxPermissionMode; + providerPolicy?: { readOnly: boolean; readRoots?: readonly string[]; protectedPaths?: readonly string[] }; }): Promise { validateIdentity(input.normalizedSessionId, "normalized session"); + if (input.providerPolicy !== undefined && typeof input.providerPolicy.readOnly !== "boolean") throw new Error("ACPX recovery requires a valid task execution policy"); if (input.requestedModel !== input.profile.qualificationModel) { throw new Error("ACPX recovery requested an unqualified model"); } @@ -72,6 +74,11 @@ export async function createAcpxRecoveryBinding(input: { qualificationModel: input.profile.qualificationModel, reportedModelId: input.profile.reportedModelId, permissionPolicy: input.profile.permissionPolicy, + ...(input.providerPolicy === undefined ? {} : { executionPolicy: { + readOnly: input.providerPolicy.readOnly, + readRoots: input.providerPolicy.readRoots ?? [], + protectedPaths: input.providerPolicy.protectedPaths ?? [], + } }), }), ); const profileSessionKey = digest( diff --git a/packages/paperclip-runner/src/drivers/acpx/response-delivery.test.ts b/packages/paperclip-runner/src/drivers/acpx/response-delivery.test.ts new file mode 100644 index 0000000000..8e2073ec06 --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/response-delivery.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it, vi } from "vitest"; +import { deliverAcpxResponse, requireAcpxResponseDelivery } from "./response-delivery.js"; + +describe("ACP interaction response delivery", () => { + it("does not acknowledge callback settlement before the provider pipe write", async () => { + const written = Promise.withResolvers(); + const settle = vi.fn(); + let acknowledged = false; + const pending = deliverAcpxResponse(requireAcpxResponseDelivery({ responseDelivery: written.promise }), settle) + .then(value => { acknowledged = true; return value; }); + expect(settle).toHaveBeenCalledOnce(); + await Promise.resolve(); + expect(acknowledged).toBe(false); + written.resolve(); + await expect(pending).resolves.toEqual({ resolved: true }); + }); + + it.each(["cancelled", "disconnected", "write failed"])("does not report delivery when the response is %s", async reason => { + const written = Promise.withResolvers(); + const pending = deliverAcpxResponse(written.promise, () => {}); + const failed = expect(pending).rejects.toThrow(reason); + written.reject(new Error(reason)); + await failed; + }); + + it("requires a transport-owned receipt before presenting an interaction", () => { + expect(() => requireAcpxResponseDelivery({})).toThrow("no bound provider response"); + }); +}); diff --git a/packages/paperclip-runner/src/drivers/acpx/response-delivery.ts b/packages/paperclip-runner/src/drivers/acpx/response-delivery.ts new file mode 100644 index 0000000000..29a4f19660 --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/response-delivery.ts @@ -0,0 +1,17 @@ +/** A local callback result is not evidence that the provider received its reply. */ +export function requireAcpxResponseDelivery(context: { responseDelivery?: Promise }): Promise { + if (!context.responseDelivery || typeof context.responseDelivery.then !== "function") { + throw new Error("ACP interaction has no bound provider response delivery receipt"); + } + return context.responseDelivery; +} + +/** Settle the callback first: the SDK can only write after that callback returns. */ +export async function deliverAcpxResponse( + responseDelivery: Promise, + settle: () => void, +): Promise<{ resolved: true }> { + settle(); + await responseDelivery; + return { resolved: true }; +} diff --git a/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts b/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts index a923ba3876..3a2dd3ece6 100644 --- a/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts @@ -783,7 +783,7 @@ describe("ACPX runtime host", () => { ).rejects.toThrow(); }); - it("rejects Pi before installation or runtime launch", async () => { + it("rejects Pi without an explicit task policy before installation or runtime launch", async () => { const fixture = await hostFixture(); const verifyInstallation = vi.fn(); const openRuntime = vi.fn(); @@ -801,11 +801,37 @@ describe("ACPX runtime host", () => { reportRetainedCleanupFailure: vi.fn(), }, ), - ).rejects.toThrow("descriptor-confined verified launch"); + ).rejects.toThrow("explicit task execution policy"); expect(verifyInstallation).not.toHaveBeenCalled(); expect(openRuntime).not.toHaveBeenCalled(); }); + it("binds Pi task policy independently of permissions and rejects an uninstalled candidate", async () => { + const fixture = await hostFixture(); + const model = "openrouter/deepseek/deepseek-v4-flash-0731"; + const profile = resolveQualifiedAcpxProfile("pi", model); + const openRuntime = vi.fn(async (options: AcpxRuntimePortOpenOptions) => { + expect(options.launchEnvironment.PAPERCLIP_PI_READ_ONLY).toBe("1"); + expect(options.launchEnvironment.PAPERCLIP_PI_SYSTEM_INSTRUCTIONS).toBe("Bound instructions"); + expect(JSON.parse(options.launchEnvironment.PAPERCLIP_PI_READ_ROOTS!)).toEqual([]); + expect(options.permissionMode).toBe("approve-all"); + return runtimePort({ getStatus: async () => ({ models: { currentModelId: model } }) }); + }); + const options = { ...fixture.options, agent: "pi" as const, model, + permissionMode: "approve-all" as const, providerPolicy: { readOnly: true }, systemInstructions: "Bound instructions" }; + await expect(AcpxRuntimeHost.open(options, { openRuntime, reportRetainedCleanupFailure: vi.fn() })) + .rejects.toThrow("verified candidate distribution is not installed"); + expect(openRuntime).not.toHaveBeenCalled(); + const host = await AcpxRuntimeHost.open(options, fixture.dependencies({ + verifyInstallation: async () => ({ commandDigest: profile.commandDigest, + agentServerPackageJsonPath: join(fixture.root, "package.json"), agentRuntimePackageJsonPath: null, + openCommand: async () => ({ spawn: () => { throw new Error("not used"); }, close: async () => {} }), + }), openRuntime, + })); + await host.close({ reason: "policy verified" }); + expect(openRuntime).toHaveBeenCalledOnce(); + }); + it("selects and verifies Claude's qualified reported model", async () => { const fixture = await hostFixture(); let selected = false; @@ -1265,6 +1291,8 @@ describe("ACPX runtime host", () => { const turn = runtimeTurn(); const startTurn = vi.fn(() => turn); const onElicitation = vi.fn(); + const onExtensionRequest = vi.fn(); + const onExtensionNotification = vi.fn(); const runtime = runtimePort({ startTurn }); const host = await AcpxRuntimeHost.open( { @@ -1282,12 +1310,16 @@ describe("ACPX runtime host", () => { text: "Complete the task.", requestId: "turn-1", onElicitation, + onExtensionRequest, + onExtensionNotification, }), ).toBe(turn); expect(startTurn).toHaveBeenCalledWith({ text: "Complete the task.", requestId: "turn-1", onElicitation, + onExtensionRequest, + onExtensionNotification, }); expect(() => host.startTurn({ text: "Concurrent", requestId: "turn-2" }), @@ -1304,6 +1336,34 @@ describe("ACPX runtime host", () => { ); }); + it("clones ephemeral capabilities and fences steering controls to an acknowledged active turn", async () => { + const fixture = await hostFixture(); + const turn = runtimeTurn(); + const runtime = Object.assign(runtimePort({ startTurn: () => turn }), { + steeringCapability: () => ({ steering: true, queuedFollowUp: true }), + steerActiveTurn: vi.fn(async () => undefined), + queueFollowUp: vi.fn(async () => undefined), + }); + const clientCapabilities = { _meta: { fixture: { enabled: true } } }; + let observed: Record | undefined; + const host = await AcpxRuntimeHost.open({ + ...fixture.options, agent: "codex", model: "gpt-5.6-sol", permissionMode: "approve-reads", + environment: { PAPERCLIP_ACPX_CODEX_AUTH_JSON_SECRET: "{}" }, clientCapabilities, + }, fixture.dependencies({ openRuntime: async (options) => { observed = options.clientCapabilities; return runtime; } })); + expect(observed).toEqual(clientCapabilities); + expect(observed).not.toBe(clientCapabilities); + await expect(host.steerActiveTurn("Early")).rejects.toThrow("active turn"); + host.startTurn({ text: "Start", requestId: "turn-1" }); + await expect(host.steerActiveTurn("Wrong turn", "turn-other")).rejects.toThrow("active turn"); + await host.steerActiveTurn("Steer", "turn-1"); + await host.queueFollowUp("Next", "turn-1"); + expect(runtime.steerActiveTurn).toHaveBeenCalledExactlyOnceWith("Steer", "turn-1"); + expect(runtime.queueFollowUp).toHaveBeenCalledExactlyOnceWith("Next", "turn-1"); + expect(runtime.startTurn).toHaveBeenCalledOnce(); + await host.close({ reason: "controls tested" }); + await expect(host.queueFollowUp("Too late", "turn-1")).rejects.toThrow("active turn"); + }); + it("rejects oversized turn inputs before calling the runtime", async () => { const fixture = await hostFixture(); const runtime = runtimePort(); diff --git a/packages/paperclip-runner/src/drivers/acpx/runtime-host.ts b/packages/paperclip-runner/src/drivers/acpx/runtime-host.ts index 20de8c9aac..1f2d83919c 100644 --- a/packages/paperclip-runner/src/drivers/acpx/runtime-host.ts +++ b/packages/paperclip-runner/src/drivers/acpx/runtime-host.ts @@ -1,10 +1,13 @@ +import { dirname, join } from "node:path"; +import { assertAcpxProfileEnvironment, assertAcpxProfileWorkspace, classifyAcpxProfileError, verifyAcpxProfileInstallation } from "./profile-installation.js"; +import { createAcpxRuntimeSkillLease } from "./runtime-skill-lease.js"; import { stageManagedGrokCredential } from "./grok-credentials.js"; -import { join } from "node:path"; import { claudeNativeSkillPrompt } from "./native-skill-prompt.js"; import { nativeMcpLaunchBinding } from "../native-mcp.js"; import type { AcpElicitationHandler, + AcpRuntimeOptions, AcpRuntimeEvent, AcpRuntimeTurnResult, } from "acpx/runtime"; @@ -26,7 +29,6 @@ import { type AcpxProviderLifetimeLease, } from "./codex-credentials.js"; import { - verifyQualifiedAcpxInstallation, type VerifiedAcpxCommandLease, type VerifiedAcpxInstallation, } from "./installation-integrity.js"; @@ -51,6 +53,7 @@ import { import { prepareAcpxRuntimeSandbox, type AcpxRuntimeSandbox, + type AcpxProviderRuntimePolicy, } from "./runtime-sandbox.js"; import type { AcpxExpectedSessionIdentity } from "./sidecar-protocol.js"; @@ -83,6 +86,11 @@ export interface AcpxRuntimeGoalCapability { actions: Array<"set" | "pause" | "resume" | "clear">; } +export interface AcpxRuntimeSteeringCapability { + steering: boolean; + queuedFollowUp: boolean; +} + export interface AcpxRuntimeGoalSnapshot { objective: string; status: "active" | "paused" | "blocked" | "limited" | "complete"; @@ -100,6 +108,9 @@ export interface AcpxRuntimeTurnInput { requestId: string; signal?: AbortSignal; onElicitation?: AcpElicitationHandler; + onPermissionRequest?: AcpRuntimeOptions["onPermissionRequest"]; + onExtensionRequest?: AcpRuntimeOptions["onExtensionRequest"]; + onExtensionNotification?: AcpRuntimeOptions["onExtensionNotification"]; } export interface AcpxRuntimeTurn { @@ -116,6 +127,9 @@ export interface AcpxRuntimePort { identity(): Promise; getStatus(): Promise; setModel?(model: string): Promise; + steeringCapability?(): AcpxRuntimeSteeringCapability | null; + steerActiveTurn?(text: string, requestId: string): Promise; + queueFollowUp?(text: string, requestId: string): Promise; goalCapability?(): AcpxRuntimeGoalCapability | null; goalSnapshot?(): AcpxRuntimeGoalSnapshot | null; controlGoal?( @@ -127,6 +141,8 @@ export interface AcpxRuntimePort { } export interface AcpxRuntimePortOpenOptions { + /** Ephemeral provider extension metadata; mandatory ACP caps remain host-owned. */ + clientCapabilities?: Record; command: VerifiedAcpxCommandLease; /** Replace a consumed launch snapshot after an ephemeral control session. */ refreshConsumedCommand?: () => Promise; @@ -166,7 +182,7 @@ export type AcpxSemanticToolSession = Omit; export interface AcpxRetainedCleanupFailure { resource: - "credential" | "provider_lifetime" | "command" | "runtime" | "tool_bridge"; + "credential" | "provider_lifetime" | "command" | "runtime" | "tool_bridge" | "skills"; attempt: number; error: unknown; } @@ -198,6 +214,10 @@ export interface AcpxRuntimeHostDependencies { } export interface OpenAcpxRuntimeHostOptions { + /** Explicit task execution policy; never inferred from auto-approval. Required for Pi. */ + providerPolicy?: AcpxProviderRuntimePolicy; + /** Never persisted as session options or recovery identity. */ + clientCapabilities?: Record; runtimeDirectory: string; normalizedSessionId: string; workingDirectory: string; @@ -288,10 +308,8 @@ export class AcpxRuntimeHost { dependencies: AcpxRuntimeHostDependencies, ): Promise { options.signal?.throwIfAborted(); - if (options.agent === "pi") { - throw new Error( - "ACPX pi is unavailable until its runtime has descriptor-confined verified launch", - ); + if (options.agent === "pi" && typeof options.providerPolicy?.readOnly !== "boolean") { + throw new Error("Pi admission requires an explicit task execution policy"); } const nativeMcp = nativeMcpLaunchBinding(options.environment); if (nativeMcp?.name === "paperclip") { @@ -311,6 +329,8 @@ export class AcpxRuntimeHost { profile, requestedModel: options.model, permissionMode: options.permissionMode, + ...(["cursor", "copilot", "pi"].includes(options.agent) && options.providerPolicy !== undefined + ? { providerPolicy: options.providerPolicy } : {}), }), dependencies.retainAdmissionCleanup, ); @@ -330,7 +350,7 @@ export class AcpxRuntimeHost { const installation = await runAbortableAdmissionStage( options.signal, () => - (dependencies.verifyInstallation ?? verifyQualifiedAcpxInstallation)( + (dependencies.verifyInstallation ?? verifyAcpxProfileInstallation)( profile, ), dependencies.retainAdmissionCleanup, @@ -389,6 +409,16 @@ export class AcpxRuntimeHost { agent: options.agent, environment: options.environment, tools: options.semanticTools?.tools, + ...(options.providerPolicy === undefined ? {} : { providerPolicy: { + ...options.providerPolicy, + systemInstructions: boundedInstructions(options.systemInstructions), + protectedPaths: [ + ...(options.providerPolicy.protectedPaths ?? []), + ...(installation.agentServerPackageJsonPath === null + ? [] + : [dirname(installation.agentServerPackageJsonPath)]), + ], + } }), }), dependencies.retainAdmissionCleanup, ); @@ -426,6 +456,30 @@ export class AcpxRuntimeHost { dependencies.reportRetainedCleanupFailure(failure), }); } + assertAcpxProfileEnvironment(options.agent, sandbox.launchEnvironment); + let launchEnvironment = sandbox.launchEnvironment; + if (options.agent === "pi") { + const skills = await acquireAbortableAdmissionResource({ + signal: options.signal, + acquire: () => createAcpxRuntimeSkillLease(options.runtimeContext ?? null), + resource: "skills", releaseLate: value => value.close(), + reportFailure: dependencies.reportRetainedCleanupFailure, + }); + const providerLifetime = credential; + credential = { + lifetimeFenceCandidates: providerLifetime.lifetimeFenceCandidates, + lifetimeFenceFds: providerLifetime.lifetimeFenceFds, + activateLifetimeOwner: pid => providerLifetime.activateLifetimeOwner(pid), + async close() { + // Only release assigned files after the provider lifetime quorum closes. + await providerLifetime.close(); + await skills.close(); + }, + }; + launchEnvironment = Object.freeze({ ...sandbox.launchEnvironment, + PAPERCLIP_PI_READ_ROOTS: JSON.stringify([...(options.providerPolicy?.readRoots ?? []), ...skills.readRoots]), + }); + } if (options.agent === "claude" || options.agent === "grok") { // The lifetime lease proves the previous provider has stopped. Refresh // the assigned snapshot before every launch, including durable resume; @@ -467,9 +521,13 @@ export class AcpxRuntimeHost { } runtime = await acquireAbortableAdmissionResource({ signal: options.signal, - acquire: () => { + acquire: async () => { options.assertWorkspaceHeld?.(); + await assertAcpxProfileWorkspace(options.agent, binding.workspacePath); return dependencies.openRuntime({ + ...(options.clientCapabilities === undefined ? {} : { + clientCapabilities: structuredClone(options.clientCapabilities), + }), command: command!, refreshConsumedCommand: commandOwner.refreshConsumedCommand, profile, @@ -480,7 +538,7 @@ export class AcpxRuntimeHost { permissionPolicy: acpxRuntimePermissionPolicy( binding.permissionMode, ), - launchEnvironment: sandbox.launchEnvironment, + launchEnvironment, credentialFenceFds: admittedLifetime.lifetimeFenceFds, activateCredentialFenceOwner: admittedLifetime.activateLifetimeOwner.bind(admittedLifetime), @@ -565,6 +623,7 @@ export class AcpxRuntimeHost { : [], }); } catch (error) { + error = classifyAcpxProfileError(options.agent, error) ?? error; const cleanup = cleanupRuntimeResources( runtime, toolBridge, @@ -637,6 +696,36 @@ export class AcpxRuntimeHost { return this.#runtime.goalCapability?.() ?? null; } + steeringCapability(): AcpxRuntimeSteeringCapability | null { + return this.#runtime.steeringCapability?.() ?? null; + } + + async steerActiveTurn(text: string, turnId?: string): Promise { + const turn = this.#activeTurn; + if (this.#closed || this.#closingStarted || !turn || (turnId !== undefined && turn.requestId !== turnId)) { + throw new Error("ACPX steering does not own an active turn"); + } + if (!this.steeringCapability()?.steering || !this.#runtime.steerActiveTurn) { + throw new Error("ACPX active steering is unavailable"); + } + await turn.promptStarted; + if (this.#activeTurn !== turn || this.#closingStarted) throw new Error("ACPX steering turn expired"); + await this.#runtime.steerActiveTurn(boundedTurnText(text), turn.requestId); + } + + async queueFollowUp(text: string, turnId?: string): Promise { + const turn = this.#activeTurn; + if (this.#closed || this.#closingStarted || !turn || (turnId !== undefined && turn.requestId !== turnId)) { + throw new Error("ACPX follow-up does not own an active turn"); + } + if (!this.steeringCapability()?.queuedFollowUp || !this.#runtime.queueFollowUp) { + throw new Error("ACPX queued follow-up is unavailable"); + } + await turn.promptStarted; + if (this.#activeTurn !== turn || this.#closingStarted) throw new Error("ACPX follow-up turn expired"); + await this.#runtime.queueFollowUp(boundedTurnText(text), turn.requestId); + } + goalSnapshot(): AcpxRuntimeGoalSnapshot | null { return this.#runtime.goalSnapshot?.() ?? null; } @@ -670,6 +759,9 @@ export class AcpxRuntimeHost { requestId, ...(input.signal ? { signal: input.signal } : {}), ...(input.onElicitation ? { onElicitation: input.onElicitation } : {}), + ...(input.onPermissionRequest ? { onPermissionRequest: input.onPermissionRequest } : {}), + ...(input.onExtensionRequest ? { onExtensionRequest: input.onExtensionRequest } : {}), + ...(input.onExtensionNotification ? { onExtensionNotification: input.onExtensionNotification } : {}), }); this.#activeTurn = turn; void turn.result diff --git a/packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.test.ts b/packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.test.ts index e3c7ffd1f3..fb80619a55 100644 --- a/packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.test.ts @@ -85,6 +85,8 @@ describe("ACPX runtime sandbox", () => { ["pi", "OPENROUTER_API_KEY", "pi-home"], ["claude", "ANTHROPIC_API_KEY", "claude-home"], ["codex", "OPENAI_API_KEY", "codex-home"], + ["cursor", "CURSOR_API_KEY", "cursor-home"], + ["copilot", "COPILOT_GITHUB_TOKEN", "copilot-home"], ] as const)( "creates a private %s filesystem and split environment", async (agent, credentialName, homeSuffix) => { @@ -92,10 +94,14 @@ describe("ACPX runtime sandbox", () => { const sandbox = await prepareAcpxRuntimeSandbox({ binding: fixture.binding, agent, + providerPolicy: { readOnly: false, systemInstructions: "bound instructions" }, environment: { PATH: process.env.PATH, [credentialName]: "provider-secret", UNRELATED_SECRET: "must-not-enter", + HOME: "/ambient/home", XDG_CONFIG_HOME: "/ambient/config", CURSOR_CONFIG_DIR: "/ambient/cursor", + COPILOT_HOME: "/ambient/copilot", COPILOT_PKG_CACHE_HOME: "/ambient/extraction-cache", COPILOT_ALLOW_ALL: "true", + PAPERCLIP_PI_READ_ONLY: "1", PAPERCLIP_PI_READ_ROOTS: '["/"]', PAPERCLIP_PI_ENTRYPOINT: "/ambient/pi.js", HTTPS_PROXY: "https://proxy-user:proxy-password@example.test", PAPERCLIP_NATIVE_MCP_URL: "https://mcp.example.test/connect?ticket=secret", @@ -155,7 +161,32 @@ describe("ACPX runtime sandbox", () => { 0o600, ); } + expect(sandbox.protectedPaths).toEqual([dirname(dirname(sandbox.root))]); + expect(sandbox.launchEnvironment.PAPERCLIP_PI_ENTRYPOINT).toBeUndefined(); + expect(sandbox.launchEnvironment.COPILOT_PKG_CACHE_HOME).toBeUndefined(); + if (agent === "cursor") { + expect(sandbox.launchEnvironment).toMatchObject({ + CURSOR_CONFIG_DIR: sandbox.agentHomeDirectory, CURSOR_DATA_DIR: sandbox.dataDirectory, + AGENT_CLI_CREDENTIAL_STORE: "memory", NO_OPEN_BROWSER: "1", NODE_DISABLE_COMPILE_CACHE: "1", + }); + } + if (agent === "copilot") { + expect(sandbox.launchEnvironment).toMatchObject({ + COPILOT_HOME: sandbox.agentHomeDirectory, COPILOT_CACHE_HOME: sandbox.cacheDirectory, + COPILOT_AUTO_UPDATE: "false", COPILOT_ALLOW_ALL: "false", COPILOT_DISABLE_TERMINAL_TITLE: "true", NO_COLOR: "1", + }); + expect(JSON.parse(await readFile(join(sandbox.agentHomeDirectory, "config.json"), "utf8"))).toEqual({ + autoUpdate: false, trustedFolders: [], disableAllHooks: true, memory: false, + ide: { autoConnect: false, openDiffOnEdit: false }, + }); + } if (agent === "pi") { + expect(sandbox.launchEnvironment).toMatchObject({ + PAPERCLIP_PI_READ_ONLY: "0", PAPERCLIP_PI_READ_ROOTS: "[]", + PAPERCLIP_PI_PROTECTED_ROOTS: JSON.stringify(sandbox.protectedPaths), + PAPERCLIP_PI_SYSTEM_INSTRUCTIONS: "bound instructions", + }); + expect(sandbox.persistedEnvironment.PAPERCLIP_PI_SYSTEM_INSTRUCTIONS).toBeUndefined(); await expect( readFile(join(sandbox.agentHomeDirectory, "settings.json"), "utf8"), ).resolves.toContain('"defaultProjectTrust":"never"'); @@ -163,6 +194,24 @@ describe("ACPX runtime sandbox", () => { }, ); + it("requires Pi task policy and validates assigned roots independently from approval mode", async () => { + const fixture = await sandboxFixture("pi"); + await expect(prepareAcpxRuntimeSandbox({ binding: fixture.binding, agent: "pi" })).rejects.toThrow("explicit task execution policy"); + const readRoot = join(fixture.root, "assigned-skills"); + await mkdir(readRoot); + const sandbox = await prepareAcpxRuntimeSandbox({ + binding: { ...fixture.binding, permissionMode: "approve-all" }, agent: "pi", + providerPolicy: { readOnly: true, readRoots: [readRoot], protectedPaths: ["/verified/provider-pack"] }, + }); + expect(sandbox.launchEnvironment.PAPERCLIP_PI_READ_ONLY).toBe("1"); + expect(JSON.parse(sandbox.launchEnvironment.PAPERCLIP_PI_READ_ROOTS!)).toHaveLength(1); + expect(sandbox.protectedPaths).toContain("/verified/provider-pack"); + for (const readRoots of [["/"], ["relative"], ["/missing/assigned-skills"]]) { + await expect(prepareAcpxRuntimeSandbox({ binding: fixture.binding, agent: "pi", providerPolicy: { readOnly: false, readRoots } })).rejects.toThrow(); + } + await expect(prepareAcpxRuntimeSandbox({ binding: fixture.binding, agent: "pi", providerPolicy: { readOnly: false, systemInstructions: "x".repeat(32 * 1024 + 1) } })).rejects.toThrow("bounded size"); + }); + it("re-prepares and re-synchronizes an existing private sandbox", async () => { const fixture = await sandboxFixture("claude"); const first = await prepareAcpxRuntimeSandbox({ @@ -336,7 +385,7 @@ describe("ACPX runtime sandbox", () => { ); }); -async function sandboxFixture(agent: "pi" | "claude" | "codex" | "grok") { +async function sandboxFixture(agent: "pi" | "claude" | "codex" | "grok" | "cursor" | "copilot") { const root = await mkdtemp(join(tmpdir(), "paperclip-acpx-sandbox-")); temporaryDirectories.push(root); const workspace = join(root, "workspace"); @@ -347,6 +396,8 @@ async function sandboxFixture(agent: "pi" | "claude" | "codex" | "grok") { pi: "openrouter/deepseek/deepseek-v4-flash-0731", claude: "claude-sonnet-5", codex: "gpt-5.6-sol", + cursor: "provider-model-cursor", + copilot: "provider-model-copilot", } as const; const binding = await createAcpxRecoveryBinding({ runtimeDirectory, diff --git a/packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.ts b/packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.ts index 44782dae8e..8ecdbc88ed 100644 --- a/packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.ts +++ b/packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.ts @@ -51,6 +51,8 @@ export interface AcpxRuntimeSandbox { workspaceRecordPath: string; launchEnvironment: Readonly; persistedEnvironment: Readonly; + /** The host sandbox must protect these runtime/config paths from tools. */ + protectedPaths: readonly string[]; } /** A recovered workspace pinned until the provider process is admitted. */ @@ -320,6 +322,15 @@ async function closeRecoveryHandles( } } +export interface AcpxProviderRuntimePolicy { + /** Task execution policy, independent of provider permission auto-approval. */ + readOnly: boolean; + /** Assigned, verified skill roots. Never taken from ambient provider config. */ + readRoots?: readonly string[]; + systemInstructions?: string; + protectedPaths?: readonly string[]; +} + /** Prepare the private filesystem and environment visible to an ACPX agent. */ export async function prepareAcpxRuntimeSandbox(input: { binding: AcpxRecoveryBinding; @@ -327,7 +338,12 @@ export async function prepareAcpxRuntimeSandbox(input: { environment?: NodeJS.ProcessEnv; /** Public operations on the runner-owned Paperclip MCP bridge only. */ tools?: readonly Readonly>[]; + providerPolicy?: AcpxProviderRuntimePolicy; }): Promise { + if (input.agent === "pi" && (input.providerPolicy === undefined || typeof input.providerPolicy.readOnly !== "boolean")) { + throw new Error("Pi admission requires an explicit task execution policy"); + } + const policy = await validateProviderPolicy(input.providerPolicy); const expectedRoot = input.binding.runtimeRoot; if (resolve(expectedRoot) !== expectedRoot) { throw new Error("ACPX runtime root must be an absolute normalized path"); @@ -364,6 +380,7 @@ export async function prepareAcpxRuntimeSandbox(input: { join(root, `${input.agent}-home`), root, ); + const protectedPaths = Object.freeze([...new Set([physicalRuntimeDirectory, ...policy.protectedPaths])]); const workspaceRecordPath = join(root, "workspace"); await writePrivateFile( workspaceRecordPath, @@ -415,6 +432,15 @@ export async function prepareAcpxRuntimeSandbox(input: { })}\n`, ); } + if (input.agent === "copilot") { + await writePrivateFile(join(agentHomeDirectory, "config.json"), `${JSON.stringify({ + autoUpdate: false, + trustedFolders: [], + disableAllHooks: true, + memory: false, + ide: { autoConnect: false, openDiffOnEdit: false }, + })}\n`); + } if (input.agent === "codex") { await writePrivateFile( join(agentHomeDirectory, "config.toml"), @@ -455,6 +481,31 @@ export async function prepareAcpxRuntimeSandbox(input: { PI_CODING_AGENT_DIR: agentHomeDirectory, PI_SKIP_VERSION_CHECK: "1", PI_TELEMETRY: "0", + PAPERCLIP_PI_READ_ONLY: policy.readOnly ? "1" : "0", + PAPERCLIP_PI_READ_ROOTS: JSON.stringify(policy.readRoots), + PAPERCLIP_PI_PROTECTED_ROOTS: JSON.stringify(protectedPaths), + PAPERCLIP_PI_SYSTEM_INSTRUCTIONS: policy.systemInstructions, + } + : {}), + ...(input.agent === "cursor" + ? { + CURSOR_CONFIG_DIR: agentHomeDirectory, + CURSOR_DATA_DIR: dataDirectory, + AGENT_CLI_CREDENTIAL_STORE: "memory", + NO_OPEN_BROWSER: "1", + NODE_DISABLE_COMPILE_CACHE: "1", + } + : {}), + ...(input.agent === "copilot" + ? { + COPILOT_HOME: agentHomeDirectory, + COPILOT_CACHE_HOME: cacheDirectory, + COPILOT_AUTO_UPDATE: "false", + COPILOT_ALLOW_ALL: "false", + COPILOT_DISABLE_TERMINAL_TITLE: "true", + NO_COLOR: "1", + // COPILOT_PKG_CACHE_HOME belongs to the per-spawn immutable command + // lease. Reusing a persistent extraction cache invalidates its proof. } : {}), ...(input.agent === "claude" @@ -487,11 +538,34 @@ export async function prepareAcpxRuntimeSandbox(input: { cacheDirectory, agentHomeDirectory, workspaceRecordPath, + protectedPaths, launchEnvironment: Object.freeze({ ...launchEnvironment }), persistedEnvironment: Object.freeze(persistedEnvironment), }; } +async function validateProviderPolicy(policy: AcpxProviderRuntimePolicy | undefined): Promise<{ + readOnly: boolean; readRoots: string[]; protectedPaths: string[]; systemInstructions: string; +}> { + if (policy !== undefined && typeof policy.readOnly !== "boolean") throw new Error("Provider task execution policy is invalid"); + const paths = async (values: readonly string[] | undefined, requireExisting: boolean): Promise => { + if (values !== undefined && (!Array.isArray(values) || values.length > 128)) throw new Error("Provider runtime paths are invalid"); + return await Promise.all((values ?? []).map(async (value) => { + if (typeof value !== "string" || !isAbsolute(value) || value === dirname(value) || /[\0\r\n]/.test(value)) throw new Error("Provider runtime path is invalid"); + const normalized = resolve(value); + return requireExisting ? await realpath(normalized) : normalized; + })); + }; + const systemInstructions = policy?.systemInstructions ?? ""; + if (typeof systemInstructions !== "string" || systemInstructions.includes("\0") || Buffer.byteLength(systemInstructions) > 32 * 1024) throw new Error("Provider runtime instructions exceed their bounded size"); + return { + readOnly: policy?.readOnly ?? true, + readRoots: await paths(policy?.readRoots, true), + protectedPaths: await paths(policy?.protectedPaths, false), + systemInstructions, + }; +} + async function ensurePrivateDirectory( directory: string, physicalParent: string, @@ -617,6 +691,8 @@ function isPersistableEnvironmentName(name: string): boolean { /^(?:HOME|XDG_CONFIG_HOME|XDG_DATA_HOME|XDG_CACHE_HOME)$/.test(name) || /^(?:PAPERCLIP_ACPX_PROFILE|PAPERCLIP_ACPX_ISOLATED_CONTEXT)$/.test(name) || /^(?:PI_CODING_AGENT_DIR|PI_SKIP_VERSION_CHECK|PI_TELEMETRY)$/.test(name) || + /^(?:CURSOR_CONFIG_DIR|CURSOR_DATA_DIR|AGENT_CLI_CREDENTIAL_STORE|NO_OPEN_BROWSER|NODE_DISABLE_COMPILE_CACHE)$/.test(name) || + /^(?:COPILOT_HOME|COPILOT_CACHE_HOME|COPILOT_AUTO_UPDATE|COPILOT_ALLOW_ALL|COPILOT_DISABLE_TERMINAL_TITLE|NO_COLOR)$/.test(name) || /^(?:CLAUDE_CONFIG_DIR|CODEX_HOME|NO_BROWSER|DEFAULT_AUTH_REQUEST)$/.test( name, ) diff --git a/packages/paperclip-runner/src/drivers/acpx/runtime-skill-lease.ts b/packages/paperclip-runner/src/drivers/acpx/runtime-skill-lease.ts new file mode 100644 index 0000000000..aec91cc1ae --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/runtime-skill-lease.ts @@ -0,0 +1,22 @@ +import { mkdtemp, realpath, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import type { NativeRuntimeContextSnapshot } from "../../contracts/runtime-context.js"; +import { materializeNativeRuntimeSkills, releaseMaterializedNativeRuntimeSkills } from "../runtime-context-materializer.js"; + +/** Assigned assets live outside the protected credential/runtime tree. */ +export async function createAcpxRuntimeSkillLease(context: NativeRuntimeContextSnapshot | null) { + const root = await realpath(await mkdtemp(join(tmpdir(), "paperclip-acpx-skills-"))); + const skills = join(root, "skills"); + const close = async () => { + await releaseMaterializedNativeRuntimeSkills(skills); + await rm(root, { recursive: true, force: true }); + }; + try { + await materializeNativeRuntimeSkills(context, skills); + return { readRoots: context?.skills.length ? [await realpath(skills)] : [], close }; + } catch (error) { + await close(); + throw error; + } +} diff --git a/packages/paperclip-runner/src/drivers/acpx/sidecar-protocol.ts b/packages/paperclip-runner/src/drivers/acpx/sidecar-protocol.ts index 72cc629690..0d2abe1273 100644 --- a/packages/paperclip-runner/src/drivers/acpx/sidecar-protocol.ts +++ b/packages/paperclip-runner/src/drivers/acpx/sidecar-protocol.ts @@ -46,6 +46,7 @@ export interface AcpxSidecarOpenParams { model: string; permissionMode: NativeAcpxPermissionMode; permissionModePinned: boolean; + providerPolicy?: { readOnly: boolean }; systemInstructions: string; runtimeContext: NativeRuntimeContextSnapshot | null; tools: readonly Readonly>[]; diff --git a/packages/paperclip-runner/src/drivers/acpx/turn-controls.test.ts b/packages/paperclip-runner/src/drivers/acpx/turn-controls.test.ts new file mode 100644 index 0000000000..cc97869d7c --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/turn-controls.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import { AcpxTurnControlLedger, parseAcpxTurnControl } from "./turn-controls.js"; + +const control = { turnId: "turn-1", controlId: "control-1", mode: "follow_up", message: "Then validate it" }; +describe("ACP active turn controls", () => { + it("preserves explicit queued follow-up and rejects unbounded or ambiguous mutations", () => { + expect(parseAcpxTurnControl(control)).toEqual(control); + for (const invalid of [ { mode: "cancel" }, { mode: undefined }, { message: " " }, { message: "x\0y" }, + { message: "🙂".repeat(16_385) }, { turnId: "stale id" }, { controlId: "" }, { executable: "ambient" } ]) { + expect(() => parseAcpxTurnControl({ ...control, ...invalid })).toThrow(); + } + }); + it("tombstones ambiguous attempts, fences stale turns and bounds per-turn retention", () => { + const ledger = new AcpxTurnControlLedger(); + const parsed = parseAcpxTurnControl(control); + ledger.begin("turn-1"); + expect(() => ledger.reserve(parsed, null)).toThrow(/stale/); + ledger.reserve(parsed, "turn-1"); + expect(() => ledger.reserve(parsed, "turn-1")).toThrow(/already attempted/); + for (let index = 1; index < 1024; index++) ledger.reserve({ ...parsed, controlId: `control-${index + 1}` }, "turn-1"); + expect(() => ledger.reserve({ ...parsed, controlId: "overflow" }, "turn-1")).toThrow(/limit/); + ledger.begin("turn-2"); + expect(() => ledger.reserve(parsed, "turn-2")).toThrow(/stale/); + expect(() => ledger.reserve({ ...parsed, turnId: "turn-2" }, "turn-2")).not.toThrow(); + }); +}); diff --git a/packages/paperclip-runner/src/drivers/acpx/turn-controls.ts b/packages/paperclip-runner/src/drivers/acpx/turn-controls.ts new file mode 100644 index 0000000000..0b2830dceb --- /dev/null +++ b/packages/paperclip-runner/src/drivers/acpx/turn-controls.ts @@ -0,0 +1,42 @@ +export type AcpxTurnControlMode = "steer" | "follow_up"; +export interface AcpxTurnControl { + turnId: string; + controlId: string; + mode: AcpxTurnControlMode; + message: string; +} + +/** Closed, bounded mutation envelope; a follow-up never becomes a cancellation. */ +export function parseAcpxTurnControl(input: Record): AcpxTurnControl { + if (Object.keys(input).some(key => !["turnId", "controlId", "mode", "message"].includes(key))) { + throw new Error("ACP turn control contains unknown fields"); + } + const identity = (value: unknown, limit: number, name: string): string => { + if (typeof value !== "string" || value.length > limit || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value)) { + throw new Error(`ACP ${name} is invalid`); + } + return value; + }; + if (input.mode !== "steer" && input.mode !== "follow_up") throw new Error("ACP turn control mode is invalid"); + if (typeof input.message !== "string" || !input.message.trim() || input.message.includes("\0") + || Buffer.byteLength(input.message, "utf8") > 65_536) throw new Error("ACP turn control message is invalid"); + return { + turnId: identity(input.turnId, 240, "turn id"), + controlId: identity(input.controlId, 160, "control id"), + mode: input.mode, + message: input.message, + }; +} + +/** Reserve before dispatch and retain ambiguous attempts until the turn ends. */ +export class AcpxTurnControlLedger { + #turnId: string | null = null; + #attempts = new Set(); + begin(turnId: string): void { this.#turnId = turnId; this.#attempts.clear(); } + reserve(control: AcpxTurnControl, activeTurnId: string | null): void { + if (control.turnId !== activeTurnId || this.#turnId !== activeTurnId) throw new Error("ACP turn control names a stale or inactive turn"); + if (this.#attempts.has(control.controlId)) throw new Error("ACP turn control was already attempted"); + if (this.#attempts.size >= 1024) throw new Error("ACP turn control limit reached"); + this.#attempts.add(control.controlId); + } +} diff --git a/packages/paperclip-runner/src/drivers/acpx/usage-accounting.test.ts b/packages/paperclip-runner/src/drivers/acpx/usage-accounting.test.ts index 168e81cc8c..d8d3e84191 100644 --- a/packages/paperclip-runner/src/drivers/acpx/usage-accounting.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/usage-accounting.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { persistedAcpxTurnUsage, + acpxUsageEstimateNotice, qualifiedAcpxUsageBreakdown, } from "./usage-accounting.js"; @@ -54,9 +55,9 @@ describe("qualified ACPX usage", () => { expect(qualifiedAcpxUsageBreakdown("claude", null)).toBeNull(); }); - it("does not infer accounting semantics for Pi or an uninitialized agent", () => { + it("does not double count reasoning for the exact pinned Pi model and preserves unknown profiles", () => { const usage = { inputTokens: 12, outputTokens: 30, thoughtTokens: 20 }; - expect(qualifiedAcpxUsageBreakdown("pi", usage)).toEqual(usage); + expect(qualifiedAcpxUsageBreakdown("pi", usage)).toEqual({ ...usage, thoughtTokens: 0 }); expect(qualifiedAcpxUsageBreakdown(null, usage)).toEqual(usage); }); }); @@ -130,3 +131,42 @@ describe("persisted terminal ACPX usage", () => { }); }); }); + +describe("Pi prompt accounting authority", () => { + const after = { lastRequestId: "turn", usageCost: { amount: 999, currency: "USD" }, requestTokenUsage: { + message: { input_tokens: 12, paperclip_pi: { provenance: "assistant_message_receipts", cost_usd: 0.123 } }, + } }; + it("preserves an exact new Pi receipt estimate without charging it as billed spend", () => { + const usage = persistedAcpxTurnUsage({}, after, "turn", "pi")!; + expect(usage.cost).toBeUndefined(); + expect(usage.pricingEstimateUsd).toBe(0.123); + expect(acpxUsageEstimateNotice(usage, "turn:pricing")?.payload).toMatchObject({ + category: "pi_usage_pricing_estimate", summary: expect.stringContaining("Billing cost is unverified"), + }); + expect(persistedAcpxTurnUsage(after, after, "turn", "pi")).toBeNull(); + }); + it("ignores foreign, missing, invalid and unproven estimates", () => { + expect(persistedAcpxTurnUsage({}, after, "turn", "copilot")?.pricingEstimateUsd).toBeUndefined(); + for (const receipt of [{}, { provenance: "other", cost_usd: 2 }, + { provenance: "assistant_message_receipts", cost_usd: -1 }, + { provenance: "assistant_message_receipts", cost_usd: Infinity }]) { + const usage = persistedAcpxTurnUsage({}, { ...after, requestTokenUsage: { + message: { input_tokens: 12, paperclip_pi: receipt }, + } }, "turn", "pi")!; + expect(usage.cost).toBeUndefined(); + expect(acpxUsageEstimateNotice(usage, "turn:pricing")).toBeNull(); + } + }); + it("preserves compaction receipt provenance without treating estimates as billing", () => { + const usage = persistedAcpxTurnUsage({}, { ...after, requestTokenUsage: { + message: { input_tokens: 12, paperclip_pi: { + provenance: "assistant_message_and_compaction_receipts", cost_usd: 0.25, + } }, + } }, "turn", "pi")!; + expect(usage.cost).toBeUndefined(); + expect(usage.usageProvenance).toBe("pi_assistant_message_and_compaction_receipts"); + expect(acpxUsageEstimateNotice(usage, "turn:pricing")?.payload).toMatchObject({ + details: expect.arrayContaining([{ name: "Usage source", value: "Assistant message and compaction receipts for this prompt" }]), + }); + }); +}); diff --git a/packages/paperclip-runner/src/drivers/acpx/usage-accounting.ts b/packages/paperclip-runner/src/drivers/acpx/usage-accounting.ts index f6c2ad50d6..2d8c2ff357 100644 --- a/packages/paperclip-runner/src/drivers/acpx/usage-accounting.ts +++ b/packages/paperclip-runner/src/drivers/acpx/usage-accounting.ts @@ -1,3 +1,4 @@ +import type { CanonicalProviderEvent } from "../../provider-events.js"; import type { QualifiedAcpxAgent } from "./qualified-profiles.js"; function record(value: unknown): Record { @@ -13,9 +14,10 @@ export function qualifiedAcpxUsageBreakdown( ): unknown { if (value === null || value === undefined) return value; const breakdown = record(value); - if (agent !== "claude" && agent !== "codex") return breakdown; + if (agent !== "claude" && agent !== "codex" && agent !== "pi") return breakdown; // Claude SDK aggregate output and Codex ACP toPromptUsage.outputTokens both - // INCLUDE reasoning. PRP folds thought into output, so its additive component + // INCLUDE reasoning. The exact pinned Pi OpenRouter model uses + // openai-completions, where completion_tokens also includes reasoning. PRP folds thought into output, so its additive component // is zero here, not the provider's diagnostic reasoning-token subset. return { ...breakdown, @@ -37,6 +39,7 @@ export function persistedAcpxTurnUsage( before: unknown, after: unknown, requestId: string, + agent: QualifiedAcpxAgent | null = null, ): Record | null { const current = record(after); if (current.lastRequestId !== requestId) return null; @@ -47,11 +50,20 @@ export function persistedAcpxTurnUsage( ); if (added.length !== 1) return null; const usage = record(receipts[added[0]!]); + const piReceipt = agent === "pi" ? record(usage.paperclip_pi) : {}; + const piReceiptVerified = piReceipt.provenance === "assistant_message_receipts" + || piReceipt.provenance === "assistant_message_and_compaction_receipts"; + const estimate = piReceiptVerified && typeof piReceipt.cost_usd === "number" + && Number.isFinite(piReceipt.cost_usd) && piReceipt.cost_usd >= 0 ? piReceipt.cost_usd : undefined; return { type: "status", tag: "usage_update", text: "terminal prompt usage", - cost: current.usageCost, + // Pi calculates cost from catalog prices, not billing receipts. Never feed + // this estimate into the authoritative/cumulative provider spend channel. + cost: agent === "pi" ? undefined : current.usageCost, + ...(piReceiptVerified ? { usageProvenance: `pi_${piReceipt.provenance}` } : {}), + ...(estimate === undefined ? {} : { pricingEstimateUsd: estimate }), breakdown: { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens, @@ -62,3 +74,24 @@ export function persistedAcpxTurnUsage( }, }; } + +/** Preserve the estimate for inspection while keeping billing authority separate. */ +export function acpxUsageEstimateNotice(usage: Record, itemId: string): CanonicalProviderEvent | null { + if ((usage.usageProvenance !== "pi_assistant_message_receipts" + && usage.usageProvenance !== "pi_assistant_message_and_compaction_receipts") + || typeof usage.pricingEstimateUsd !== "number" || !Number.isFinite(usage.pricingEstimateUsd) + || usage.pricingEstimateUsd < 0) return null; + return { + eventType: "provider.notice.recorded", itemId, + payload: { + schema: "paperclip.provider.notice.v1", noticeId: itemId, + severity: "info", category: "pi_usage_pricing_estimate", scope: "turn", + recoverable: true, userActionable: false, + summary: `Pi estimates this turn at $${usage.pricingEstimateUsd.toFixed(6)} from its model prices. Billing cost is unverified.`, + details: [{ name: "Cost source", value: "Pi model catalog pricing estimate" }, + { name: "Usage source", value: usage.usageProvenance === "pi_assistant_message_and_compaction_receipts" + ? "Assistant message and compaction receipts for this prompt" : "Assistant message receipts for this prompt" }, + { name: "Estimated USD", value: String(usage.pricingEstimateUsd) }], + }, + }; +} diff --git a/packages/paperclip-runner/src/drivers/codex/app-server-transport.ts b/packages/paperclip-runner/src/drivers/codex/app-server-transport.ts index 4dedcb6726..0b06817a53 100644 --- a/packages/paperclip-runner/src/drivers/codex/app-server-transport.ts +++ b/packages/paperclip-runner/src/drivers/codex/app-server-transport.ts @@ -1,3 +1,4 @@ +import type { NativeTurnControlCapabilities } from "../../contracts/types.js"; import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; import type { HarnessRuntimeRequestResolution } from "../../contracts/harness-driver.js"; import { githubCredentialEnvironment } from "../../github-credential-environment.js"; @@ -30,6 +31,8 @@ export type CodexServerRequestHandler = ( ) => Promise>; export interface CodexAppServerTransport { + /** Runner-owned live capability projection; absent on native Codex transports. */ + turnControlCapabilities?(): NativeTurnControlCapabilities | null; request( method: string, params: Record, diff --git a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.test-support.ts b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.test-support.ts index dd5f3e2661..b2d9a09644 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.test-support.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.test-support.ts @@ -1,4 +1,7 @@ -import { realpathSync } from "node:fs"; +import { mkdtempSync, realpathSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll } from "vitest"; import { fileURLToPath } from "node:url"; import { @@ -45,7 +48,10 @@ import { type CodexTraceInterpretation, } from "./app-server-transport.js"; -export const WORKSPACE = realpathSync.native(process.cwd()); +// Tests run in managed worktrees beneath sensitive Codex state. Use a real +// disposable workspace instead of weakening production host-path admission. +export const WORKSPACE = realpathSync.native(mkdtempSync(join(tmpdir(), "paperclip-codex-test-workspace-"))); +afterAll(() => rmSync(WORKSPACE, { recursive: true, force: true })); export class TestQueue implements AsyncIterable { values: T[] = []; diff --git a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.test.ts index 58d8e547a1..36480ed9f0 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.test.ts @@ -1486,6 +1486,38 @@ describe("Codex app-server Codex driver", () => { ).rejects.toThrow("cannot be a filesystem root"); }); + it("uses negotiated ACP controls through the Codex transport facade", async () => { + let controls = { steering: false, queuedFollowUp: false }; + const transport = Object.assign(new FakeCodexTransport(), { turnControlCapabilities: () => ({ ...controls }) }); + const session = await makeDriver([transport], { + driverIdentity: { kind: "acpx_runtime", displayName: "Pi ACP", version: "test" }, + capabilities: { steering: false }, + }).openSession({ runId: "run-pi", normalizedSessionId: "session-pi", workingDirectory: TEST_WORKING_DIRECTORY }); + try { + expect(session.turnControlCapabilities?.()).toEqual(controls); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Work" } }); + await expect(session.steer?.({ turnId, message: { role: "user", text: "Change" } })).rejects.toThrow("not negotiated"); + controls = { steering: true, queuedFollowUp: true }; + expect(session.turnControlCapabilities?.()).toEqual(controls); + const input = { turnId, correlationId: "queued-1", mode: "follow_up" as const, message: { role: "user" as const, text: "Then validate" } }; + await session.steer?.(input); + expect(transport.calls.find(call => call.method === "turn/steer")?.params).toMatchObject({ expectedTurnId: turnId, mode: "follow_up", correlationId: "queued-1" }); + await expect(session.steer?.(input)).rejects.toThrow("already acknowledged"); + expect(transport.calls.filter(call => call.method === "turn/steer")).toHaveLength(1); + } finally { await session.close({ reason: "verified" }); } + }); + + it("keeps native follow-up disabled for the Codex app-server driver", async () => { + const transport = Object.assign(new FakeCodexTransport(), { turnControlCapabilities: () => ({ steering: true, queuedFollowUp: true }) }); + const session = await makeDriver([transport]).openSession({ runId: "run-codex", normalizedSessionId: "session-codex", workingDirectory: TEST_WORKING_DIRECTORY }); + try { + expect(session.turnControlCapabilities?.()).toEqual({ steering: true, queuedFollowUp: false }); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Work" } }); + await expect(session.steer?.({ turnId, mode: "follow_up", message: { role: "user", text: "Queue" } })).rejects.toThrow("not negotiated"); + expect(transport.calls.filter(call => call.method === "turn/steer")).toHaveLength(0); + } finally { await session.close({ reason: "verified" }); } + }); + it("steers and interrupts an active turn without replacing the session", async () => { const transport = new FakeCodexTransport(); const session = await makeDriver([transport]).openSession({ diff --git a/packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts b/packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts index eb3bf0e576..5aecc4dd8a 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts @@ -72,6 +72,13 @@ export class CodexHarnessSession } } + turnControlCapabilities() { + if (this.driverKind === "acpx_runtime") { + return this.transport.turnControlCapabilities?.() ?? { steering: false, queuedFollowUp: false }; + } + return { steering: this.capabilities.steering, queuedFollowUp: false }; + } + ids(): ReturnType { return { driverSessionId: this.opened.threadId, @@ -300,18 +307,23 @@ export class CodexHarnessSession } async steer(input: { + mode?: "steer" | "follow_up"; turnId: string; message: NativeUserMessage; correlationId?: string; }): Promise { this.assertProtocolIntegrity(); - this.requireCapability("steering"); + const controls = this.turnControlCapabilities(); + if (!(input.mode === "follow_up" ? controls.queuedFollowUp : controls.steering)) { + throw this.unsupported("steering", "requested turn control was not negotiated"); + } this.requireActiveTurn(input.turnId, "steering"); if (input.correlationId) { const acknowledgedTurnId = this.acknowledgedSteeringCorrelations.get( input.correlationId, ); if (acknowledgedTurnId) { + if (this.driverKind === "acpx_runtime") throw new Error("ACP turn control correlation was already acknowledged"); if (acknowledgedTurnId !== input.turnId) throw new HarnessOperationAlreadyTerminalError("steering"); return; @@ -323,6 +335,7 @@ export class CodexHarnessSession input: [userInput(input.message)], expectedTurnId: input.turnId, correlationId: input.correlationId, + ...(input.mode === undefined ? {} : { mode: input.mode }), }); if (this.activeTurnId !== input.turnId) { throw new HarnessOperationAlreadyTerminalError("steering"); @@ -337,7 +350,8 @@ export class CodexHarnessSession "item.completed", { kind: "steering_acknowledgement", - text: "Steering acknowledged for the active turn.", + text: input.mode === "follow_up" ? "Follow-up queued by the active provider." : "Steering acknowledged for the active turn.", + ...(this.driverKind === "acpx_runtime" ? { mode: input.mode ?? "steer" } : {}), status: "acknowledged", }, { diff --git a/packages/paperclip-runner/src/drivers/codex/codex-question-adapter.ts b/packages/paperclip-runner/src/drivers/codex/codex-question-adapter.ts index 1ac244ece5..d75f709caf 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-question-adapter.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-question-adapter.ts @@ -1,3 +1,4 @@ +import { isAcpxCanonicalInputMethod } from "../acpx/profile-extensions.js"; import type { HarnessRuntimeRequest, HarnessRuntimeRequestKind, @@ -70,7 +71,7 @@ export function runtimeRequestKind(method: string): HarnessRuntimeRequestKind | ) { return "file_approval"; } - if (method === "item/permissions/requestApproval") + if (method === "item/permissions/requestApproval" || method === "session/request_permission") return "permission_approval"; if ( method === "item/tool/requestUserInput" || @@ -78,7 +79,7 @@ export function runtimeRequestKind(method: string): HarnessRuntimeRequestKind | ) { return "user_input"; } - if (method === "mcpServer/elicitation/request" || method === "elicitation/create") return "elicitation"; + if (method === "mcpServer/elicitation/request" || isAcpxCanonicalInputMethod(method)) return "elicitation"; return null; } @@ -89,7 +90,7 @@ export function runtimeRequestKind(method: string): HarnessRuntimeRequestKind | * degrading back to the legacy textarea presentation. */ export function hasCodexQuestionForm(method: string, params: Record): boolean { - if (method === "elicitation/create") return "questionSet" in params; + if (isAcpxCanonicalInputMethod(method)) return "questionSet" in params; if (method === "item/tool/requestUserInput" || method === "tool/requestUserInput") { return "questions" in params; } @@ -267,7 +268,7 @@ export function normalizeCodexQuestionSet( params: Record, responseContext: CodexQuestionResponseContext, ): PaperclipQuestionSet | null { - if (method === "elicitation/create") return parsePaperclipQuestionSet(params.questionSet); + if (isAcpxCanonicalInputMethod(method)) return parsePaperclipQuestionSet(params.questionSet); if (method === "item/tool/requestUserInput" || method === "tool/requestUserInput") { if (!Array.isArray(params.questions) || params.questions.length === 0) return null; if (params.questions.length > 64) throw new Error("Codex question form exceeds 64 questions"); @@ -447,6 +448,9 @@ export function normalizeCodexQuestionSet( } export function runtimeRequestProtocolPayload(request: HarnessRuntimeRequest): Record { + if (request.method === "session/request_permission") { + return { ...request, schema: PAPERCLIP_RUNTIME_REQUEST_SCHEMA_V2, type: "permission", choices: request.details.choices }; + } if (request.input !== undefined) { return { schema: PAPERCLIP_RUNTIME_REQUEST_SCHEMA_V2, @@ -554,7 +558,7 @@ export function runtimeRequestResponse( ): Record { // The durable transport sends this canonical resolution to the ACPX sidecar, // which owns conversion back to the original provider form values. - if (request.method === "elicitation/create") return structuredClone(resolution); + if (isAcpxCanonicalInputMethod(request.method) || request.method === "session/request_permission") return structuredClone(resolution); if ( request.requestKind === "command_approval" || request.requestKind === "file_approval" diff --git a/packages/paperclip-runner/src/drivers/codex/codex-session-server-requests.ts b/packages/paperclip-runner/src/drivers/codex/codex-session-server-requests.ts index 19c7814958..6e35218072 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-session-server-requests.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-session-server-requests.ts @@ -1,3 +1,4 @@ +import { isAcpxCanonicalInputMethod } from "../acpx/profile-extensions.js"; import type { HarnessRuntimeRequest, PaperclipQuestionSet } from "../../contracts/harness-driver.js"; import { CODEX_BLOCK_TOOL_NAME, @@ -292,7 +293,7 @@ async function handleServerRequestBody( prompt: runtimeRequestPrompt(requestKind, request.params), details: record(redactCodexValue(boundedCodexValue(request.params))), ...(input !== null ? { input } : {}), - origin: request.method === "elicitation/create" ? { + origin: isAcpxCanonicalInputMethod(request.method) || request.method === "session/request_permission" ? { adapter: "acpx-runtime-sidecar", provider: text(record(request.params.origin).provider, "acpx"), method: request.method, diff --git a/packages/paperclip-runner/src/drivers/codex/codex-thread-normalization.ts b/packages/paperclip-runner/src/drivers/codex/codex-thread-normalization.ts index aae21ed3d6..6513b2446a 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-thread-normalization.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-thread-normalization.ts @@ -1,3 +1,4 @@ +import { isAcpxCanonicalInputMethod } from "../acpx/profile-extensions.js"; import type { HarnessThreadGoal, HarnessThreadLineageEntry, @@ -204,10 +205,11 @@ export function safeCodexRequestResponse( method: string, action: "decline" | "cancel" = "decline", ): Record { + if (method === "session/request_permission") return { action: "cancel" }; if (method === "item/permissions/requestApproval") { return { permissions: {}, scope: "turn" }; } - if (method === "mcpServer/elicitation/request" || method === "elicitation/create") { + if (method === "mcpServer/elicitation/request" || isAcpxCanonicalInputMethod(method)) { return { action, content: null, _meta: null }; } if ( diff --git a/packages/paperclip-runner/src/issue-thread/issue-thread.test.ts b/packages/paperclip-runner/src/issue-thread/issue-thread.test.ts index 85cea2d767..401ea3c0d0 100644 --- a/packages/paperclip-runner/src/issue-thread/issue-thread.test.ts +++ b/packages/paperclip-runner/src/issue-thread/issue-thread.test.ts @@ -500,6 +500,11 @@ describe("Capability live projection", () => { .map((item) => item.body); expect(bodies.some((body) => body.includes("semantic-interaction-result"))).toBe(false); }); + it.each([["cursor", "Cursor"], ["copilot", "GitHub Copilot"], ["pi", "Pi"]] as const)("labels the %s provider truthfully", async (agent, label) => { + const snapshot = await liveSnapshot(); + snapshot.config = { ...snapshot.config, provider: "acpx", driver: "acpx_runtime", acpxAgent: agent }; + expect(projectCapabilityIssueThread({ snapshot }).identity.agentLabel).toBe(`Real ${label} via ACPX`); + }); it("separates exposed agent tools from withheld control-plane operations", async () => { const view = projectCapabilityIssueThread({ snapshot: await liveSnapshot() }); diff --git a/packages/paperclip-runner/src/issue-thread/live-projection.ts b/packages/paperclip-runner/src/issue-thread/live-projection.ts index 7069b5ddb7..bcf606d18c 100644 --- a/packages/paperclip-runner/src/issue-thread/live-projection.ts +++ b/packages/paperclip-runner/src/issue-thread/live-projection.ts @@ -8,6 +8,7 @@ * change already exists in the input. */ +import { ACPX_CAPABILITY_PROFILES } from "../drivers/acpx/capability-profiles.js"; import type { CapabilityFixtureInteraction, CapabilityFixtureState, @@ -761,7 +762,7 @@ export function projectCapabilityIssueThread( : snapshot.config.provider === "opencode" || snapshot.config.driver === "opencode_server" ? "Real OpenCode" : snapshot.config.provider === "acpx" || snapshot.config.driver === "acpx_runtime" - ? `Real ${snapshot.config.acpxAgent === "grok" ? "Grok Build" : snapshot.config.acpxAgent === "claude" ? "Claude" : snapshot.config.acpxAgent === "codex" ? "Codex" : "Pi"} via ACPX` + ? `Real ${ACPX_CAPABILITY_PROFILES[snapshot.config.acpxAgent ?? "pi"].displayName} via ACPX` : "Real Codex" : mode === "replay" ? "Replay" : "Fake agent"; const connection = input.connection ?? { state: "connected", attempt: 0 }; diff --git a/packages/paperclip-runner/src/issue-thread/public-view.ts b/packages/paperclip-runner/src/issue-thread/public-view.ts index a36b320a3b..e1f62304fc 100644 --- a/packages/paperclip-runner/src/issue-thread/public-view.ts +++ b/packages/paperclip-runner/src/issue-thread/public-view.ts @@ -246,7 +246,18 @@ function publicProviderPayload( case "wait": return common(["schema", "waitId", "reason", "status", "plannedDurationMs", "elapsedDurationMs"]); case "provider_notice": - return common(["schema", "noticeId", "severity", "category", "scope", "recoverable", "userActionable", "summary"]); + return { + ...common(["schema", "noticeId", "severity", "category", "scope", "recoverable", "userActionable", "summary"]), + details: (Array.isArray(source.details) ? source.details : []).slice(0, 64).map(value => { + const detail = asRecord(value); + return { name: clamp(typeof detail.name === "string" ? detail.name : "Detail", 160), value: clamp(typeof detail.value === "string" ? detail.value : "", 4_000) }; + }), + ...(source.provenance ? { provenance: Object.fromEntries( + Object.entries(asRecord(source.provenance)).filter(([key, value]) => + ["method", "eventType", "sessionId", "turnId", "agentId", "timestamp"].includes(key) && typeof value === "string" + ).map(([key, value]) => [key, clamp(String(value), 240)]), + ) } : {}), + }; } } diff --git a/packages/paperclip-runner/src/live/live-session.test.ts b/packages/paperclip-runner/src/live/live-session.test.ts index 999c5a9683..8eef8f295f 100644 --- a/packages/paperclip-runner/src/live/live-session.test.ts +++ b/packages/paperclip-runner/src/live/live-session.test.ts @@ -26,6 +26,17 @@ import { defaultCapabilityRunnerdBinary } from "./runnerd-codex-transport.js"; import { captureTurnRejection } from "../../test/capture-turn-rejection.js"; import * as workspaceDiff from "./workspace-diff.js"; +it.each(["pi", "cursor", "copilot"] as const)("requires separately bound evaluation opt-in for %s", async (acpxAgent) => { + const service = new CapabilityLiveSessionService(); + await expect(service.create({ provider: "acpx", acpxAgent, requestedModel: "explicit-model" })) + .rejects.toThrow("explicit evaluation opt-in"); + const mismatched = new CapabilityLiveSessionService({ transportOptions: { + acpxCandidateProfile: acpxAgent === "pi" ? "cursor" : "pi", + } }); + await expect(mismatched.create({ provider: "acpx", acpxAgent, requestedModel: "explicit-model" })) + .rejects.toThrow("explicit evaluation opt-in"); +}); + class AsyncNotifications implements AsyncIterable { #values: CodexRpcNotification[] = []; #waiters: Array<(value: IteratorResult) => void> = []; @@ -65,6 +76,8 @@ interface FakeProviderState { holdAfterTool: boolean; closeError: Error | null; usageRunDelta: Record | null; + omitReadUsage?: boolean; + assistantReply?: string; onTurnStart?: () => Promise; onUsage?: (queue: AsyncNotifications, turnId: string) => void | Promise; /** Delays the fake `turn/interrupt` reply, to model a slow transport round trip. */ @@ -106,14 +119,14 @@ class FakeCapabilityCodexTransport implements CodexAppServerTransport { id: this.state.threadId, sessionId: this.state.providerSessionId, turns: [...this.state.turns].map(([id, status]) => ({ id, status })), - tokenUsage: { + ...(this.state.omitReadUsage ? {} : { tokenUsage: { total: { inputTokens: this.state.nextTurn * 100, cachedInputTokens: this.state.nextTurn * 20, outputTokens: this.state.nextTurn * 10, reasoningOutputTokens: this.state.nextTurn * 2, }, - }, + } }), }, }; } @@ -250,7 +263,7 @@ class FakeCapabilityCodexTransport implements CodexAppServerTransport { params: { threadId: this.state.threadId, turnId, - item: { id: `message-${turnId}`, type: "agentMessage", text: assistantText }, + item: { id: `message-${turnId}`, type: "agentMessage", text: this.state.assistantReply ?? assistantText }, }, }); this.state.turns.set(turnId, "completed"); @@ -890,6 +903,78 @@ describe("Capability live runnerd and Codex session", () => { await service.shutdown(session.id); }); + it("exposes native completion consistently on fresh and resumed sessions without granting semantic mutations", async () => { + const state = providerState(); + const store = new InMemoryCapabilityLiveSessionStore(); + const authority: Array[0]> = []; + const factory: CapabilityLiveTransportFactory = (options) => { + authority.push(options); + return fakeTransportFactory(state)(options); + }; + const firstService = new CapabilityLiveSessionService({ store, transportFactory: factory }); + const first = await firstService.create(); + const opened = state.transports[0]!.requests.find((request) => request.method === "thread/start")!; + const names = (opened.params.dynamicTools as Array<{ name: string }>).map((tool) => tool.name); + expect(names.filter((name) => name === "paperclip_finish")).toHaveLength(1); + expect(names.filter((name) => name === "paperclip_block")).toHaveLength(1); + expect(names).not.toContain("create_task"); + expect(opened.params.baseInstructions).toContain('"revision":"paperclip-capability-live-v1"'); + expect(opened.params.baseInstructions).toContain('"criterionIds":["objective"]'); + await first.suspend(); + const restoredService = new CapabilityLiveSessionService({ store, transportFactory: factory }); + const restored = await restoredService.restore(first.id); + // The facade appends native completion schemas to this admitted semantic list. + expect(authority[1]?.resumeDynamicTools?.map((tool) => tool.name)).toEqual( + names.filter((name) => name !== "paperclip_finish" && name !== "paperclip_block"), + ); + const resumed = state.transports[1]!.requests.find((request) => request.method === "thread/resume")!; + expect(resumed.params.baseInstructions).toBe(opened.params.baseInstructions); + await restoredService.shutdown(restored.id); + await firstService.shutdown(first.id); + }); + + it("accepts only revision-bound advisory completion while mock mutations keep their own authority", async () => { + const state = providerState(); + const service = new CapabilityLiveSessionService({ transportFactory: fakeTransportFactory(state) }); + const session = await service.create(); + const before = session.mockState(); + const result = { + schema: "paperclip.run_result.v1", reportedWorkDisposition: "done", summary: "Orientation complete", + completionClaim: { contractRevision: "paperclip-capability-live-v1", objectiveSatisfied: true, + criteria: [{ criterionId: "objective", status: "satisfied", evidenceRefs: [] }], remainingWork: [] }, + evidence: [], verification: [], + }; + const outcomes: Record[] = []; + state.onTurnStart = async () => { + const transport = state.transports.at(-1)!; + const turnId = [...state.turns.keys()].at(-1)!; + const call = (tool: string, arguments_: unknown, overrideTurn = turnId) => transport.invokeServerRequest({ + id: `call-${outcomes.length}`, method: "item/tool/call", + params: { threadId: state.threadId, turnId: overrideTurn, callId: `call-${outcomes.length}`, tool, arguments: arguments_ }, + }); + outcomes.push(await call("paperclip_finish", { ...result, completionClaim: { ...result.completionClaim, contractRevision: "stale" } })); + outcomes.push(await call("paperclip_finish", result, "wrong-turn")); + outcomes.push(await call("paperclip_block", result)); + outcomes.push(await call("paperclip_finish", result)); + outcomes.push(await call("paperclip_finish", { ...result, summary: "Conflicting report" })); + outcomes.push(await call("create_task", { title: "Unauthorized task", idempotencyKey: "unauthorized-task" })); + expect(session.snapshot().terminalTurns).toHaveLength(0); + }; + await session.sendMessage("Orient to the assigned task."); + expect(outcomes.map((outcome) => outcome.success)).toEqual([false, false, false, true, false, false]); + expect(session.snapshot().semanticResult).toMatchObject(result); + const completionEvidence = session.snapshot().evidence.filter((entry) => entry.data.operationId === "paperclip_finish"); + expect(completionEvidence.map((entry) => entry.kind)).toEqual(["tool_call", "tool_result"]); + expect(completionEvidence[1]?.data.beforeRevision).toBe(completionEvidence[1]?.data.afterRevision); + expect(session.mockState().tasks).toEqual(before.tasks); + expect(session.snapshot().terminalTurns).toHaveLength(1); + expect(session.snapshot().authorizationRecords).toContainEqual(expect.objectContaining({ operationId: "create_task", allowed: false })); + state.onTurnStart = undefined; + await session.sendMessage("Read the task again without reporting completion."); + expect(session.snapshot().semanticResult).toBeNull(); + await service.shutdown(session.id); + }); + it("passes caller-supplied native system instructions to the provider", async () => { const state = providerState(); const service = new CapabilityLiveSessionService({ @@ -905,9 +990,11 @@ describe("Capability live runnerd and Codex session", () => { state.transports[0]?.requests.find( (request) => request.method === "thread/start", )?.params.baseInstructions, - ).toBe( - 'Native instructions\n\nRead-only instruction sibling root: /runtime/instructions\nFor native paperclip_finish/paperclip_block reports, completionContract={"revision":"paperclip-capability-live-v1","criterionIds":["objective"]}. This report does not change mock task state; use the exposed semantic tools for task changes.', + ).toContain( + "Native instructions\n\nRead-only instruction sibling root: /runtime/instructions", ); + expect(state.transports[0]?.requests.find((request) => request.method === "thread/start")?.params.baseInstructions) + .toContain('Native completion report contract: {"revision":"paperclip-capability-live-v1","criterionIds":["objective"]}'); await service.shutdown(session.id); }); @@ -1077,6 +1164,58 @@ describe("Capability live runnerd and Codex session", () => { await service.shutdown(session.id); }); + it.each(["pi", "cursor", "copilot"] as const)("retains a completed %s result with explicit unavailable usage and no fabricated receipt", async (acpxAgent) => { + const state = providerState(); + state.omitReadUsage = true; + state.assistantReply = "Upgrade your plan to continue"; + state.onUsage = (queue, turnId) => queue.push({ + method: "turn/completed", + params: { threadId: state.threadId, turn: { id: turnId, status: "completed" } }, + }); + const store = new InMemoryCapabilityLiveSessionStore(); + const service = new CapabilityLiveSessionService({ + store, + transportFactory: fakeTransportFactory(state), + transportOptions: { acpxCandidateProfile: acpxAgent }, + }); + const session = await service.create({ + provider: "acpx", acpxAgent, + requestedModel: acpxAgent === "pi" ? "openrouter/deepseek/deepseek-v4-flash-0731" : "exact-model", + }); + const result = await session.sendMessage("Orient to this task."); + expect(result.status).toBe("completed"); + expect(result.assistantText).toBe(state.assistantReply); + expect(result.snapshot.usageLedger).toEqual([]); + expect(result.snapshot.usageUnavailable).toEqual([{ + turnId: result.turnId, + attemptId: result.snapshot.currentAttemptId, + agent: acpxAgent, + reason: "provider_did_not_report_usage", + tokenUsage: null, + costNanodollars: null, + observedAt: expect.any(String), + }]); + expect(result.snapshot.authorizationRecords.filter((entry) => entry.phase !== "exposure")).toEqual([]); + await service.shutdown(session.id); + expect((await store.load(session.id))?.usageUnavailable).toEqual(result.snapshot.usageUnavailable); + expect((await store.load(session.id))?.process?.runnerExited).toBe(true); + }); + + it("still rejects missing usage for a qualified provider", async () => { + const state = providerState(); + state.omitReadUsage = true; + state.onUsage = (queue, turnId) => queue.push({ + method: "turn/completed", + params: { threadId: state.threadId, turn: { id: turnId, status: "completed" } }, + }); + const service = new CapabilityLiveSessionService({ transportFactory: fakeTransportFactory(state) }); + const session = await service.create(); + await expect(session.sendMessage("Orient to this task.")).rejects.toThrow("capability_live_usage_missing"); + expect(session.snapshot().usageLedger).toEqual([]); + expect(session.snapshot().usageUnavailable).toBeUndefined(); + await service.shutdown(session.id); + }); + it("streams normalized usage before terminal with cumulative fallback and explicit run deltas", async () => { const state = providerState(); const service = new CapabilityLiveSessionService({ transportFactory: fakeTransportFactory(state) }); diff --git a/packages/paperclip-runner/src/live/live-session.ts b/packages/paperclip-runner/src/live/live-session.ts index cad893333e..d24995b5ec 100644 --- a/packages/paperclip-runner/src/live/live-session.ts +++ b/packages/paperclip-runner/src/live/live-session.ts @@ -8,7 +8,10 @@ import type { CodexRpcServerRequest, } from "../drivers/codex/app-server-transport.js"; import { redactCodexDiagnostic } from "../drivers/codex/app-server-transport.js"; -import { createSkilllessCodexThreadConfig } from "../drivers/codex/codex-app-server-driver.js"; +import { codexSemanticToolSpecs, createSkilllessCodexThreadConfig } from "../drivers/codex/codex-app-server-driver.js"; +import { createCodexTaskEnvelope } from "../contracts/codex.js"; +import { codexToolAcceptsResult, isCodexSemanticTool, isRetainableCodexPayload, rejectedCodexToolCall } from "../drivers/codex/codex-boundaries.js"; +import { validateCodexResultProposal } from "../mock-core/codex-runner.js"; import { resolveQualifiedAcpxProfile, type QualifiedAcpxAgent, @@ -59,6 +62,11 @@ const LIVE_COMPLETION_CONTRACT = Object.freeze({ revision: "paperclip-capability-live-v1", criterionIds: ["objective"], }); +const LIVE_COMPLETION_ENVELOPE = createCodexTaskEnvelope({ + objective: "Complete the current requested action without exceeding mock semantic authority.", + contractRevision: LIVE_COMPLETION_CONTRACT.revision, + criteria: LIVE_COMPLETION_CONTRACT.criterionIds.map((id) => ({ id, requirement: "Complete the current requested action." })), +}); const LIVE_BASE_INSTRUCTIONS = [ "You are operating one mock Paperclip issue through typed semantic tools.", "Use only the tools exposed in this thread; never call a Paperclip REST API.", @@ -200,6 +208,16 @@ export interface CapabilityLiveUsageReceipt { observedAt: string; } +export interface CapabilityLiveUnavailableUsage { + turnId: string; + attemptId: string; + agent: "pi" | "cursor" | "copilot"; + reason: "provider_did_not_report_usage"; + tokenUsage: null; + costNanodollars: null; + observedAt: string; +} + export interface CapabilityLiveStateRevision { revision: number; at: string; @@ -267,6 +285,8 @@ export interface CapabilityLiveSessionSnapshot { terminalTurns?: CapabilityLiveTurnTerminalFact[]; /** Exact-once provider usage/cost receipts retained across every attempt. */ usageLedger?: CapabilityLiveUsageReceipt[]; + /** Candidate turns without a provider receipt; these are never zero-valued receipts. */ + usageUnavailable?: CapabilityLiveUnavailableUsage[]; /** Process-level evidence for an interrupted Codex semantic-call resume. */ nativeResume?: { schema: "paperclip.runner.native-resume-proof/v1"; @@ -629,7 +649,7 @@ export function assertCapabilityLiveSessionSnapshot( } if (provider === "acpx") { const agent = config.acpxAgent; - if (agent !== "pi" && agent !== "claude" && agent !== "codex" && agent !== "grok") { + if (agent !== "pi" && agent !== "claude" && agent !== "codex" && agent !== "grok" && agent !== "cursor" && agent !== "copilot") { throw new Error("capability_live_checkpoint_corrupt: invalid config.acpxAgent"); } const expected = resolveQualifiedAcpxProfile(agent, text(config.requestedModel)); @@ -769,6 +789,24 @@ export function assertCapabilityLiveSessionSnapshot( } usageReceiptIds.add(receiptId); } + if (snapshot.usageUnavailable !== undefined && !Array.isArray(snapshot.usageUnavailable)) { + throw new Error("capability_live_checkpoint_corrupt: invalid unavailable usage"); + } + const unavailableTurnIds = new Set(); + for (const value of snapshot.usageUnavailable ?? []) { + const unavailable = record(value); + const turnId = text(unavailable.turnId); + if (config.provider !== "acpx" || unavailable.agent !== config.acpxAgent + || !["pi", "cursor", "copilot"].includes(text(unavailable.agent)) + || !turnId || turnId.length > 512 || unavailableTurnIds.has(turnId) + || !text(unavailable.attemptId) || text(unavailable.attemptId).length > 512 + || !text(unavailable.observedAt) + || unavailable.reason !== "provider_did_not_report_usage" + || unavailable.tokenUsage !== null || unavailable.costNanodollars !== null) { + throw new Error("capability_live_checkpoint_corrupt: invalid unavailable usage"); + } + unavailableTurnIds.add(turnId); + } if ( snapshot.stateHistory !== undefined && (!Array.isArray(snapshot.stateHistory) || snapshot.stateHistory.length > 256) @@ -862,8 +900,10 @@ export class CapabilityLiveSessionService { } async create(input: CreateCapabilityLiveSessionInput = {}): Promise { - if (input.provider === "acpx" && input.acpxAgent === "pi") { - throw new Error("The Pi ACPX profile is not available"); + if (input.provider === "acpx" && input.acpxAgent !== undefined + && ["pi", "cursor", "copilot"].includes(input.acpxAgent) + && this.#transportOptions.acpxCandidateProfile !== input.acpxAgent) { + throw new Error("The candidate ACPX profile requires explicit evaluation opt-in"); } if (input.provider === "claude_managed" && !input.managedProfile) { throw new Error("Claude Managed live sessions require a qualified managed profile"); @@ -1146,6 +1186,7 @@ export class CapabilityLiveSession { readonly #attempts: CapabilityLiveAttemptSnapshot[]; readonly #terminalTurns: CapabilityLiveTurnTerminalFact[]; readonly #usageLedger: CapabilityLiveUsageReceipt[]; + readonly #usageUnavailable: CapabilityLiveUnavailableUsage[]; readonly #stateHistory: CapabilityLiveStateRevision[]; readonly #workspaceDiffs: CapabilityLiveWorkspaceDiffEntry[]; readonly #workspaceFileReferences: CapabilityLiveWorkspaceFileReferenceEntry[]; @@ -1155,6 +1196,7 @@ export class CapabilityLiveSession { #providerThreadId = ""; #providerSessionId: string | null = null; #providerModel: { id: string; provider: string } | undefined; + #semanticResult: Record | null = null; #status: CapabilityLiveSessionStatus = "starting"; #activeTurnId: string | null = null; #providerRunBinding: { runId: string; turnId: string; itemId: string } | null; @@ -1203,6 +1245,7 @@ export class CapabilityLiveSession { this.#currentAttemptId = initialAttemptId; this.#terminalTurns = structuredClone(options.snapshot?.terminalTurns ?? []); this.#usageLedger = structuredClone(options.snapshot?.usageLedger ?? []); + this.#usageUnavailable = structuredClone(options.snapshot?.usageUnavailable ?? []); this.#stateHistory = structuredClone(options.snapshot?.stateHistory ?? [{ revision: this.#port.snapshot().revision, at: this.#createdAt, @@ -1218,6 +1261,11 @@ export class CapabilityLiveSession { this.#providerModel = options.snapshot?.providerModel === undefined ? undefined : structuredClone(options.snapshot.providerModel); + if (options.snapshot?.semanticResult) { + const validation = validateCodexResultProposal(options.snapshot.semanticResult, LIVE_COMPLETION_ENVELOPE); + if (validation.status !== "accepted") throw new Error("capability_live_invalid_semantic_result_checkpoint"); + this.#semanticResult = jsonValue(validation.result) as Record; + } this.#activeTurnId = options.snapshot?.activeTurnId ?? null; this.#providerRunBinding = options.snapshot?.providerRunBinding === undefined ? null @@ -1366,6 +1414,7 @@ export class CapabilityLiveSession { ...(this.#providerModel === undefined ? {} : { providerModel: structuredClone(this.#providerModel) }), + semanticResult: structuredClone(this.#semanticResult), status: this.#status, activeTurnId: this.#activeTurnId, ...(this.#providerRunBinding === null @@ -1388,6 +1437,7 @@ export class CapabilityLiveSession { currentAttemptId: this.#currentAttemptId, terminalTurns: structuredClone(this.#terminalTurns), usageLedger: structuredClone(this.#usageLedger), + ...(this.#usageUnavailable.length === 0 ? {} : { usageUnavailable: structuredClone(this.#usageUnavailable) }), stateHistory: structuredClone(this.#stateHistory), workspaceDiffs: structuredClone(this.#workspaceDiffs), workspaceFileReferences: structuredClone(this.#workspaceFileReferences), @@ -1483,6 +1533,7 @@ export class CapabilityLiveSession { settle: () => settleAdmission(), }; this.#pendingTurnAdmission = admission; + this.#semanticResult = null; this.#status = "running"; this.#clearIdleTimer(); this.#turnEventCount = 0; @@ -1772,10 +1823,29 @@ export class CapabilityLiveSession { ...selected, costNanodollars: Math.max(selected.costNanodollars, reported?.costNanodollars ?? 0), }; - if ( - (selectedWithReportedCost?.inputTokens ?? 0) + (selectedWithReportedCost?.outputTokens ?? 0) === 0 - && !allowEmpty - ) { + const missingTokens = (selectedWithReportedCost?.inputTokens ?? 0) + + (selectedWithReportedCost?.outputTokens ?? 0) === 0; + const candidate = this.#config.acpxAgent; + if (missingTokens && this.#config.provider === "acpx" + && (candidate === "pi" || candidate === "cursor" || candidate === "copilot") + && this.#transportOptions.acpxCandidateProfile === candidate) { + // Native candidate wrappers can complete a turn without a usage receipt, + // including entitlement-denied turns. Retain the actual result for the + // oracle without inventing tokens, charges, or a successful model call. + if (!this.#usageUnavailable.some((entry) => entry.turnId === turnId)) { + this.#usageUnavailable.push({ + turnId, + attemptId: this.#currentAttemptId, + agent: candidate, + reason: "provider_did_not_report_usage", + tokenUsage: null, + costNanodollars: null, + observedAt: this.#now().toISOString(), + }); + } + return; + } + if (missingTokens && !allowEmpty) { throw new Error(`capability_live_usage_missing:${JSON.stringify({ captured: captured !== undefined, needsRead, @@ -2282,6 +2352,23 @@ export class CapabilityLiveSession { itemId: `item_lab_${identityDigest}`, }; this.#providerRunBinding = providerRunBinding; + // PRP run-attach metadata binds server validation, but ACP providers receive + // only these system instructions. Make the same immutable contract visible + // without expanding the model's separately governed mock task authority. + const baseInstructions = [ + this.#transportOptions.baseInstructions ?? LIVE_BASE_INSTRUCTIONS, + "", + "Native completion report contract: " + JSON.stringify(LIVE_COMPLETION_CONTRACT), + "For paperclip_finish or paperclip_block, use that exact contract revision and criterion IDs. These tools report the current provider run result; they do not change mock task state. Mock task mutations still require separately authorized semantic tools and an explicit user request.", + ].join("\n"); + const authorizedTools = this.#dispatcher.listTools(this.#authority.runId); + const tools = (this.#config.toolExposure ?? "eager") === "lazy" + ? authorizedTools.filter((tool) => tool.annotations.exposure === "always") + : authorizedTools; + const semanticTools = [ + ...tools.map(dynamicToolSpec), + ...((this.#config.toolExposure ?? "eager") === "lazy" ? discoveryToolSpecs() : []), + ]; const transportBundle = this.#transportFactory({ ...this.#transportOptions, provider, @@ -2317,6 +2404,7 @@ export class CapabilityLiveSession { : {}), lifecyclePolicy: this.#config.lifecyclePolicy ?? { mode: "per_turn", idleTimeoutMs: null }, resumeActiveTurnId: resume ? this.#activeTurnId : null, + ...(resume ? { resumeDynamicTools: semanticTools } : {}), stateDirectory: resolve(this.#config.workingDirectory, ".paperclip-runner-prp", identityDigest), prpIdentity: { runnerInstanceId: `runner_lab_${identityDigest}`, @@ -2349,10 +2437,7 @@ export class CapabilityLiveSession { capabilities: { experimentalApi: true, requestAttestation: false }, }); this.#transport.notify("initialized"); - const authorizedTools = this.#dispatcher.listTools(this.#authority.runId); - const tools = (this.#config.toolExposure ?? "eager") === "lazy" - ? authorizedTools.filter((tool) => tool.annotations.exposure === "always") - : authorizedTools; + this.#appendEvidence("tool_exposure", null, { operationIds: tools.map((tool) => tool.name), scenarioId: this.#config.scenario.id, @@ -2394,9 +2479,7 @@ export class CapabilityLiveSession { config: createSkilllessCodexThreadConfig(this.#config.workingDirectory), permissions: CODEX_PERMISSION_PROFILE, runtimeWorkspaceRoots: [this.#config.workingDirectory], - baseInstructions: - [this.#transportOptions.baseInstructions ?? LIVE_BASE_INSTRUCTIONS, - `For native paperclip_finish/paperclip_block reports, completionContract=${JSON.stringify(LIVE_COMPLETION_CONTRACT)}. This report does not change mock task state; use the exposed semantic tools for task changes.`].join("\n"), + baseInstructions, persistExtendedHistory: true, }); const resumedThread = record(resumed.thread); @@ -2421,14 +2504,9 @@ export class CapabilityLiveSession { permissions: CODEX_PERMISSION_PROFILE, runtimeWorkspaceRoots: [this.#config.workingDirectory], approvalPolicy: "never", - baseInstructions: - [this.#transportOptions.baseInstructions ?? LIVE_BASE_INSTRUCTIONS, - `For native paperclip_finish/paperclip_block reports, completionContract=${JSON.stringify(LIVE_COMPLETION_CONTRACT)}. This report does not change mock task state; use the exposed semantic tools for task changes.`].join("\n"), + baseInstructions, completionContract: LIVE_COMPLETION_CONTRACT, - dynamicTools: [ - ...tools.map(dynamicToolSpec), - ...((this.#config.toolExposure ?? "eager") === "lazy" ? discoveryToolSpecs() : []), - ], + dynamicTools: [...semanticTools, ...codexSemanticToolSpecs()], experimentalRawEvents: true, persistExtendedHistory: true, }); @@ -2519,10 +2597,28 @@ export class CapabilityLiveSession { contentItems: [{ type: "inputText", text: "Tool call was outside the active Capability thread and turn." }], }; } - const runResult = liveRunResultFeedback(operationId, request.params.arguments, LIVE_COMPLETION_CONTRACT.revision); - if (runResult) { - this.#appendEvidence("tool_call", turnId, { callId, operationId, input: jsonValue(request.params.arguments), beforeRevision: this.#port.snapshot().revision }); - this.#appendEvidence("tool_result", turnId, { callId, operationId, result: jsonValue(runResult), beforeRevision: this.#port.snapshot().revision, afterRevision: this.#port.snapshot().revision }); + if (isCodexSemanticTool(operationId)) { + // Native completion is an advisory run report. It cannot mutate the mock + // task, bypass semantic claims, or substitute for provider turn settlement. + if (terminalReplay || !isRetainableCodexPayload(request.params.arguments)) { + return rejectedCodexToolCall("Native completion requires a bounded active-turn report."); + } + const validation = validateCodexResultProposal(request.params.arguments, LIVE_COMPLETION_ENVELOPE); + if (validation.status !== "accepted" || !codexToolAcceptsResult(operationId, validation.result)) { + return rejectedCodexToolCall("Native completion must match the current completion contract and tool disposition."); + } + const result = jsonValue(validation.result) as Record; + if (this.#semanticResult !== null && JSON.stringify(this.#semanticResult) !== JSON.stringify(result)) { + return rejectedCodexToolCall("A different native completion report was already accepted for this turn."); + } + const runResult = liveRunResultFeedback(operationId, request.params.arguments, LIVE_COMPLETION_CONTRACT.revision); + if (!runResult?.ok) { + return rejectedCodexToolCall("Native completion does not satisfy the current run result contract."); + } + this.#semanticResult = result; + const revision = this.#port.snapshot().revision; + this.#appendEvidence("tool_call", turnId, { callId, operationId, input: jsonValue(request.params.arguments), beforeRevision: revision }); + this.#appendEvidence("tool_result", turnId, { callId, operationId, result: jsonValue(runResult), beforeRevision: revision, afterRevision: revision }); await this.#persist(); return this.#codexToolResponse(runResult); } diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts index 1baae36920..825e8dd5a1 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts @@ -33,7 +33,10 @@ import type { PrpTerminalState, } from "../protocol/replay-contract.js"; import { executeNativeSession } from "../native-session-runtime.js"; +import { redactCapabilityEvidenceData } from "./evidence-redaction.js"; import { NativeSessionCloseUnrecoverableError } from "../contracts/native-session-backend.js"; +import { parsePaperclipQuestionSet } from "../contracts/question-set.js"; +import { ACPX_CREDENTIAL_BINDING_ENV, createAcpxCredentialBinding, createAcpxSidecarHostEnvironment } from "../drivers/acpx/environment.js"; import { DurablePrpControlPlane } from "../control-plane/durable-prp-control-plane.js"; import * as durableControlPlane from "../control-plane/durable-prp-control-plane.js"; @@ -57,6 +60,7 @@ import { releaseMaterializedNativeRuntimeSkills } from "../drivers/runtime-conte import { RUNNERD_CANONICAL_ITEM } from "../drivers/codex/codex-driver-values.js"; import { + parseAcpxTurnControlCapabilities, authorizedToolSetForProvider, createCapabilityRunnerdCodexTransport, createCapabilityRunnerdProviderEnvironment, @@ -93,6 +97,20 @@ const defaultCapabilityRunnerdBinary = () => process.env.PAPERCLIP_ATTACH_TRANSITION_RUNNER ?? qualifiedCapabilityRunnerdBinary(); +it("attributes opened candidate sessions to the harness biller instead of the model vendor", () => { + const provider = runnerdLaunchProfileInternals.openedThreadModelProvider; + for (const model of ["gpt-5.6-luna[context=272k,reasoning=medium,fast=false]", "claude-sonnet-5"]) { + expect(provider("acpx", "cursor", model)).toBe("cursor"); + expect(provider("acpx", "copilot", model)).toBe("github"); + } + expect(provider("acpx", "pi", "openrouter/deepseek/deepseek-v4-flash-0731")).toBe("openrouter"); + expect(provider("acpx", "claude", "claude-sonnet-5")).toBe("anthropic"); + expect(provider("acpx", "codex", "gpt-5.6-sol")).toBe("openai"); + expect(provider("opencode", "codex", "openrouter/openai/gpt-6-astra")).toBe("openrouter"); + expect(provider("claude_managed", "codex", "claude-sonnet-5")).toBe("anthropic"); + expect(provider("aws_agentcore", "codex", "global.anthropic.claude-sonnet-4-6")).toBe("aws"); +}); + async function expectTurnStarted( notifications: AsyncIterator<{ method: string }>, ) { @@ -704,7 +722,14 @@ it.each(["after_budget", "within_budget", "persistence_failure"] as const)( const bundle = createCapabilityRunnerdCodexTransport({ runnerBinary: defaultCapabilityRunnerdBinary(), codexCommand: fakeCodex, - codexArgs: fakeCodexArgs(stateDirectory, "--split-event-burst"), + // Exercise a real output suffix around the held semantic callback. + // The default 96/48-frame stress burst spends seconds on unrelated + // durable text fsyncs before handler entry, consuming this barrier test's + // wall-clock budget under the full suite. Stress cases retain defaults. + codexArgs: fakeCodexArgs( + stateDirectory, "--split-event-burst", + "--split-event-prefix-count", "2", "--split-event-suffix-count", "2", + ), stateDirectory, closeGraceMs: 2_000, controlPlaneRegistration: async (authority) => { @@ -1681,6 +1706,10 @@ it("uses file-backed AWS workload identity without forwarding access keys or Pap }); it.each([ + { agent: "cursor" as const, allowed: ["CURSOR_API_KEY", "CURSOR_AUTH_TOKEN"], + denied: ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN", "OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"] }, + { agent: "copilot" as const, allowed: ["COPILOT_GITHUB_TOKEN"], + denied: ["CURSOR_API_KEY", "CURSOR_AUTH_TOKEN", "GH_TOKEN", "GITHUB_TOKEN", "OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"] }, { agent: "pi" as const, allowed: ["OPENROUTER_API_KEY"], @@ -1716,6 +1745,8 @@ it.each([ "passes only $agent ACPX credentials and the durable runtime binding", ({ agent, allowed, denied }) => { const credentialEnvironment: Record = { + CURSOR_API_KEY: "cursor-key-canary", CURSOR_AUTH_TOKEN: "cursor-token-canary", + COPILOT_GITHUB_TOKEN: "copilot-canary", GH_TOKEN: "ambient-gh-canary", GITHUB_TOKEN: "ambient-github-canary", OPENROUTER_API_KEY: "openrouter-canary", ANTHROPIC_API_KEY: "anthropic-canary", CLAUDE_CODE_OAUTH_TOKEN: "claude-oauth-canary", @@ -1732,6 +1763,7 @@ it.each([ environment: { PATH: "/bin", ...credentialEnvironment, + [ACPX_CREDENTIAL_BINDING_ENV]: "untrusted-caller-marker", PAPERCLIP_ACPX_BUILTIN_ROOT: "/attacker/builtin", PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT: "/attacker/package-root", PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST: @@ -1769,11 +1801,36 @@ it.each([ for (const key of allowed) expect(environment[key]).toBe(credentialEnvironment[key]); for (const key of denied) expect(environment[key]).toBeUndefined(); + if (["cursor", "copilot", "pi"].includes(agent)) { + expect(JSON.parse(environment[ACPX_CREDENTIAL_BINDING_ENV]!)).toEqual({ + schema: "paperclip.acpx_credential_binding.v1", agent, sessionId: "session-1", names: allowed, + }); + const sidecar = createAcpxSidecarHostEnvironment(environment, agent, "session-1"); + for (const key of allowed) expect(sidecar[key]).toBe(credentialEnvironment[key]); + } else expect(environment[ACPX_CREDENTIAL_BINDING_ENV]).toBeUndefined(); expect(environment.PAPERCLIP_API_KEY).toBeUndefined(); expect(environment.DATABASE_URL).toBeUndefined(); }, ); +it("does not bind ambient candidate credentials from an inherited or caller-supplied marker", () => { + const forged = createAcpxCredentialBinding({ CURSOR_API_KEY: "ambient-secret" }, "cursor", "session-1")!; + vi.stubEnv("CURSOR_API_KEY", "ambient-secret"); + vi.stubEnv(ACPX_CREDENTIAL_BINDING_ENV, forged); + try { + for (const explicitEnvironment of [undefined, { [ACPX_CREDENTIAL_BINDING_ENV]: forged }]) { + const environment = createCapabilityRunnerdProviderEnvironment({ + provider: "acpx", options: { provider: "acpx", acpxAgent: "cursor", environment: explicitEnvironment }, + identity: { runnerInstanceId: "runner-1", environmentLeaseId: "lease-1", runId: "run-1", normalizedSessionId: "session-1", turnId: "turn-1", itemId: "item-1" }, + codexHome: "/isolated/home", runtimeContextPath: "/isolated/context.json", hasRuntimeContext: false, + }); + expect(environment.CURSOR_API_KEY).toBeUndefined(); + expect(JSON.parse(environment[ACPX_CREDENTIAL_BINDING_ENV]!)).toMatchObject({ names: [] }); + expect(createAcpxSidecarHostEnvironment(environment, "cursor", "session-1").CURSOR_API_KEY).toBeUndefined(); + } + } finally { vi.unstubAllEnvs(); } +}); + it.each(["opencode", "acpx"] as const)( "advertises runner-managed planning through the %s provider boundary", async (provider) => { @@ -1902,6 +1959,35 @@ it("binds a durable semantic result to the active provider turn", () => { }); }); +it.each(["summary", "detail"])("preserves canonical reasoning %s deltas without publishing assistant text", (channel) => { + // Exact shape emitted by the Rust ACPX projector, with synthetic private text. + const payload = { provider: "acpx", itemId: "reason-1", kind: "reasoning", channel, + providerMethod: "runtime.event", text: "PRIVATE_REASONING_SENTINEL" }; + const method = runnerdCanonicalNotificationMethod("item.delta", payload); + expect(method).toBe(channel === "detail" ? "item/reasoning/textDelta" : "item/reasoning/summaryTextDelta"); + const params = rehydrateRunnerdDeltaNotification(payload, "provider-thread", "provider-turn"); + expect(params).toMatchObject({ threadId: "provider-thread", turnId: "provider-turn", itemId: "reason-1" }); + const evidence = redactCapabilityEvidenceData("provider_event", { method, params }); + expect(evidence).toEqual({ event: "reasoning_delta" }); + expect(JSON.stringify(evidence)).not.toContain("PRIVATE_REASONING_SENTINEL"); +}); + +it("classifies each coalesced canonical delta without mixing reasoning into assistant output", () => { + const notifications = expandRunnerdCanonicalNotifications("item/agentMessage/delta", { + coalescedCount: 3, + events: [ + { kind: "reasoning", channel: "summary", text: "PRIVATE_REASONING_SENTINEL" }, + { kind: "agentMessage", channel: "progress", text: "Visible progress" }, + { kind: "reasoning", channel: "detail", text: "PRIVATE_DETAIL_SENTINEL" }, + ], + }, "item.delta"); + expect(notifications.map((entry) => entry.method)).toEqual([ + "item/reasoning/summaryTextDelta", "item/agentMessage/delta", "item/reasoning/textDelta", + ]); + expect(notifications.filter((entry) => entry.method === "item/agentMessage/delta").map((entry) => entry.params.text)) + .toEqual(["Visible progress"]); +}); + it("restores provider identity and streamed text from a canonical delta", () => { expect(rehydrateRunnerdDeltaNotification({ text: "Reading Gmail", itemId: "message-1", turnId: "controller-turn" }, "root-thread", "provider-turn")) .toMatchObject({ threadId: "root-thread", turnId: "provider-turn", delta: "Reading Gmail", itemId: "message-1" }); @@ -3936,6 +4022,26 @@ it("bridges a runnerd-native question into the server request handler and resolv }, }); expect(methods).toContain("turn/completed"); + // A terminal notification alone does not prove that the question reached + // durable storage or that its answer and provider suffix settled. + await bundle.transport.close(); + const runnerState = JSON.parse(await readFile( + join(stateDirectory, "runner", "runner-state.json"), "utf8", + )); + expect(runnerState.lifecycle).toBe("suspended"); + const controlState = JSON.parse(await readFile( + join(stateDirectory, "control-plane", "control-plane-state.json"), "utf8", + )) as { + committedEvents: Array<{ eventType: string; envelope: { payload: { payload: { request?: { input: unknown } } } } }>; + commands: Array<{ type: string; status: string }>; + }; + const created = controlState.committedEvents.filter(event => event.eventType === "runtime_request.created"); + expect(created).toHaveLength(1); + const input = parsePaperclipQuestionSet(created[0]!.envelope.payload.payload.request!.input); + expect(input.questions[0]!.options![0]!.description).toBe("Deploy safely."); + expect(input.questions[1]!.options![0]).not.toHaveProperty("description"); + expect(controlState.commands.filter(command => command.type === "request.resolve")) + .toEqual([expect.objectContaining({ status: "completed" })]); } finally { await bundle.transport.close(); await rm(stateDirectory, { recursive: true, force: true }); @@ -7264,6 +7370,89 @@ it("resolves explicit skills to the remote provider home and rejects unassigned }); +it("admits only exact live Pi turn controls", () => { + expect(parseAcpxTurnControlCapabilities(undefined, "pi")).toEqual({ steering: false, queuedFollowUp: false }); + expect(parseAcpxTurnControlCapabilities({ steering: true, queuedFollowUp: true }, "pi")).toEqual({ steering: true, queuedFollowUp: true }); + for (const value of [null, [], {}, { steering: 1, queuedFollowUp: false }, { steering: true, queuedFollowUp: "true" }, { steering: true, queuedFollowUp: true, arbitrary: true }]) { + expect(() => parseAcpxTurnControlCapabilities(value, "pi")).toThrow("malformed"); + } + for (const agent of ["cursor", "copilot", "codex", "claude", undefined]) { + expect(() => parseAcpxTurnControlCapabilities({ steering: true, queuedFollowUp: false }, agent)).toThrow("cannot advertise"); + } +}); + +async function withPreparedOpenCodeCleanup(input: { + run: () => Promise; + closeSession: () => Promise; + closeTransport: () => Promise; + removeRoot: () => Promise; + evidence: () => Pick< + ReturnType["evidence"]>, + "runnerPid" | "runnerExited" | "runnerExitCode" | "runnerSignal" | "diagnostics" + >; +}): Promise { + const failures: { stage: string; error: unknown }[] = []; + for (const [stage, operation] of [ + ["run", input.run], + ["session.close", input.closeSession], + ["transport.close", input.closeTransport], + ["removeRoot", input.removeRoot], + ] as const) { + try { + await operation(); + } catch (error) { + failures.push({ stage, error }); + } + } + if (failures.length === 0) return; + const evidence = input.evidence(); + // Never dump stderr, environment values, prompts, paths, or arbitrary + // diagnostic text. Keep only bounded process/settlement facts alongside + // the original errors, so a finally failure cannot hide the primary one. + const summary = { + failedStages: failures.map(({ stage }) => stage), + runnerStarted: evidence.runnerPid !== null, + runnerExited: evidence.runnerExited, + runnerExitCode: evidence.runnerExitCode, + runnerSignalled: evidence.runnerSignal !== null, + diagnosticCount: evidence.diagnostics.length, + suspensionRejected: evidence.diagnostics.includes( + "runner did not prove durable suspension before checkpoint", + ), + }; + throw new AggregateError( + failures.map(({ error }) => error), + `Prepared OpenCode fixture failed: ${JSON.stringify(summary)}`, + { cause: failures[0]!.error }, + ); +} + +it.each([true, false])("preserves prepared OpenCode cleanup errors (primary failure: %s)", async (failRun) => { + const primary = new Error("prepared input assertion failed"); + const cleanup = new NativeSessionCloseUnrecoverableError(); + const calls: string[] = []; + const evidence = { + runnerPid: 123, + runnerExited: true, + runnerExitCode: 1, + runnerSignal: null, + diagnostics: ["Bearer fixture-secret", "runner did not prove durable suspension before checkpoint"], + }; + const failure = await withPreparedOpenCodeCleanup({ + run: async () => { calls.push("run"); if (failRun) throw primary; }, + closeSession: async () => { calls.push("session.close"); throw cleanup; }, + closeTransport: async () => { calls.push("transport.close"); }, + removeRoot: async () => { calls.push("removeRoot"); }, + evidence: () => evidence, + }).catch((error: unknown) => error); + expect(failure).toBeInstanceOf(AggregateError); + expect((failure as AggregateError).errors).toEqual(failRun ? [primary, cleanup] : [cleanup]); + expect((failure as AggregateError).cause).toBe(failRun ? primary : cleanup); + expect(calls).toEqual(["run", "session.close", "transport.close", "removeRoot"]); + expect((failure as Error).message).toContain('"suspensionRejected":true'); + expect((failure as Error).message).not.toContain("fixture-secret"); +}); + it("preserves prepared input through runnerd and the real OpenCode proxy boundary", async () => { const root = await mkdtemp(join(tmpdir(), "runnerd-prepared-opencode-")); // The qualified launch boundary unlinks its executable after exec. Use a @@ -7319,30 +7508,21 @@ it("preserves prepared input through runnerd and the real OpenCode proxy boundar task: { prompt: "Keep this request unchanged." }, completionContract: { revision: "prepared-v1", criteria: task.completionContract.criteria }, }); - let failure: unknown; - try { - session = await driver.openSession({ runId: "prepared-opencode", normalizedSessionId: "prepared-opencode", workingDirectory: root }); - await session.startTurn({ message: { role: "user", text: prepared } }); - for await (const event of session.events()) { - if (event.eventType === "turn.completed") break; - } - const sessionRoots = (await readdir(runtime, { withFileTypes: true })).filter((entry) => entry.isDirectory()); - expect(sessionRoots).toHaveLength(1); - const requests = (await readFile(join(runtime, sessionRoots[0]!.name, "data/fake-prompt-requests.ndjson"), "utf8")).trim().split("\n").map((line) => JSON.parse(line)); - expect(requests.map((request) => request.parts)).toEqual([[{ type: "text", text: prepared }]]); - } catch (error) { - failure = error; - } finally { - try { - await session?.close(); - await bundle.transport.close(); - } catch (error) { - // Preserve bootstrap failures when cleanup independently cannot suspend. - failure ??= error; - } - await rm(root, { recursive: true, force: true }); - } - if (failure) { - throw new Error(`${String(failure)}\n${bundle.evidence().diagnostics.join("\n")}`, { cause: failure }); - } + await withPreparedOpenCodeCleanup({ + run: async () => { + session = await driver.openSession({ runId: "prepared-opencode", normalizedSessionId: "prepared-opencode", workingDirectory: root }); + await session.startTurn({ message: { role: "user", text: prepared } }); + for await (const event of session.events()) { + if (event.eventType === "turn.completed") break; + } + const sessionRoots = (await readdir(runtime, { withFileTypes: true })).filter((entry) => entry.isDirectory()); + expect(sessionRoots).toHaveLength(1); + const requests = (await readFile(join(runtime, sessionRoots[0]!.name, "data/fake-prompt-requests.ndjson"), "utf8")).trim().split("\n").map((line) => JSON.parse(line)); + expect(requests.map((request) => request.parts)).toEqual([[{ type: "text", text: prepared }]]); + }, + closeSession: async () => { await session?.close(); }, + closeTransport: () => bundle.transport.close(), + removeRoot: () => rm(root, { recursive: true, force: true }), + evidence: () => bundle.evidence(), + }); }, 30_000); diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index 5de9203e64..2176cbd4d1 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -1,3 +1,4 @@ +import { isAcpxCanonicalInputMethod } from "../drivers/acpx/profile-extensions.js"; import { RunnerdTraceFrameIndex } from "./runnerd-trace-frame-index.js"; import { waitForWarmAttachmentReadiness } from "./warm-attachment-readiness.js"; import { codexExecutableReadOnlyRoots } from "../drivers/codex/codex-security-config.js"; @@ -38,7 +39,7 @@ import type { DurableRecoveryCommittedEvent, DurableRecoveryIdentity, } from "../contracts/durable-recovery.js"; -import type { NativeRunIdentity } from "../contracts/types.js"; +import type { NativeRunIdentity, NativeTurnControlCapabilities } from "../contracts/types.js"; import type { PrpEvent } from "../protocol/replay-contract.js"; import { NativeSessionCloseUnrecoverableError } from "../contracts/native-session-backend.js"; import type { @@ -60,7 +61,7 @@ import { resolveQualifiedAcpxProfile, type QualifiedAcpxAgent, } from "../drivers/acpx/qualified-profiles.js"; -import { createSanitizedAcpxSpawnInput } from "../drivers/acpx/environment.js"; +import { ACPX_CREDENTIAL_BINDING_ENV, createAcpxCredentialBinding, createSanitizedAcpxSpawnInput } from "../drivers/acpx/environment.js"; import { createSanitizedAwsAgentCoreEnvironment, createSanitizedClaudeManagedEnvironment, @@ -887,7 +888,7 @@ export function bridgedCodexQuestionParams( }; // ACPX has already normalized and bound these IDs in Rust. Reconstructing a // Codex form here would change option IDs and break the answer's return path. - if (method === "elicitation/create") { + if (isAcpxCanonicalInputMethod(method)) { return { ...common, questionSet, origin: request.origin, message: questionSet.description ?? questionSet.title ?? "A tool needs your input" }; } @@ -1078,6 +1079,8 @@ export interface CapabilityRunnerdCodexTransportOptions { provider?: "codex" | "opencode" | "claude_managed" | "aws_agentcore" | "acpx"; opencodePermissionMode?: NativeOpenCodePermissionMode; acpxAgent?: QualifiedAcpxAgent; + /** Explicit evaluation-only candidate selection, never derived from persisted session input. */ + acpxCandidateProfile?: "pi" | "cursor" | "copilot"; acpxPermissionMode?: NativeAcpxPermissionMode; acpxPermissionModePinned?: boolean; acpxSidecarPath?: string; @@ -1325,9 +1328,11 @@ export function expandRunnerdCanonicalNotifications( eventType?: string, ): Array<{ method: string; params: Record }> { const payload = record(input); - if (!Array.isArray(payload.events)) return [{ method, params: payload }]; - return payload.events.map((event) => { + const events = Array.isArray(payload.events) ? payload.events : [payload]; + return events.map((event) => { const params = record(event); + // A coalesced envelope can contain different event kinds. Classify each + // payload after expansion, never using the envelope's kind. return { method: eventType ? runnerdCanonicalNotificationMethod(eventType, params) ?? method @@ -3137,8 +3142,10 @@ export function createCapabilityRunnerdProviderEnvironment(input: { const providerPackageAuthority = acpxProviderPackageAuthority(sidecarPath); // This is the trusted runner/sidecar boundary. The provider sandbox still // uses createSanitizedAcpxSpawnInput and does not inherit gateway tokens. - const assignedGateway = input.options.acpxAgent === "pi" - ? null : nativeMcpLaunchBinding(input.options.environment ?? {}); + const assignedGateway = nativeMcpLaunchBinding(input.options.environment ?? {}); + const credentialBinding = createAcpxCredentialBinding( + input.options.environment, input.options.acpxAgent ?? "codex", input.identity.normalizedSessionId, + ); return { ...(assignedGateway ? { PAPERCLIP_NATIVE_MCP_TOKEN: assignedGateway.token, @@ -3147,6 +3154,7 @@ export function createCapabilityRunnerdProviderEnvironment(input: { input.options.environment, input.options.acpxAgent ?? "codex", ).env, + ...(credentialBinding === undefined ? {} : { [ACPX_CREDENTIAL_BINDING_ENV]: credentialBinding }), ...(input.options.acpxAgent === "grok" && input.options.environment?.PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET ? { PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET: input.options.environment.PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET } : {}), ...commonIdentity, @@ -3316,6 +3324,27 @@ export function unwrapToolResponse(response: Record, preserveEn }; } +/** Only a live, admitted Pi ACP session can enable native turn controls. */ +export function parseAcpxTurnControlCapabilities( + value: unknown, + agent: unknown, +): NativeTurnControlCapabilities { + const unsupported = { steering: false, queuedFollowUp: false }; + if (value === undefined) return unsupported; + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error("ACPX turn control capabilities are malformed"); + } + const controls = value as Record; + if (Object.keys(controls).some(key => key !== "steering" && key !== "queuedFollowUp") + || typeof controls.steering !== "boolean" || typeof controls.queuedFollowUp !== "boolean") { + throw new Error("ACPX turn control capabilities are malformed"); + } + if (agent !== "pi" && (controls.steering || controls.queuedFollowUp)) { + throw new Error("ACPX profile cannot advertise these turn controls"); + } + return { steering: controls.steering, queuedFollowUp: controls.queuedFollowUp }; +} + class DurablePrpCodexTransport implements CodexAppServerTransport { readonly #root: string; readonly #ownsRoot: boolean; @@ -3348,6 +3377,13 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { #threadId = ""; #sessionId: string | null = null; #providerIdentity: Record | null = null; + #turnControls: NativeTurnControlCapabilities = { steering: false, queuedFollowUp: false }; + + turnControlCapabilities(): NativeTurnControlCapabilities | null { + if (this.options.provider !== "acpx") return null; + if (this.#closed || this.#failure) return { steering: false, queuedFollowUp: false }; + return { ...this.#turnControls }; + } #providerIdentityEventType: "harness.ready" | "session.started" | "session.resumed" | null = null; #checkpointProviderIdentityExpectation: { @@ -3387,14 +3423,16 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { readonly #traceFrameIndex = new RunnerdTraceFrameIndex(); #pendingTraceRehydrations: PendingTraceRehydration[] = []; #pendingDriverTraceInterpretations: PendingDriverTraceInterpretation[] = []; - readonly #bridgedRuntimeInputs = new Map(); + readonly #bridgedRuntimeInputs = new Map(); constructor(readonly options: CapabilityRunnerdCodexTransportOptions) { if (options.adoptExistingRunner && !options.stateDirectory?.trim()) { throw new Error("native_adopted_runner_state_directory_required"); } - if (options.provider === "acpx" && options.acpxAgent === "pi") { - throw new Error("The Pi ACPX profile is not available"); + if (options.provider === "acpx" && options.acpxAgent !== undefined + && ["pi", "cursor", "copilot"].includes(options.acpxAgent) + && options.acpxCandidateProfile !== options.acpxAgent) { + throw new Error("The candidate ACPX profile requires explicit evaluation opt-in"); } this.#failureSignal = new Promise((_resolve, reject) => { this.#rejectFailureSignal = reject; @@ -3508,6 +3546,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { ? params.expectedTurnId : this.#turnId; if (!text.trim()) throw new Error("turn/steer requires a message"); + if (params.mode !== undefined && params.mode !== "steer" && params.mode !== "follow_up") throw new Error("turn/steer mode is invalid"); if (expectedTurnId !== this.#turnId) throw new Error("turn/steer named a stale turn"); const correlationId = @@ -3520,6 +3559,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { text, turnId: this.#durableTurnId, providerTurnId: expectedTurnId, + ...(params.mode === "follow_up" ? { mode: "follow_up" } : {}), ...(correlationId ? { correlationId } : {}), }, correlationId, @@ -3899,7 +3939,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { throw new Error( `PRP runtime request ${input.requestId} is no longer pending`, ); - if (!("response" in input.resolution)) { + if (!pending.permission && !("response" in input.resolution) && input.resolution.action !== "cancel" && input.resolution.action !== "decline") { throw new Error( "runnerd-native runtime requests require a canonical question response", ); @@ -3913,7 +3953,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { { requestId: input.requestId, turnId: pending.durableTurnId, - response: input.resolution.response, + ...("response" in input.resolution ? { response: input.resolution.response } : { resolution: input.resolution }), }, commandId, ); @@ -4560,6 +4600,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { runId: identity.runId, cwd: String(params.cwd ?? tmpdir()), instructions: baseInstructions, + providerPolicy: { readOnly: params.permissions === "paperclip-runner-workspace-read-only" }, permissionMode: resolveRunnerdAcpxPermissionMode( this.options.acpxPermissionMode, ), @@ -4769,20 +4810,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { ? {} : { providerIdentity: structuredClone(this.#providerIdentity) }), model: params.model, - modelProvider: - provider === "opencode" && typeof params.model === "string" - ? params.model.split("/", 1)[0] - : provider === "claude_managed" - ? "anthropic" - : provider === "aws_agentcore" - ? "aws" - : provider === "acpx" - ? acpxAgent === "pi" - ? "openrouter" - : acpxAgent === "grok" ? "xai" : acpxAgent === "claude" - ? "anthropic" - : "openai" - : "openai", + modelProvider: openedThreadModelProvider(provider, acpxAgent, params.model), }, }; } @@ -5928,6 +5956,12 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { this.#applyProviderIdentityEvent(event); continue; } + if (event.eventType === "session.capabilities.updated" && this.options.provider === "acpx") { + const capabilities = record(record(event.envelope.payload).payload); + if (capabilities.turnControls !== undefined) { + this.#turnControls = parseAcpxTurnControlCapabilities(capabilities.turnControls, this.#evidence.acpxAgent); + } + } if (event.eventType === "harness.diagnostic") { const diagnostic = record(record(event.envelope.payload).payload); if ( @@ -5951,7 +5985,13 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { : ""; const origin = record(normalizedRequest.origin); const method = typeof origin.method === "string" ? origin.method : ""; - const params = bridgedCodexQuestionParams( + const permission = normalizedRequest.type === "permission" && normalizedRequest.requestKind === "permission_approval" + && method === "session/request_permission"; + const params = permission ? { + threadId: this.#threadId, turnId: this.#turnId, + itemId: normalizedRequest.itemId, reason: normalizedRequest.prompt, + choices: normalizedRequest.choices, origin, + } : bridgedCodexQuestionParams( normalizedRequest, method, this.#threadId, @@ -5963,10 +6003,11 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { (method === "item/tool/requestUserInput" || method === "tool/requestUserInput" || method === "mcpServer/elicitation/request" || - method === "elicitation/create") && + isAcpxCanonicalInputMethod(method) || permission) && !this.#bridgedRuntimeInputs.has(requestId) ) { this.#bridgedRuntimeInputs.set(requestId, { + permission, durableTurnId: typeof event.envelope.turnId === "string" ? event.envelope.turnId @@ -6201,6 +6242,12 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { } = resolveRunnerdSessionIdentity(started); const providerIdentity = record(started.providerIdentity); this.#confirmCheckpointProviderIdentity(started, event.eventType); + if (this.options.provider === "acpx") { + if (descriptor.turnControls !== undefined && descriptor.agent !== (this.options.acpxAgent ?? "codex")) { + throw new Error("ACPX capability identity differs from the admitted profile"); + } + this.#turnControls = parseAcpxTurnControlCapabilities(descriptor.turnControls, descriptor.agent); + } if (pid !== null) { this.#evidence.providerPid = pid; this.#evidence.providerProcessStartedAt = readLocalProcessStartedAt(pid); @@ -6221,7 +6268,9 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { if ( descriptor.agent === "pi" || descriptor.agent === "claude" || - descriptor.agent === "codex" + descriptor.agent === "codex" || + descriptor.agent === "cursor" || + descriptor.agent === "copilot" ) this.#evidence.acpxAgent = descriptor.agent; if (typeof descriptor.agentServerVersion === "string") @@ -6661,7 +6710,27 @@ export function createCapabilityRunnerdCodexTransport( export const createRunnerdCodexTransport = createCapabilityRunnerdCodexTransport; +/** The transport provider identifies the biller, independently of model family. */ +function openedThreadModelProvider( + provider: CapabilityRunnerdCodexTransportOptions["provider"], + acpxAgent: QualifiedAcpxAgent, + model: unknown, +): string { + if (provider === "opencode" && typeof model === "string") return model.split("/", 1)[0]!; + if (provider === "claude_managed") return "anthropic"; + if (provider === "aws_agentcore") return "aws"; + if (provider === "acpx") { + if (acpxAgent === "pi") return "openrouter"; + if (acpxAgent === "grok") return "xai"; + if (acpxAgent === "claude") return "anthropic"; + if (acpxAgent === "cursor") return "cursor"; + if (acpxAgent === "copilot") return "github"; + } + return "openai"; +} + export const runnerdLaunchProfileInternals = Object.freeze({ + openedThreadModelProvider, acpxProviderPackageAuthority, acpxRunnerLaunchProfile, resolveBuildOwnedCliArtifact, diff --git a/packages/paperclip-runner/src/protocol/generated/schema-bundle.ts b/packages/paperclip-runner/src/protocol/generated/schema-bundle.ts index aac57ca6c9..7e3e42a1eb 100644 --- a/packages/paperclip-runner/src/protocol/generated/schema-bundle.ts +++ b/packages/paperclip-runner/src/protocol/generated/schema-bundle.ts @@ -650,7 +650,9 @@ export const providerDescriptorSchema = { "enum": [ "pi", "claude", - "codex" + "codex", + "cursor", + "copilot" ] }, "requestedModel": { @@ -691,6 +693,9 @@ export const providerDescriptorSchema = { "null" ], "maxLength": 240 + }, + "turnControls": { + "$ref": "#/$defs/turnControls" } }, "allOf": [ @@ -868,6 +873,24 @@ export const providerDescriptorSchema = { } } ], + "$defs": { + "turnControls": { + "type": "object", + "required": [ + "steering", + "queuedFollowUp" + ], + "properties": { + "steering": { + "type": "boolean" + }, + "queuedFollowUp": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, "additionalProperties": false } as const; @@ -1820,6 +1843,65 @@ export const providerEventSchema = { }, "summary": { "$ref": "#/$defs/shortText" + }, + "provenance": { + "type": "object", + "required": [ + "method", + "eventType", + "sessionId", + "turnId" + ], + "properties": { + "method": { + "type": "string", + "maxLength": 160 + }, + "eventType": { + "type": "string", + "maxLength": 160 + }, + "sessionId": { + "type": "string", + "maxLength": 240 + }, + "turnId": { + "type": "string", + "maxLength": 240 + }, + "agentId": { + "type": "string", + "maxLength": 240 + }, + "timestamp": { + "type": "string", + "maxLength": 80 + } + }, + "additionalProperties": false + }, + "details": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 160 + }, + "value": { + "type": "string", + "maxLength": 4000 + } + }, + "additionalProperties": false + } } }, "additionalProperties": false @@ -2628,7 +2710,7 @@ export const questionSetSchema = { }, "description": { "type": "string", - "maxLength": 4000 + "maxLength": 100000 }, "submitLabel": { "type": "string", @@ -2920,6 +3002,181 @@ export const requestSchema = { "$id": "https://paperclip.dev/schemas/prp/v1/request.schema.json", "title": "PRP request", "oneOf": [ + { + "type": "object", + "required": [ + "schema", + "requestKind", + "requestId", + "type", + "status", + "prompt", + "choices", + "origin", + "turnId" + ], + "properties": { + "schema": { + "const": "paperclip.runtime_request.v2" + }, + "requestKind": { + "const": "permission_approval" + }, + "requestId": { + "type": "string", + "minLength": 1, + "maxLength": 160 + }, + "type": { + "const": "permission" + }, + "status": { + "enum": [ + "pending", + "resolved", + "expired", + "cancelled" + ] + }, + "prompt": { + "type": "string", + "minLength": 1, + "maxLength": 4000 + }, + "choices": { + "type": "array", + "minItems": 1, + "maxItems": 4, + "items": { + "type": "object", + "required": [ + "key", + "label" + ], + "properties": { + "key": { + "enum": [ + "accept", + "accept_for_session", + "decline", + "cancel" + ] + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 500 + } + }, + "additionalProperties": false + }, + "allOf": [ + { + "contains": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "const": "accept" + } + } + }, + "minContains": 0, + "maxContains": 1 + }, + { + "contains": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "const": "accept_for_session" + } + } + }, + "minContains": 0, + "maxContains": 1 + }, + { + "contains": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "const": "decline" + } + } + }, + "minContains": 0, + "maxContains": 1 + }, + { + "contains": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "const": "cancel" + } + } + }, + "minContains": 0, + "maxContains": 1 + } + ] + }, + "details": { + "type": "object", + "additionalProperties": true + }, + "origin": { + "type": "object", + "required": [ + "adapter" + ], + "properties": { + "adapter": { + "type": "string", + "minLength": 1, + "maxLength": 160 + }, + "provider": { + "type": "string", + "minLength": 1, + "maxLength": 160 + }, + "method": { + "type": "string", + "minLength": 1, + "maxLength": 500 + } + }, + "additionalProperties": false + }, + "turnId": { + "type": "string", + "minLength": 1, + "maxLength": 240 + }, + "itemId": { + "type": [ + "string", + "null" + ], + "minLength": 1, + "maxLength": 240 + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -4383,6 +4640,9 @@ export const eventV2Schema = { "properties": { "sessionGoals": { "$ref": "https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability" + }, + "turnControls": { + "$ref": "https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json#/$defs/turnControls" } }, "additionalProperties": true diff --git a/packages/paperclip-runner/src/protocol/generated/standalone-validators.ts b/packages/paperclip-runner/src/protocol/generated/standalone-validators.ts index 3eba6e57ee..ba95b6a21f 100644 --- a/packages/paperclip-runner/src/protocol/generated/standalone-validators.ts +++ b/packages/paperclip-runner/src/protocol/generated/standalone-validators.ts @@ -4,4 +4,4 @@ import equalModule from "ajv/dist/runtime/equal.js"; import ucs2LengthModule from "ajv/dist/runtime/ucs2length.js"; -"use strict";export const fixtureValidator: unknown = validate20;const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/fixture.schema.json","title":"PRP scripted replay fixture","type":"object","required":["schema","fixtureVersion","protocolVersion","name","description","identity","capabilities","commands","events","result"],"properties":{"schema":{"const":"paperclip.prp.fixture.v1"},"fixtureVersion":{"const":1},"protocolVersion":{"enum":[1,2]},"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","minLength":1,"maxLength":1000},"identity":{"$ref":"https://paperclip.dev/schemas/prp/v1/identity.schema.json"},"capabilities":{"oneOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json"},{"$ref":"https://paperclip.dev/schemas/prp/v2/capabilities.schema.json"}]},"commands":{"type":"array","items":{"oneOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/command.schema.json"},{"$ref":"https://paperclip.dev/schemas/prp/v2/command.schema.json"}]}},"events":{"type":"array","minItems":1,"items":{"oneOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/event.schema.json"},{"$ref":"https://paperclip.dev/schemas/prp/v2/event.schema.json"}]}},"requests":{"type":"array","items":{"$ref":"https://paperclip.dev/schemas/prp/v1/request.schema.json"}},"result":{"$ref":"https://paperclip.dev/schemas/prp/v1/result.schema.json"}},"additionalProperties":true};const schema41 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json","title":"PRP negotiated capabilities","type":"object","required":["schema","sessionReusePolicy","driver","steer","interrupt","resume","runtimeRequests","structuredResult","typedEvents"],"properties":{"schema":{"const":"paperclip.prp.capabilities.v1"},"sessionReusePolicy":{"enum":["new_per_run","reuse_per_issue","reuse_per_workspace"]},"driver":{"type":"object","required":["kind","version"],"properties":{"kind":{"type":"string","minLength":1,"maxLength":80},"version":{"type":"string","minLength":1,"maxLength":80}},"additionalProperties":true},"steer":{"type":"boolean"},"interrupt":{"type":"boolean"},"resume":{"type":"boolean"},"runtimeRequests":{"type":"boolean"},"structuredResult":{"type":"boolean"},"typedEvents":{"type":"boolean"},"typedEventFamilies":{"type":"array","maxItems":64,"items":{"type":"object","required":["family","version","availability","detailLevel"],"properties":{"family":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z][a-z0-9_.-]*$"},"version":{"const":1},"availability":{"enum":["available","unsupported","policy_disabled"]},"detailLevel":{"enum":["summary","structured"]}},"additionalProperties":false}},"semanticTools":{"type":"object","required":["schema","schemaVersion","operations"],"properties":{"schema":{"const":"paperclip.prp.semantic_tools.v1"},"schemaVersion":{"const":1},"operations":{"type":"array","items":{"type":"object","required":["operationId","version","availability","redactionDisposition"],"properties":{"operationId":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"},"version":{"const":1},"availability":{"enum":["available","denied","unsupported"]},"redactionDisposition":{"const":"digest_only"},"requiredClaims":{"type":"array","items":{"type":"string","minLength":1,"maxLength":160},"uniqueItems":true},"reasonCode":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z][a-z0-9_.:-]*$"}},"additionalProperties":true}}},"additionalProperties":true},"unsupported":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true}},"additionalProperties":true};const schema45 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/command.schema.json","title":"PRP runner command","type":"object","required":["schema","commandId","controllerSeq","type","issuedAt","payload"],"properties":{"schema":{"const":"paperclip.prp.command.v1"},"commandId":{"type":"string","minLength":1,"maxLength":160},"controllerSeq":{"type":"integer","minimum":1},"type":{"enum":["run.prepare","run.attach","session.open","turn.start","turn.steer","turn.interrupt","turn.stop","request.resolve","interaction.receipt","semantic_tool.result","session.snapshot","session.close","session.budget.increase","session.destroy","run.cancel","runner.drain","runner.suspend","runner.shutdown"]},"issuedAt":{"type":"string","format":"date-time"},"deadlineAt":{"type":"string","format":"date-time"},"precondition":{"type":"object","properties":{"runnerState":{"type":"array","items":{"type":"string"}},"runState":{"type":"array","items":{"type":"string"}},"sessionState":{"type":"array","items":{"type":"string"}},"activeTurnId":{"type":["string","null"]}},"additionalProperties":true},"payload":{"type":"object","additionalProperties":true}},"additionalProperties":true};const schema46 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v2/command.schema.json","title":"PRP v2 runner command","type":"object","required":["schema","commandId","controllerSeq","type","issuedAt","payload"],"properties":{"schema":{"const":"paperclip.prp.command.v2"},"commandId":{"type":"string","minLength":1,"maxLength":160},"controllerSeq":{"type":"integer","minimum":1},"type":{"enum":["run.prepare","run.attach","session.open","turn.start","turn.steer","turn.interrupt","turn.stop","request.resolve","interaction.receipt","semantic_tool.result","session.snapshot","session.close","session.budget.increase","session.destroy","run.cancel","runner.drain","runner.suspend","runner.shutdown","session.goal.get","session.goal.set","session.goal.clear"]},"issuedAt":{"type":"string","format":"date-time"},"deadlineAt":{"type":"string","format":"date-time"},"precondition":{"type":"object","properties":{"runnerState":{"type":"array","items":{"type":"string"}},"runState":{"type":"array","items":{"type":"string"}},"sessionState":{"type":"array","items":{"type":"string"}},"activeTurnId":{"type":["string","null"]}},"additionalProperties":true},"payload":{"type":"object","additionalProperties":true}},"allOf":[{"if":{"properties":{"type":{"const":"session.goal.set"}}},"then":{"properties":{"payload":{"type":"object","properties":{"requestId":{"type":"string","minLength":1,"maxLength":160},"objective":{"type":"string","minLength":1,"maxLength":4000},"status":{"enum":["active","paused"]},"tokenBudget":{"type":["integer","null"],"minimum":1}},"additionalProperties":true}}}}],"additionalProperties":true};const schema199 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/result.schema.json","title":"PRP structured run result","type":"object","required":["schema","reportedWorkDisposition","summary","completionClaim","evidence","verification","attentionRequests","artifacts"],"properties":{"schema":{"const":"paperclip.run_result.v1"},"reportedWorkDisposition":{"enum":["done","blocked","needs_review","yielded"]},"summary":{"type":"string","minLength":1,"maxLength":12000},"completionClaim":{"type":"object","required":["contractRevision","objectiveSatisfied","criteria","remainingWork"],"properties":{"contractRevision":{"type":"string","minLength":1},"objectiveSatisfied":{"type":"boolean"},"criteria":{"type":"array","items":{"type":"object","required":["criterionId","status","evidenceRefs"],"properties":{"criterionId":{"type":"string","minLength":1},"status":{"enum":["satisfied","not_satisfied","unknown"]},"evidenceRefs":{"type":"array","items":{"type":"string"}},"explanation":{"type":"string"}},"additionalProperties":true}},"remainingWork":{"type":"array","items":{"type":"object","required":["description","blocksCompletion"],"properties":{"description":{"type":"string","minLength":1},"blocksCompletion":{"type":"boolean"}},"additionalProperties":true}}},"additionalProperties":true},"evidence":{"type":"array","items":{"type":"object"}},"verification":{"type":"array","description":"Checks used to verify the work. Use failed only when the check actually ran and found a defect in the work. If a check could not run or complete because the environment, tool, dependency, permission, credential, policy, external service, or budget was unavailable, use not_run with reasonCode even when the attempted command exited non-zero.","items":{"type":"object","required":["commandOrCheck","status"],"properties":{"commandOrCheck":{"type":"string","minLength":1},"status":{"enum":["passed","failed","not_run"],"description":"passed means the check ran and succeeded; failed means it ran to a meaningful verdict and found the work incorrect; not_run means no meaningful verdict was possible because the check was not attempted or could not complete."},"reasonCode":{"description":"Required for not_run; identify why the check had no meaningful verdict.","enum":["environment_unavailable","tool_unavailable","dependency_missing","permission_denied","credential_missing","policy_restricted","external_service_unavailable","budget_exhausted","other"]},"detail":{"type":"string"},"artifactRef":{"type":"string"}},"additionalProperties":true}},"blocker":{"type":"object","required":["reasonCode","owner","unblockAction","scope"],"properties":{"reasonCode":{"type":"string","minLength":1},"owner":{"type":"object","required":["kind","name"],"properties":{"kind":{"enum":["agent","user","system","external"]},"name":{"type":"string","minLength":1}},"additionalProperties":true},"unblockAction":{"type":"string","minLength":1},"scope":{"enum":["current_track","task_wide"]}},"additionalProperties":true},"attentionRequests":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","minLength":1},"summary":{"type":"string","minLength":1},"ownerClass":{"enum":["human","agent","external_system"]},"targetAgentId":{"type":"string","minLength":1}},"additionalProperties":true}},"artifacts":{"type":"array","items":{"type":"object","required":["kind","ref"],"properties":{"kind":{"type":"string","minLength":1},"ref":{"type":"string","minLength":1},"title":{"type":"string"}},"additionalProperties":true}},"continuation":{"type":"object","required":["kind","summary","idempotencyKey"],"properties":{"kind":{"enum":["same_agent","retry","delegated_issue","response_wake","monitor"]},"summary":{"type":"string","minLength":1},"idempotencyKey":{"type":"string","minLength":1}},"additionalProperties":true}},"allOf":[{"if":{"properties":{"reportedWorkDisposition":{"const":"blocked"}}},"then":{"required":["blocker"]}},{"if":{"properties":{"reportedWorkDisposition":{"const":"yielded"}}},"then":{"required":["continuation"]}}],"additionalProperties":true};const func1 = typeof ucs2LengthModule === "function" ? ucs2LengthModule : ucs2LengthModule.default;const schema32 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/identity.schema.json","title":"PRP identity graph","type":"object","required":["schema","companyId","issueId","runId","environmentLeaseId","runnerInstanceId","normalizedSessionId","driverSessionId"],"properties":{"schema":{"const":"paperclip.prp.identity.v1"},"companyId":{"$ref":"#/$defs/stableId"},"issueId":{"$ref":"#/$defs/stableId"},"runId":{"$ref":"#/$defs/stableId"},"environmentLeaseId":{"$ref":"#/$defs/stableId"},"runnerInstanceId":{"$ref":"#/$defs/stableId"},"normalizedSessionId":{"$ref":"#/$defs/stableId"},"driverSessionId":{"$ref":"#/$defs/stableId"},"providerSessionId":{"$ref":"#/$defs/stableId"}},"additionalProperties":true,"$defs":{"stableId":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"}}};const schema33 = {"type":"string","minLength":1,"maxLength":160,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"};const pattern4 = new RegExp("^[A-Za-z0-9][A-Za-z0-9._:-]*$", "u");function validate21(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/identity.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate21.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.companyId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "companyId"},message:"must have required property '"+"companyId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.issueId === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "issueId"},message:"must have required property '"+"issueId"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.runId === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "runId"},message:"must have required property '"+"runId"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.environmentLeaseId === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "environmentLeaseId"},message:"must have required property '"+"environmentLeaseId"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.runnerInstanceId === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "runnerInstanceId"},message:"must have required property '"+"runnerInstanceId"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.normalizedSessionId === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "normalizedSessionId"},message:"must have required property '"+"normalizedSessionId"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.driverSessionId === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "driverSessionId"},message:"must have required property '"+"driverSessionId"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.schema !== undefined){if("paperclip.prp.identity.v1" !== data.schema){const err8 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.identity.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.companyId !== undefined){let data1 = data.companyId;if(typeof data1 === "string"){if(func1(data1) > 160){const err9 = {instancePath:instancePath+"/companyId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(func1(data1) < 1){const err10 = {instancePath:instancePath+"/companyId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(!pattern4.test(data1)){const err11 = {instancePath:instancePath+"/companyId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/companyId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.issueId !== undefined){let data2 = data.issueId;if(typeof data2 === "string"){if(func1(data2) > 160){const err13 = {instancePath:instancePath+"/issueId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data2) < 1){const err14 = {instancePath:instancePath+"/issueId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(!pattern4.test(data2)){const err15 = {instancePath:instancePath+"/issueId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}else {const err16 = {instancePath:instancePath+"/issueId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.runId !== undefined){let data3 = data.runId;if(typeof data3 === "string"){if(func1(data3) > 160){const err17 = {instancePath:instancePath+"/runId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(func1(data3) < 1){const err18 = {instancePath:instancePath+"/runId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(!pattern4.test(data3)){const err19 = {instancePath:instancePath+"/runId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}else {const err20 = {instancePath:instancePath+"/runId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.environmentLeaseId !== undefined){let data4 = data.environmentLeaseId;if(typeof data4 === "string"){if(func1(data4) > 160){const err21 = {instancePath:instancePath+"/environmentLeaseId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data4) < 1){const err22 = {instancePath:instancePath+"/environmentLeaseId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(!pattern4.test(data4)){const err23 = {instancePath:instancePath+"/environmentLeaseId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/environmentLeaseId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.runnerInstanceId !== undefined){let data5 = data.runnerInstanceId;if(typeof data5 === "string"){if(func1(data5) > 160){const err25 = {instancePath:instancePath+"/runnerInstanceId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(func1(data5) < 1){const err26 = {instancePath:instancePath+"/runnerInstanceId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(!pattern4.test(data5)){const err27 = {instancePath:instancePath+"/runnerInstanceId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}else {const err28 = {instancePath:instancePath+"/runnerInstanceId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data.normalizedSessionId !== undefined){let data6 = data.normalizedSessionId;if(typeof data6 === "string"){if(func1(data6) > 160){const err29 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(func1(data6) < 1){const err30 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(!pattern4.test(data6)){const err31 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}else {const err32 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}if(data.driverSessionId !== undefined){let data7 = data.driverSessionId;if(typeof data7 === "string"){if(func1(data7) > 160){const err33 = {instancePath:instancePath+"/driverSessionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(func1(data7) < 1){const err34 = {instancePath:instancePath+"/driverSessionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(!pattern4.test(data7)){const err35 = {instancePath:instancePath+"/driverSessionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}else {const err36 = {instancePath:instancePath+"/driverSessionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data.providerSessionId !== undefined){let data8 = data.providerSessionId;if(typeof data8 === "string"){if(func1(data8) > 160){const err37 = {instancePath:instancePath+"/providerSessionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(func1(data8) < 1){const err38 = {instancePath:instancePath+"/providerSessionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(!pattern4.test(data8)){const err39 = {instancePath:instancePath+"/providerSessionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/providerSessionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}}else {const err41 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}validate21.errors = vErrors;return errors === 0;}validate21.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema42 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v2/capabilities.schema.json","title":"PRP v2 negotiated capabilities","type":"object","required":["schema","sessionReusePolicy","driver","steer","interrupt","resume","runtimeRequests","structuredResult","typedEvents","sessionGoals"],"properties":{"schema":{"const":"paperclip.prp.capabilities.v2"},"sessionReusePolicy":{"enum":["new_per_run","reuse_per_issue","reuse_per_workspace"]},"driver":{"type":"object","required":["kind","version"],"properties":{"kind":{"type":"string","minLength":1,"maxLength":80},"version":{"type":"string","minLength":1,"maxLength":80}},"additionalProperties":true},"steer":{"type":"boolean"},"interrupt":{"type":"boolean"},"resume":{"type":"boolean"},"runtimeRequests":{"type":"boolean"},"structuredResult":{"type":"boolean"},"typedEvents":{"type":"boolean"},"sessionGoals":{"$ref":"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability"},"unsupported":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true}},"additionalProperties":true};const schema44 = {"type":"object","required":["availability","actions","autonomousUpdates","persistentAcrossResume","maxObjectiveChars","tokenBudgetControl","usageReporting"],"properties":{"availability":{"enum":["available","unsupported","policy_disabled"]},"actions":{"type":"array","items":{"enum":["set","pause","resume","clear"]},"uniqueItems":true},"autonomousUpdates":{"type":"boolean"},"persistentAcrossResume":{"type":"boolean"},"maxObjectiveChars":{"type":"integer","minimum":1,"maximum":4000},"tokenBudgetControl":{"type":"boolean"},"usageReporting":{"type":"boolean"},"reasonCode":{"type":"string","minLength":1,"maxLength":160},"reason":{"type":"string","minLength":1,"maxLength":1000}},"additionalProperties":true};const func0 = typeof equalModule === "function" ? equalModule : equalModule.default;function validate23(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v2/capabilities.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate23.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.sessionReusePolicy === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sessionReusePolicy"},message:"must have required property '"+"sessionReusePolicy"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.driver === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "driver"},message:"must have required property '"+"driver"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.steer === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "steer"},message:"must have required property '"+"steer"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.interrupt === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "interrupt"},message:"must have required property '"+"interrupt"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.resume === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "resume"},message:"must have required property '"+"resume"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.runtimeRequests === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "runtimeRequests"},message:"must have required property '"+"runtimeRequests"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.structuredResult === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "structuredResult"},message:"must have required property '"+"structuredResult"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.typedEvents === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "typedEvents"},message:"must have required property '"+"typedEvents"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data.sessionGoals === undefined){const err9 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sessionGoals"},message:"must have required property '"+"sessionGoals"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.schema !== undefined){if("paperclip.prp.capabilities.v2" !== data.schema){const err10 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.capabilities.v2"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.sessionReusePolicy !== undefined){let data1 = data.sessionReusePolicy;if(!(((data1 === "new_per_run") || (data1 === "reuse_per_issue")) || (data1 === "reuse_per_workspace"))){const err11 = {instancePath:instancePath+"/sessionReusePolicy",schemaPath:"#/properties/sessionReusePolicy/enum",keyword:"enum",params:{allowedValues: schema42.properties.sessionReusePolicy.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.driver !== undefined){let data2 = data.driver;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.kind === undefined){const err12 = {instancePath:instancePath+"/driver",schemaPath:"#/properties/driver/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(data2.version === undefined){const err13 = {instancePath:instancePath+"/driver",schemaPath:"#/properties/driver/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(data2.kind !== undefined){let data3 = data2.kind;if(typeof data3 === "string"){if(func1(data3) > 80){const err14 = {instancePath:instancePath+"/driver/kind",schemaPath:"#/properties/driver/properties/kind/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(func1(data3) < 1){const err15 = {instancePath:instancePath+"/driver/kind",schemaPath:"#/properties/driver/properties/kind/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}else {const err16 = {instancePath:instancePath+"/driver/kind",schemaPath:"#/properties/driver/properties/kind/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.version !== undefined){let data4 = data2.version;if(typeof data4 === "string"){if(func1(data4) > 80){const err17 = {instancePath:instancePath+"/driver/version",schemaPath:"#/properties/driver/properties/version/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(func1(data4) < 1){const err18 = {instancePath:instancePath+"/driver/version",schemaPath:"#/properties/driver/properties/version/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/driver/version",schemaPath:"#/properties/driver/properties/version/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}}else {const err20 = {instancePath:instancePath+"/driver",schemaPath:"#/properties/driver/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.steer !== undefined){if(typeof data.steer !== "boolean"){const err21 = {instancePath:instancePath+"/steer",schemaPath:"#/properties/steer/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data.interrupt !== undefined){if(typeof data.interrupt !== "boolean"){const err22 = {instancePath:instancePath+"/interrupt",schemaPath:"#/properties/interrupt/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data.resume !== undefined){if(typeof data.resume !== "boolean"){const err23 = {instancePath:instancePath+"/resume",schemaPath:"#/properties/resume/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data.runtimeRequests !== undefined){if(typeof data.runtimeRequests !== "boolean"){const err24 = {instancePath:instancePath+"/runtimeRequests",schemaPath:"#/properties/runtimeRequests/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.structuredResult !== undefined){if(typeof data.structuredResult !== "boolean"){const err25 = {instancePath:instancePath+"/structuredResult",schemaPath:"#/properties/structuredResult/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data.typedEvents !== undefined){if(typeof data.typedEvents !== "boolean"){const err26 = {instancePath:instancePath+"/typedEvents",schemaPath:"#/properties/typedEvents/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data.sessionGoals !== undefined){let data11 = data.sessionGoals;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){if(data11.availability === undefined){const err27 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "availability"},message:"must have required property '"+"availability"+"'"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data11.actions === undefined){const err28 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "actions"},message:"must have required property '"+"actions"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data11.autonomousUpdates === undefined){const err29 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "autonomousUpdates"},message:"must have required property '"+"autonomousUpdates"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(data11.persistentAcrossResume === undefined){const err30 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "persistentAcrossResume"},message:"must have required property '"+"persistentAcrossResume"+"'"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(data11.maxObjectiveChars === undefined){const err31 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "maxObjectiveChars"},message:"must have required property '"+"maxObjectiveChars"+"'"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(data11.tokenBudgetControl === undefined){const err32 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "tokenBudgetControl"},message:"must have required property '"+"tokenBudgetControl"+"'"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(data11.usageReporting === undefined){const err33 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "usageReporting"},message:"must have required property '"+"usageReporting"+"'"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(data11.availability !== undefined){let data12 = data11.availability;if(!(((data12 === "available") || (data12 === "unsupported")) || (data12 === "policy_disabled"))){const err34 = {instancePath:instancePath+"/sessionGoals/availability",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/availability/enum",keyword:"enum",params:{allowedValues: schema44.properties.availability.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}if(data11.actions !== undefined){let data13 = data11.actions;if(Array.isArray(data13)){const len0 = data13.length;for(let i0=0; i0 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data13[i1], data13[j0])){const err36 = {instancePath:instancePath+"/sessionGoals/actions",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/actions/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;break outer0;}}}}}else {const err37 = {instancePath:instancePath+"/sessionGoals/actions",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/actions/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data11.autonomousUpdates !== undefined){if(typeof data11.autonomousUpdates !== "boolean"){const err38 = {instancePath:instancePath+"/sessionGoals/autonomousUpdates",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/autonomousUpdates/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data11.persistentAcrossResume !== undefined){if(typeof data11.persistentAcrossResume !== "boolean"){const err39 = {instancePath:instancePath+"/sessionGoals/persistentAcrossResume",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/persistentAcrossResume/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data11.maxObjectiveChars !== undefined){let data17 = data11.maxObjectiveChars;if(!(((typeof data17 == "number") && (!(data17 % 1) && !isNaN(data17))) && (isFinite(data17)))){const err40 = {instancePath:instancePath+"/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if((typeof data17 == "number") && (isFinite(data17))){if(data17 > 4000 || isNaN(data17)){const err41 = {instancePath:instancePath+"/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/maximum",keyword:"maximum",params:{comparison: "<=", limit: 4000},message:"must be <= 4000"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}if(data17 < 1 || isNaN(data17)){const err42 = {instancePath:instancePath+"/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}}if(data11.tokenBudgetControl !== undefined){if(typeof data11.tokenBudgetControl !== "boolean"){const err43 = {instancePath:instancePath+"/sessionGoals/tokenBudgetControl",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/tokenBudgetControl/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data11.usageReporting !== undefined){if(typeof data11.usageReporting !== "boolean"){const err44 = {instancePath:instancePath+"/sessionGoals/usageReporting",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/usageReporting/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data11.reasonCode !== undefined){let data20 = data11.reasonCode;if(typeof data20 === "string"){if(func1(data20) > 160){const err45 = {instancePath:instancePath+"/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func1(data20) < 1){const err46 = {instancePath:instancePath+"/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data11.reason !== undefined){let data21 = data11.reason;if(typeof data21 === "string"){if(func1(data21) > 1000){const err48 = {instancePath:instancePath+"/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}if(func1(data21) < 1){const err49 = {instancePath:instancePath+"/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}else {const err50 = {instancePath:instancePath+"/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}else {const err51 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data.unsupported !== undefined){let data22 = data.unsupported;if(Array.isArray(data22)){const len1 = data22.length;for(let i2=0; i2 1){const indices0 = {};for(;i3--;){let item0 = data22[i3];if(typeof item0 !== "string"){continue;}if(typeof indices0[item0] == "number"){j1 = indices0[item0];const err54 = {instancePath:instancePath+"/unsupported",schemaPath:"#/properties/unsupported/uniqueItems",keyword:"uniqueItems",params:{i: i3, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i3+" are identical)"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;break;}indices0[item0] = i3;}}}else {const err55 = {instancePath:instancePath+"/unsupported",schemaPath:"#/properties/unsupported/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}validate23.errors = vErrors;return errors === 0;}validate23.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema47 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/event.schema.json","title":"PRP native event","type":"object","required":["schema","sourceEventId","sourceSeq","sourceInstanceId","sourceKind","runId","eventType","schemaVersion","priority","emittedAt","payload"],"properties":{"schema":{"const":"paperclip.prp.event.v1"},"sourceEventId":{"type":"string","minLength":1,"maxLength":160},"sourceSeq":{"type":"integer","minimum":1,"maximum":9007199254740991},"sourceInstanceId":{"type":"string","minLength":1,"maxLength":160},"sourceKind":{"enum":["runner","control_plane"]},"runId":{"type":"string","minLength":1,"maxLength":160},"normalizedSessionId":{"type":"string","minLength":1,"maxLength":160},"turnId":{"type":"string","minLength":1,"maxLength":240},"itemId":{"type":"string","minLength":1,"maxLength":240},"eventType":{"enum":["runner.connected","runner.reconnected","runner.reconciled","runner.disconnected","runner.draining","runner.backpressure","runner.suspending","runner.suspended","runner.stopped","runner.diagnostic","runtime.phase.changed","sandbox.metric","workspace.ready","workspace.change.updated","workspace.diff.recorded","workspace.file.referenced","harness.starting","harness.ready","harness.exited","harness.diagnostic","plan.updated","tool.execution.started","tool.execution.progressed","tool.execution.completed","research.started","research.progressed","research.completed","delegation.started","delegation.updated","delegation.completed","model.route.changed","model.verification.updated","context.compacted","artifact.viewed","artifact.generated","review.mode.changed","hook.started","hook.completed","memory.citation.referenced","safety.review.started","safety.review.completed","terminal.input.sent","wait.started","wait.completed","provider.notice.recorded","session.starting","session.started","session.resuming","session.resumed","session.reconciled","session.updated","session.closed","session.failed","turn.submitted","turn.accepted","turn.started","turn.completed","turn.failed","turn.interrupted","turn.cancelled","item.started","item.delta","item.completed","item.failed","usage.reported","semantic_tool.input","semantic_tool.result","semantic_tool.reconciled","mcp_app.discovered","mcp_app.resource.resolved","mcp_app.initializing","mcp_app.ready","mcp_app.tool_input","mcp_app.tool_result","mcp_app.action.requested","mcp_app.action.resolved","mcp_app.host_context.changed","mcp_app.failed","mcp_app.teardown","runtime_request.created","runtime_request.resolved","runtime_request.expired","runtime_request.cancelled","interaction.request.proposed","interaction.request.materialized","interaction.request.rejected","interaction.response.progressed","interaction.response.resolved","interaction.response.delivered","run.attached","run.detached","run.result.proposed","run.result.accepted","run.result.rejected","attention.request.proposed","attention.request.routed","attention.request.resolved","attention.request.expired","attention.request.superseded","work.assessment.recorded","issue.status.decision.recorded","issue.status.decision.applied","issue.status.decision.rejected","issue.status.decision.superseded","run.terminal"]},"schemaVersion":{"const":1},"priority":{"enum":[0,1,2]},"emittedAt":{"type":"string","format":"date-time"},"observedAt":{"type":"string","format":"date-time"},"payload":{"type":"object","properties":{"channel":{"enum":["progress","final","summary","detail","unknown"]},"providerPhase":{"type":"string"},"providerMethod":{"type":"string"}},"additionalProperties":true},"debug":{"type":"object","additionalProperties":true}},"allOf":[{"if":{"properties":{"eventType":{"const":"plan.updated"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/plan"}}}},{"if":{"properties":{"eventType":{"enum":["tool.execution.started","tool.execution.progressed","tool.execution.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/toolExecution"}}}},{"if":{"properties":{"eventType":{"enum":["research.started","research.progressed","research.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/research"}}}},{"if":{"properties":{"eventType":{"enum":["delegation.started","delegation.updated","delegation.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/delegation"}}}},{"if":{"properties":{"eventType":{"const":"model.route.changed"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/modelRoute"}}}},{"if":{"properties":{"eventType":{"const":"model.verification.updated"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/modelVerification"}}}},{"if":{"properties":{"eventType":{"const":"context.compacted"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/contextCompacted"}}}},{"if":{"properties":{"eventType":{"const":"artifact.viewed"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/artifactViewed"}}}},{"if":{"properties":{"eventType":{"const":"artifact.generated"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/artifactGenerated"}}}},{"if":{"properties":{"eventType":{"const":"review.mode.changed"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/reviewMode"}}}},{"if":{"properties":{"eventType":{"enum":["hook.started","hook.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/hook"}}}},{"if":{"properties":{"eventType":{"const":"memory.citation.referenced"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/memoryCitation"}}}},{"if":{"properties":{"eventType":{"enum":["safety.review.started","safety.review.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/safetyReview"}}}},{"if":{"properties":{"eventType":{"const":"terminal.input.sent"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/terminalInput"}}}},{"if":{"properties":{"eventType":{"enum":["wait.started","wait.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/wait"}}}},{"if":{"properties":{"eventType":{"const":"provider.notice.recorded"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/providerNotice"}}}},{"if":{"properties":{"eventType":{"const":"workspace.file.referenced"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json"}}}},{"if":{"properties":{"eventType":{"enum":["workspace.change.updated","workspace.diff.recorded"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json"}}}},{"if":{"properties":{"eventType":{"const":"workspace.diff.recorded"}}},"then":{"properties":{"payload":{"allOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json"},{"type":"object","properties":{"complete":{"const":true}}}]}}}},{"if":{"properties":{"eventType":{"enum":["semantic_tool.input","mcp_app.tool_input"]}}},"then":{"properties":{"payload":{"type":"object","properties":{"semantic_tool":{"allOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json"},{"type":"object","properties":{"phase":{"const":"input"}}}]}},"additionalProperties":true}}}},{"if":{"properties":{"eventType":{"enum":["semantic_tool.result","mcp_app.tool_result"]}}},"then":{"properties":{"payload":{"type":"object","properties":{"semantic_tool":{"allOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json"},{"type":"object","properties":{"phase":{"const":"result"}}}]}},"additionalProperties":true}}}},{"if":{"properties":{"eventType":{"const":"semantic_tool.reconciled"}}},"then":{"properties":{"payload":{"type":"object","properties":{"semantic_tool":{"allOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json"},{"type":"object","properties":{"phase":{"const":"reconciled"}}}]}},"additionalProperties":true}}}},{"if":{"properties":{"eventType":{"const":"run.terminal"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/terminal.schema.json"}}}},{"if":{"properties":{"eventType":{"const":"run.result.proposed"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/result.schema.json"}}}}],"additionalProperties":true};const schema171 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json","title":"Paperclip workspace file reference","type":"object","required":["schema","referenceId","source","path","displayName","mediaType","presentation","line","preview","previewTruncated","contentDigest"],"properties":{"schema":{"const":"paperclip.workspace.file_reference.v1"},"referenceId":{"type":"string","minLength":1,"maxLength":240},"source":{"enum":["harness_reported","runner_verified"]},"path":{"type":"string","minLength":1,"maxLength":1024,"pattern":"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},"displayName":{"type":"string","minLength":1,"maxLength":255},"mediaType":{"type":["string","null"],"maxLength":160},"presentation":{"enum":["document","code","image","generic"]},"line":{"type":["integer","null"],"minimum":1},"preview":{"type":["string","null"],"maxLength":262144},"previewTruncated":{"type":"boolean"},"contentDigest":{"oneOf":[{"type":"null"},{"type":"string","pattern":"^sha256:[a-f0-9]{64}$"}]}},"additionalProperties":false};const schema172 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json","title":"Paperclip workspace diff","type":"object","required":["schema","changeSetId","revision","source","complete","files","totals","patchArtifactRef"],"properties":{"schema":{"const":"paperclip.workspace.diff.v1"},"changeSetId":{"type":"string","minLength":1,"maxLength":200},"revision":{"type":"integer","minimum":1},"source":{"enum":["harness_reported","runner_verified"]},"complete":{"type":"boolean"},"files":{"type":"array","maxItems":2000,"items":{"type":"object","required":["path","operation","previousPath","additions","deletions","binary","diff"],"properties":{"path":{"type":"string","minLength":1,"maxLength":1024,"pattern":"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},"operation":{"enum":["create","modify","delete","rename","mode_change"]},"previousPath":{"oneOf":[{"type":"null"},{"type":"string","minLength":1,"maxLength":1024,"pattern":"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"}]},"additions":{"type":["integer","null"],"minimum":0},"deletions":{"type":["integer","null"],"minimum":0},"binary":{"type":"boolean"},"diff":{"type":["string","null"],"maxLength":262144}},"additionalProperties":false}},"totals":{"type":"object","required":["files","additions","deletions"],"properties":{"files":{"type":"integer","minimum":0},"additions":{"type":["integer","null"],"minimum":0},"deletions":{"type":["integer","null"],"minimum":0}},"additionalProperties":false},"patchArtifactRef":{"type":["string","null"],"maxLength":2048}},"additionalProperties":false};const schema49 = {"type":"object","description":"A complete provider-authored within-turn checklist snapshot. Consumers replace the prior snapshot with the same planId in PRP sourceSeq order; this is not Paperclip's durable Plan document.","required":["schema","planId","revision","steps","complete","syncStatus"],"properties":{"schema":{"const":"paperclip.plan.updated.v1"},"planId":{"$ref":"#/$defs/id"},"revision":{"type":"integer","minimum":1},"explanation":{"$ref":"#/$defs/nullableShortText"},"steps":{"type":"array","maxItems":256,"items":{"type":"object","required":["stepId","body","status"],"properties":{"stepId":{"$ref":"#/$defs/id"},"body":{"$ref":"#/$defs/shortText"},"status":{"enum":["pending","in_progress","completed","blocked"]}},"additionalProperties":false}},"complete":{"type":"boolean"},"syncStatus":{"description":"Legacy compatibility field. Within-turn checklist snapshots use not_applicable.","enum":["streaming","pending","synchronized","conflict","not_applicable"]},"documentRevision":{"description":"Legacy compatibility field. Within-turn checklist snapshots use null.","type":["integer","null"],"minimum":1}},"additionalProperties":false};const schema50 = {"type":"string","minLength":1,"maxLength":160,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"};const schema51 = {"type":["string","null"],"maxLength":4000};const schema53 = {"type":"string","maxLength":4000};function validate60(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate60.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.planId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "planId"},message:"must have required property '"+"planId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.revision === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "revision"},message:"must have required property '"+"revision"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.steps === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "steps"},message:"must have required property '"+"steps"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.complete === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "complete"},message:"must have required property '"+"complete"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.syncStatus === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "syncStatus"},message:"must have required property '"+"syncStatus"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema") || (key0 === "planId")) || (key0 === "revision")) || (key0 === "explanation")) || (key0 === "steps")) || (key0 === "complete")) || (key0 === "syncStatus")) || (key0 === "documentRevision"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.plan.updated.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.plan.updated.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.planId !== undefined){let data1 = data.planId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/planId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/planId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/planId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/planId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.revision !== undefined){let data2 = data.revision;if(!(((typeof data2 == "number") && (!(data2 % 1) && !isNaN(data2))) && (isFinite(data2)))){const err12 = {instancePath:instancePath+"/revision",schemaPath:"#/properties/revision/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if((typeof data2 == "number") && (isFinite(data2))){if(data2 < 1 || isNaN(data2)){const err13 = {instancePath:instancePath+"/revision",schemaPath:"#/properties/revision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}}if(data.explanation !== undefined){let data3 = data.explanation;if((typeof data3 !== "string") && (data3 !== null)){const err14 = {instancePath:instancePath+"/explanation",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(typeof data3 === "string"){if(func1(data3) > 4000){const err15 = {instancePath:instancePath+"/explanation",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}if(data.steps !== undefined){let data4 = data.steps;if(Array.isArray(data4)){if(data4.length > 256){const err16 = {instancePath:instancePath+"/steps",schemaPath:"#/properties/steps/maxItems",keyword:"maxItems",params:{limit: 256},message:"must NOT have more than 256 items"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}const len0 = data4.length;for(let i0=0; i0 160){const err21 = {instancePath:instancePath+"/steps/" + i0+"/stepId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data6) < 1){const err22 = {instancePath:instancePath+"/steps/" + i0+"/stepId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(!pattern4.test(data6)){const err23 = {instancePath:instancePath+"/steps/" + i0+"/stepId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/steps/" + i0+"/stepId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data5.body !== undefined){let data7 = data5.body;if(typeof data7 === "string"){if(func1(data7) > 4000){const err25 = {instancePath:instancePath+"/steps/" + i0+"/body",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/steps/" + i0+"/body",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data5.status !== undefined){let data8 = data5.status;if(!((((data8 === "pending") || (data8 === "in_progress")) || (data8 === "completed")) || (data8 === "blocked"))){const err27 = {instancePath:instancePath+"/steps/" + i0+"/status",schemaPath:"#/properties/steps/items/properties/status/enum",keyword:"enum",params:{allowedValues: schema49.properties.steps.items.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/steps/" + i0,schemaPath:"#/properties/steps/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}else {const err29 = {instancePath:instancePath+"/steps",schemaPath:"#/properties/steps/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data.complete !== undefined){if(typeof data.complete !== "boolean"){const err30 = {instancePath:instancePath+"/complete",schemaPath:"#/properties/complete/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data.syncStatus !== undefined){let data10 = data.syncStatus;if(!(((((data10 === "streaming") || (data10 === "pending")) || (data10 === "synchronized")) || (data10 === "conflict")) || (data10 === "not_applicable"))){const err31 = {instancePath:instancePath+"/syncStatus",schemaPath:"#/properties/syncStatus/enum",keyword:"enum",params:{allowedValues: schema49.properties.syncStatus.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}if(data.documentRevision !== undefined){let data11 = data.documentRevision;if((!(((typeof data11 == "number") && (!(data11 % 1) && !isNaN(data11))) && (isFinite(data11)))) && (data11 !== null)){const err32 = {instancePath:instancePath+"/documentRevision",schemaPath:"#/properties/documentRevision/type",keyword:"type",params:{type: schema49.properties.documentRevision.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if((typeof data11 == "number") && (isFinite(data11))){if(data11 < 1 || isNaN(data11)){const err33 = {instancePath:instancePath+"/documentRevision",schemaPath:"#/properties/documentRevision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}}else {const err34 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}validate60.errors = vErrors;return errors === 0;}validate60.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema54 = {"type":"object","required":["schema","executionId","transport","operation","status","output","outputBytes","outputTruncated","outputDigest"],"properties":{"schema":{"const":"paperclip.tool.execution.v1"},"executionId":{"$ref":"#/$defs/id"},"transport":{"enum":["process","mcp","dynamic","builtin"]},"operation":{"enum":["read","search","list","execute","edit","unknown"]},"name":{"$ref":"#/$defs/nullableShortText"},"target":{"$ref":"#/$defs/safePath"},"namespace":{"type":["string","null"],"maxLength":240},"readOnly":{"type":["boolean","null"]},"inputUpdated":{"type":"boolean"},"status":{"enum":["running","completed","failed","cancelled","interrupted"]},"durationMs":{"type":["integer","null"],"minimum":0},"exitCode":{"type":["integer","null"]},"progress":{"$ref":"#/$defs/nullableShortText"},"output":{"type":["string","null"],"maxLength":65536},"outputBytes":{"type":"integer","minimum":0},"outputTruncated":{"type":"boolean"},"outputDigest":{"type":["string","null"],"pattern":"^sha256:[a-f0-9]{64}$"}},"additionalProperties":false};const schema57 = {"type":["string","null"],"maxLength":4096,"pattern":"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"};const func58 = Object.prototype.hasOwnProperty;const pattern18 = new RegExp("^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$", "u");const pattern19 = new RegExp("^sha256:[a-f0-9]{64}$", "u");function validate62(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate62.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.executionId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "executionId"},message:"must have required property '"+"executionId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.transport === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "transport"},message:"must have required property '"+"transport"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.operation === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "operation"},message:"must have required property '"+"operation"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.status === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.output === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "output"},message:"must have required property '"+"output"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.outputBytes === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "outputBytes"},message:"must have required property '"+"outputBytes"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.outputTruncated === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "outputTruncated"},message:"must have required property '"+"outputTruncated"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.outputDigest === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "outputDigest"},message:"must have required property '"+"outputDigest"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}for(const key0 in data){if(!(func58.call(schema54.properties, key0))){const err9 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.schema !== undefined){if("paperclip.tool.execution.v1" !== data.schema){const err10 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.tool.execution.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.executionId !== undefined){let data1 = data.executionId;if(typeof data1 === "string"){if(func1(data1) > 160){const err11 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(func1(data1) < 1){const err12 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(!pattern4.test(data1)){const err13 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}else {const err14 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.transport !== undefined){let data2 = data.transport;if(!((((data2 === "process") || (data2 === "mcp")) || (data2 === "dynamic")) || (data2 === "builtin"))){const err15 = {instancePath:instancePath+"/transport",schemaPath:"#/properties/transport/enum",keyword:"enum",params:{allowedValues: schema54.properties.transport.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.operation !== undefined){let data3 = data.operation;if(!((((((data3 === "read") || (data3 === "search")) || (data3 === "list")) || (data3 === "execute")) || (data3 === "edit")) || (data3 === "unknown"))){const err16 = {instancePath:instancePath+"/operation",schemaPath:"#/properties/operation/enum",keyword:"enum",params:{allowedValues: schema54.properties.operation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.name !== undefined){let data4 = data.name;if((typeof data4 !== "string") && (data4 !== null)){const err17 = {instancePath:instancePath+"/name",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 4000){const err18 = {instancePath:instancePath+"/name",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}}if(data.target !== undefined){let data5 = data.target;if((typeof data5 !== "string") && (data5 !== null)){const err19 = {instancePath:instancePath+"/target",schemaPath:"#/$defs/safePath/type",keyword:"type",params:{type: schema57.type},message:"must be string,null"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data5 === "string"){if(func1(data5) > 4096){const err20 = {instancePath:instancePath+"/target",schemaPath:"#/$defs/safePath/maxLength",keyword:"maxLength",params:{limit: 4096},message:"must NOT have more than 4096 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(!pattern18.test(data5)){const err21 = {instancePath:instancePath+"/target",schemaPath:"#/$defs/safePath/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"+"\""};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}}if(data.namespace !== undefined){let data6 = data.namespace;if((typeof data6 !== "string") && (data6 !== null)){const err22 = {instancePath:instancePath+"/namespace",schemaPath:"#/properties/namespace/type",keyword:"type",params:{type: schema54.properties.namespace.type},message:"must be string,null"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(typeof data6 === "string"){if(func1(data6) > 240){const err23 = {instancePath:instancePath+"/namespace",schemaPath:"#/properties/namespace/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}if(data.readOnly !== undefined){let data7 = data.readOnly;if((typeof data7 !== "boolean") && (data7 !== null)){const err24 = {instancePath:instancePath+"/readOnly",schemaPath:"#/properties/readOnly/type",keyword:"type",params:{type: schema54.properties.readOnly.type},message:"must be boolean,null"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.inputUpdated !== undefined){if(typeof data.inputUpdated !== "boolean"){const err25 = {instancePath:instancePath+"/inputUpdated",schemaPath:"#/properties/inputUpdated/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data.status !== undefined){let data9 = data.status;if(!(((((data9 === "running") || (data9 === "completed")) || (data9 === "failed")) || (data9 === "cancelled")) || (data9 === "interrupted"))){const err26 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema54.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data.durationMs !== undefined){let data10 = data.durationMs;if((!(((typeof data10 == "number") && (!(data10 % 1) && !isNaN(data10))) && (isFinite(data10)))) && (data10 !== null)){const err27 = {instancePath:instancePath+"/durationMs",schemaPath:"#/properties/durationMs/type",keyword:"type",params:{type: schema54.properties.durationMs.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if((typeof data10 == "number") && (isFinite(data10))){if(data10 < 0 || isNaN(data10)){const err28 = {instancePath:instancePath+"/durationMs",schemaPath:"#/properties/durationMs/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}if(data.exitCode !== undefined){let data11 = data.exitCode;if((!(((typeof data11 == "number") && (!(data11 % 1) && !isNaN(data11))) && (isFinite(data11)))) && (data11 !== null)){const err29 = {instancePath:instancePath+"/exitCode",schemaPath:"#/properties/exitCode/type",keyword:"type",params:{type: schema54.properties.exitCode.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data.progress !== undefined){let data12 = data.progress;if((typeof data12 !== "string") && (data12 !== null)){const err30 = {instancePath:instancePath+"/progress",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(typeof data12 === "string"){if(func1(data12) > 4000){const err31 = {instancePath:instancePath+"/progress",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}}if(data.output !== undefined){let data13 = data.output;if((typeof data13 !== "string") && (data13 !== null)){const err32 = {instancePath:instancePath+"/output",schemaPath:"#/properties/output/type",keyword:"type",params:{type: schema54.properties.output.type},message:"must be string,null"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(typeof data13 === "string"){if(func1(data13) > 65536){const err33 = {instancePath:instancePath+"/output",schemaPath:"#/properties/output/maxLength",keyword:"maxLength",params:{limit: 65536},message:"must NOT have more than 65536 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}if(data.outputBytes !== undefined){let data14 = data.outputBytes;if(!(((typeof data14 == "number") && (!(data14 % 1) && !isNaN(data14))) && (isFinite(data14)))){const err34 = {instancePath:instancePath+"/outputBytes",schemaPath:"#/properties/outputBytes/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if((typeof data14 == "number") && (isFinite(data14))){if(data14 < 0 || isNaN(data14)){const err35 = {instancePath:instancePath+"/outputBytes",schemaPath:"#/properties/outputBytes/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}}if(data.outputTruncated !== undefined){if(typeof data.outputTruncated !== "boolean"){const err36 = {instancePath:instancePath+"/outputTruncated",schemaPath:"#/properties/outputTruncated/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data.outputDigest !== undefined){let data16 = data.outputDigest;if((typeof data16 !== "string") && (data16 !== null)){const err37 = {instancePath:instancePath+"/outputDigest",schemaPath:"#/properties/outputDigest/type",keyword:"type",params:{type: schema54.properties.outputDigest.type},message:"must be string,null"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(typeof data16 === "string"){if(!pattern19.test(data16)){const err38 = {instancePath:instancePath+"/outputDigest",schemaPath:"#/properties/outputDigest/pattern",keyword:"pattern",params:{pattern: "^sha256:[a-f0-9]{64}$"},message:"must match pattern \""+"^sha256:[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}}}else {const err39 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}validate62.errors = vErrors;return errors === 0;}validate62.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema59 = {"type":"object","required":["schema","researchId","action","status","sources"],"properties":{"schema":{"const":"paperclip.research.v1"},"researchId":{"$ref":"#/$defs/id"},"action":{"enum":["search","open_page","find_in_page","other"]},"status":{"enum":["running","completed","failed","cancelled"]},"query":{"$ref":"#/$defs/nullableShortText"},"url":{"$ref":"#/$defs/safeUrl"},"pattern":{"$ref":"#/$defs/nullableShortText"},"sources":{"type":"array","maxItems":64,"items":{"type":"object","required":["sourceId","title","url","snippet"],"properties":{"sourceId":{"$ref":"#/$defs/id"},"title":{"$ref":"#/$defs/shortText"},"url":{"$ref":"#/$defs/safeUrl"},"snippet":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false}}},"additionalProperties":false};const schema62 = {"type":["string","null"],"maxLength":8192,"pattern":"^https?://"};const pattern21 = new RegExp("^https?://", "u");function validate64(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate64.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.researchId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "researchId"},message:"must have required property '"+"researchId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.action === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "action"},message:"must have required property '"+"action"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.status === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.sources === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sources"},message:"must have required property '"+"sources"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema") || (key0 === "researchId")) || (key0 === "action")) || (key0 === "status")) || (key0 === "query")) || (key0 === "url")) || (key0 === "pattern")) || (key0 === "sources"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema !== undefined){if("paperclip.research.v1" !== data.schema){const err6 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.research.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.researchId !== undefined){let data1 = data.researchId;if(typeof data1 === "string"){if(func1(data1) > 160){const err7 = {instancePath:instancePath+"/researchId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func1(data1) < 1){const err8 = {instancePath:instancePath+"/researchId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!pattern4.test(data1)){const err9 = {instancePath:instancePath+"/researchId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/researchId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.action !== undefined){let data2 = data.action;if(!((((data2 === "search") || (data2 === "open_page")) || (data2 === "find_in_page")) || (data2 === "other"))){const err11 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema59.properties.action.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.status !== undefined){let data3 = data.status;if(!((((data3 === "running") || (data3 === "completed")) || (data3 === "failed")) || (data3 === "cancelled"))){const err12 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema59.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.query !== undefined){let data4 = data.query;if((typeof data4 !== "string") && (data4 !== null)){const err13 = {instancePath:instancePath+"/query",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 4000){const err14 = {instancePath:instancePath+"/query",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}}if(data.url !== undefined){let data5 = data.url;if((typeof data5 !== "string") && (data5 !== null)){const err15 = {instancePath:instancePath+"/url",schemaPath:"#/$defs/safeUrl/type",keyword:"type",params:{type: schema62.type},message:"must be string,null"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(typeof data5 === "string"){if(func1(data5) > 8192){const err16 = {instancePath:instancePath+"/url",schemaPath:"#/$defs/safeUrl/maxLength",keyword:"maxLength",params:{limit: 8192},message:"must NOT have more than 8192 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(!pattern21.test(data5)){const err17 = {instancePath:instancePath+"/url",schemaPath:"#/$defs/safeUrl/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}}if(data.pattern !== undefined){let data6 = data.pattern;if((typeof data6 !== "string") && (data6 !== null)){const err18 = {instancePath:instancePath+"/pattern",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(typeof data6 === "string"){if(func1(data6) > 4000){const err19 = {instancePath:instancePath+"/pattern",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}}if(data.sources !== undefined){let data7 = data.sources;if(Array.isArray(data7)){if(data7.length > 64){const err20 = {instancePath:instancePath+"/sources",schemaPath:"#/properties/sources/maxItems",keyword:"maxItems",params:{limit: 64},message:"must NOT have more than 64 items"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}const len0 = data7.length;for(let i0=0; i0 160){const err26 = {instancePath:instancePath+"/sources/" + i0+"/sourceId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(func1(data9) < 1){const err27 = {instancePath:instancePath+"/sources/" + i0+"/sourceId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(!pattern4.test(data9)){const err28 = {instancePath:instancePath+"/sources/" + i0+"/sourceId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}else {const err29 = {instancePath:instancePath+"/sources/" + i0+"/sourceId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data8.title !== undefined){let data10 = data8.title;if(typeof data10 === "string"){if(func1(data10) > 4000){const err30 = {instancePath:instancePath+"/sources/" + i0+"/title",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}else {const err31 = {instancePath:instancePath+"/sources/" + i0+"/title",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}if(data8.url !== undefined){let data11 = data8.url;if((typeof data11 !== "string") && (data11 !== null)){const err32 = {instancePath:instancePath+"/sources/" + i0+"/url",schemaPath:"#/$defs/safeUrl/type",keyword:"type",params:{type: schema62.type},message:"must be string,null"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(typeof data11 === "string"){if(func1(data11) > 8192){const err33 = {instancePath:instancePath+"/sources/" + i0+"/url",schemaPath:"#/$defs/safeUrl/maxLength",keyword:"maxLength",params:{limit: 8192},message:"must NOT have more than 8192 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(!pattern21.test(data11)){const err34 = {instancePath:instancePath+"/sources/" + i0+"/url",schemaPath:"#/$defs/safeUrl/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}if(data8.snippet !== undefined){let data12 = data8.snippet;if((typeof data12 !== "string") && (data12 !== null)){const err35 = {instancePath:instancePath+"/sources/" + i0+"/snippet",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(typeof data12 === "string"){if(func1(data12) > 4000){const err36 = {instancePath:instancePath+"/sources/" + i0+"/snippet",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}}}else {const err37 = {instancePath:instancePath+"/sources/" + i0,schemaPath:"#/properties/sources/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}}else {const err38 = {instancePath:instancePath+"/sources",schemaPath:"#/properties/sources/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}}else {const err39 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}validate64.errors = vErrors;return errors === 0;}validate64.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema68 = {"type":"object","required":["schema","delegationId","action","status","children"],"properties":{"schema":{"const":"paperclip.delegation.v1"},"delegationId":{"$ref":"#/$defs/id"},"action":{"enum":["spawn","message","resume","wait","close"]},"status":{"enum":["running","waiting","completed","failed","interrupted","closed"]},"children":{"type":"array","maxItems":64,"items":{"type":"object","required":["childId","role","model","status","summary","activitySummary"],"properties":{"childId":{"$ref":"#/$defs/id"},"role":{"type":["string","null"],"maxLength":160},"model":{"type":["string","null"],"maxLength":240},"status":{"enum":["running","waiting","completed","failed","interrupted","closed"]},"summary":{"$ref":"#/$defs/nullableShortText"},"activitySummary":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false}}},"additionalProperties":false};function validate66(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate66.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.delegationId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "delegationId"},message:"must have required property '"+"delegationId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.action === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "action"},message:"must have required property '"+"action"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.status === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.children === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "children"},message:"must have required property '"+"children"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!(((((key0 === "schema") || (key0 === "delegationId")) || (key0 === "action")) || (key0 === "status")) || (key0 === "children"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema !== undefined){if("paperclip.delegation.v1" !== data.schema){const err6 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.delegation.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.delegationId !== undefined){let data1 = data.delegationId;if(typeof data1 === "string"){if(func1(data1) > 160){const err7 = {instancePath:instancePath+"/delegationId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func1(data1) < 1){const err8 = {instancePath:instancePath+"/delegationId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!pattern4.test(data1)){const err9 = {instancePath:instancePath+"/delegationId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/delegationId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.action !== undefined){let data2 = data.action;if(!(((((data2 === "spawn") || (data2 === "message")) || (data2 === "resume")) || (data2 === "wait")) || (data2 === "close"))){const err11 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema68.properties.action.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.status !== undefined){let data3 = data.status;if(!((((((data3 === "running") || (data3 === "waiting")) || (data3 === "completed")) || (data3 === "failed")) || (data3 === "interrupted")) || (data3 === "closed"))){const err12 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema68.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.children !== undefined){let data4 = data.children;if(Array.isArray(data4)){if(data4.length > 64){const err13 = {instancePath:instancePath+"/children",schemaPath:"#/properties/children/maxItems",keyword:"maxItems",params:{limit: 64},message:"must NOT have more than 64 items"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}const len0 = data4.length;for(let i0=0; i0 160){const err21 = {instancePath:instancePath+"/children/" + i0+"/childId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data6) < 1){const err22 = {instancePath:instancePath+"/children/" + i0+"/childId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(!pattern4.test(data6)){const err23 = {instancePath:instancePath+"/children/" + i0+"/childId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/children/" + i0+"/childId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data5.role !== undefined){let data7 = data5.role;if((typeof data7 !== "string") && (data7 !== null)){const err25 = {instancePath:instancePath+"/children/" + i0+"/role",schemaPath:"#/properties/children/items/properties/role/type",keyword:"type",params:{type: schema68.properties.children.items.properties.role.type},message:"must be string,null"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(typeof data7 === "string"){if(func1(data7) > 160){const err26 = {instancePath:instancePath+"/children/" + i0+"/role",schemaPath:"#/properties/children/items/properties/role/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}}if(data5.model !== undefined){let data8 = data5.model;if((typeof data8 !== "string") && (data8 !== null)){const err27 = {instancePath:instancePath+"/children/" + i0+"/model",schemaPath:"#/properties/children/items/properties/model/type",keyword:"type",params:{type: schema68.properties.children.items.properties.model.type},message:"must be string,null"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(typeof data8 === "string"){if(func1(data8) > 240){const err28 = {instancePath:instancePath+"/children/" + i0+"/model",schemaPath:"#/properties/children/items/properties/model/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}if(data5.status !== undefined){let data9 = data5.status;if(!((((((data9 === "running") || (data9 === "waiting")) || (data9 === "completed")) || (data9 === "failed")) || (data9 === "interrupted")) || (data9 === "closed"))){const err29 = {instancePath:instancePath+"/children/" + i0+"/status",schemaPath:"#/properties/children/items/properties/status/enum",keyword:"enum",params:{allowedValues: schema68.properties.children.items.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data5.summary !== undefined){let data10 = data5.summary;if((typeof data10 !== "string") && (data10 !== null)){const err30 = {instancePath:instancePath+"/children/" + i0+"/summary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(typeof data10 === "string"){if(func1(data10) > 4000){const err31 = {instancePath:instancePath+"/children/" + i0+"/summary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}}if(data5.activitySummary !== undefined){let data11 = data5.activitySummary;if((typeof data11 !== "string") && (data11 !== null)){const err32 = {instancePath:instancePath+"/children/" + i0+"/activitySummary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(typeof data11 === "string"){if(func1(data11) > 4000){const err33 = {instancePath:instancePath+"/children/" + i0+"/activitySummary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}}else {const err34 = {instancePath:instancePath+"/children/" + i0,schemaPath:"#/properties/children/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/children",schemaPath:"#/properties/children/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}}else {const err36 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}validate66.errors = vErrors;return errors === 0;}validate66.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema73 = {"type":"object","required":["schema","routeId","provider","requestedModel","fromModel","effectiveModel","reason"],"properties":{"schema":{"const":"paperclip.model.route_changed.v1"},"routeId":{"$ref":"#/$defs/id"},"provider":{"type":"string","minLength":1,"maxLength":160},"requestedModel":{"type":"string","minLength":1,"maxLength":240},"fromModel":{"type":["string","null"],"maxLength":240},"effectiveModel":{"type":"string","minLength":1,"maxLength":240},"reason":{"$ref":"#/$defs/shortText"}},"additionalProperties":false};function validate68(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate68.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.routeId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "routeId"},message:"must have required property '"+"routeId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.provider === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "provider"},message:"must have required property '"+"provider"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.requestedModel === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "requestedModel"},message:"must have required property '"+"requestedModel"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.fromModel === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "fromModel"},message:"must have required property '"+"fromModel"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.effectiveModel === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "effectiveModel"},message:"must have required property '"+"effectiveModel"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.reason === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reason"},message:"must have required property '"+"reason"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}for(const key0 in data){if(!(((((((key0 === "schema") || (key0 === "routeId")) || (key0 === "provider")) || (key0 === "requestedModel")) || (key0 === "fromModel")) || (key0 === "effectiveModel")) || (key0 === "reason"))){const err7 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.schema !== undefined){if("paperclip.model.route_changed.v1" !== data.schema){const err8 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.model.route_changed.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.routeId !== undefined){let data1 = data.routeId;if(typeof data1 === "string"){if(func1(data1) > 160){const err9 = {instancePath:instancePath+"/routeId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(func1(data1) < 1){const err10 = {instancePath:instancePath+"/routeId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(!pattern4.test(data1)){const err11 = {instancePath:instancePath+"/routeId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/routeId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.provider !== undefined){let data2 = data.provider;if(typeof data2 === "string"){if(func1(data2) > 160){const err13 = {instancePath:instancePath+"/provider",schemaPath:"#/properties/provider/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data2) < 1){const err14 = {instancePath:instancePath+"/provider",schemaPath:"#/properties/provider/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/provider",schemaPath:"#/properties/provider/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.requestedModel !== undefined){let data3 = data.requestedModel;if(typeof data3 === "string"){if(func1(data3) > 240){const err16 = {instancePath:instancePath+"/requestedModel",schemaPath:"#/properties/requestedModel/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(func1(data3) < 1){const err17 = {instancePath:instancePath+"/requestedModel",schemaPath:"#/properties/requestedModel/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}else {const err18 = {instancePath:instancePath+"/requestedModel",schemaPath:"#/properties/requestedModel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.fromModel !== undefined){let data4 = data.fromModel;if((typeof data4 !== "string") && (data4 !== null)){const err19 = {instancePath:instancePath+"/fromModel",schemaPath:"#/properties/fromModel/type",keyword:"type",params:{type: schema73.properties.fromModel.type},message:"must be string,null"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 240){const err20 = {instancePath:instancePath+"/fromModel",schemaPath:"#/properties/fromModel/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}if(data.effectiveModel !== undefined){let data5 = data.effectiveModel;if(typeof data5 === "string"){if(func1(data5) > 240){const err21 = {instancePath:instancePath+"/effectiveModel",schemaPath:"#/properties/effectiveModel/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data5) < 1){const err22 = {instancePath:instancePath+"/effectiveModel",schemaPath:"#/properties/effectiveModel/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}else {const err23 = {instancePath:instancePath+"/effectiveModel",schemaPath:"#/properties/effectiveModel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data.reason !== undefined){let data6 = data.reason;if(typeof data6 === "string"){if(func1(data6) > 4000){const err24 = {instancePath:instancePath+"/reason",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}else {const err25 = {instancePath:instancePath+"/reason",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}}else {const err26 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}validate68.errors = vErrors;return errors === 0;}validate68.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema76 = {"type":"object","required":["schema","verificationId","status","classes","buffering","summary"],"properties":{"schema":{"const":"paperclip.model.verification.v1"},"verificationId":{"$ref":"#/$defs/id"},"status":{"enum":["running","completed","failed"]},"classes":{"type":"array","maxItems":32,"items":{"type":"string","maxLength":160}},"buffering":{"type":"boolean"},"summary":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate70(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate70.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.verificationId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "verificationId"},message:"must have required property '"+"verificationId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.status === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.classes === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "classes"},message:"must have required property '"+"classes"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.buffering === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "buffering"},message:"must have required property '"+"buffering"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.summary === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "verificationId")) || (key0 === "status")) || (key0 === "classes")) || (key0 === "buffering")) || (key0 === "summary"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.model.verification.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.model.verification.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.verificationId !== undefined){let data1 = data.verificationId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/verificationId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/verificationId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/verificationId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/verificationId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.status !== undefined){let data2 = data.status;if(!(((data2 === "running") || (data2 === "completed")) || (data2 === "failed"))){const err12 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema76.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.classes !== undefined){let data3 = data.classes;if(Array.isArray(data3)){if(data3.length > 32){const err13 = {instancePath:instancePath+"/classes",schemaPath:"#/properties/classes/maxItems",keyword:"maxItems",params:{limit: 32},message:"must NOT have more than 32 items"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}const len0 = data3.length;for(let i0=0; i0 160){const err14 = {instancePath:instancePath+"/classes/" + i0,schemaPath:"#/properties/classes/items/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/classes/" + i0,schemaPath:"#/properties/classes/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/classes",schemaPath:"#/properties/classes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.buffering !== undefined){if(typeof data.buffering !== "boolean"){const err17 = {instancePath:instancePath+"/buffering",schemaPath:"#/properties/buffering/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.summary !== undefined){let data6 = data.summary;if((typeof data6 !== "string") && (data6 !== null)){const err18 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(typeof data6 === "string"){if(func1(data6) > 4000){const err19 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}}}else {const err20 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}validate70.errors = vErrors;return errors === 0;}validate70.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema79 = {"type":"object","required":["schema","compactionId","reason","preTokens","postTokens","sameSession"],"properties":{"schema":{"const":"paperclip.context.compacted.v1"},"compactionId":{"$ref":"#/$defs/id"},"reason":{"enum":["context_window","manual","provider","unknown"]},"preTokens":{"type":["integer","null"],"minimum":0},"postTokens":{"type":["integer","null"],"minimum":0},"sameSession":{"type":"boolean"}},"additionalProperties":false};function validate72(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate72.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.compactionId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "compactionId"},message:"must have required property '"+"compactionId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.reason === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reason"},message:"must have required property '"+"reason"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.preTokens === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "preTokens"},message:"must have required property '"+"preTokens"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.postTokens === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "postTokens"},message:"must have required property '"+"postTokens"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.sameSession === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sameSession"},message:"must have required property '"+"sameSession"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "compactionId")) || (key0 === "reason")) || (key0 === "preTokens")) || (key0 === "postTokens")) || (key0 === "sameSession"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.context.compacted.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.context.compacted.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.compactionId !== undefined){let data1 = data.compactionId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/compactionId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/compactionId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/compactionId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/compactionId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.reason !== undefined){let data2 = data.reason;if(!((((data2 === "context_window") || (data2 === "manual")) || (data2 === "provider")) || (data2 === "unknown"))){const err12 = {instancePath:instancePath+"/reason",schemaPath:"#/properties/reason/enum",keyword:"enum",params:{allowedValues: schema79.properties.reason.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.preTokens !== undefined){let data3 = data.preTokens;if((!(((typeof data3 == "number") && (!(data3 % 1) && !isNaN(data3))) && (isFinite(data3)))) && (data3 !== null)){const err13 = {instancePath:instancePath+"/preTokens",schemaPath:"#/properties/preTokens/type",keyword:"type",params:{type: schema79.properties.preTokens.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if((typeof data3 == "number") && (isFinite(data3))){if(data3 < 0 || isNaN(data3)){const err14 = {instancePath:instancePath+"/preTokens",schemaPath:"#/properties/preTokens/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}}if(data.postTokens !== undefined){let data4 = data.postTokens;if((!(((typeof data4 == "number") && (!(data4 % 1) && !isNaN(data4))) && (isFinite(data4)))) && (data4 !== null)){const err15 = {instancePath:instancePath+"/postTokens",schemaPath:"#/properties/postTokens/type",keyword:"type",params:{type: schema79.properties.postTokens.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if((typeof data4 == "number") && (isFinite(data4))){if(data4 < 0 || isNaN(data4)){const err16 = {instancePath:instancePath+"/postTokens",schemaPath:"#/properties/postTokens/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}if(data.sameSession !== undefined){if(typeof data.sameSession !== "boolean"){const err17 = {instancePath:instancePath+"/sameSession",schemaPath:"#/properties/sameSession/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}}else {const err18 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}validate72.errors = vErrors;return errors === 0;}validate72.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema81 = {"type":"object","required":["schema","artifactId","reference","mediaType","title"],"properties":{"schema":{"const":"paperclip.artifact.viewed.v1"},"artifactId":{"$ref":"#/$defs/id"},"reference":{"$ref":"#/$defs/safePath"},"mediaType":{"type":["string","null"],"maxLength":160},"title":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate74(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate74.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.artifactId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "artifactId"},message:"must have required property '"+"artifactId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.reference === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reference"},message:"must have required property '"+"reference"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.mediaType === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "mediaType"},message:"must have required property '"+"mediaType"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.title === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!(((((key0 === "schema") || (key0 === "artifactId")) || (key0 === "reference")) || (key0 === "mediaType")) || (key0 === "title"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema !== undefined){if("paperclip.artifact.viewed.v1" !== data.schema){const err6 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.artifact.viewed.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.artifactId !== undefined){let data1 = data.artifactId;if(typeof data1 === "string"){if(func1(data1) > 160){const err7 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func1(data1) < 1){const err8 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!pattern4.test(data1)){const err9 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.reference !== undefined){let data2 = data.reference;if((typeof data2 !== "string") && (data2 !== null)){const err11 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/type",keyword:"type",params:{type: schema57.type},message:"must be string,null"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(typeof data2 === "string"){if(func1(data2) > 4096){const err12 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/maxLength",keyword:"maxLength",params:{limit: 4096},message:"must NOT have more than 4096 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(!pattern18.test(data2)){const err13 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"+"\""};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}}if(data.mediaType !== undefined){let data3 = data.mediaType;if((typeof data3 !== "string") && (data3 !== null)){const err14 = {instancePath:instancePath+"/mediaType",schemaPath:"#/properties/mediaType/type",keyword:"type",params:{type: schema81.properties.mediaType.type},message:"must be string,null"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(typeof data3 === "string"){if(func1(data3) > 160){const err15 = {instancePath:instancePath+"/mediaType",schemaPath:"#/properties/mediaType/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}if(data.title !== undefined){let data4 = data.title;if((typeof data4 !== "string") && (data4 !== null)){const err16 = {instancePath:instancePath+"/title",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 4000){const err17 = {instancePath:instancePath+"/title",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}}}else {const err18 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}validate74.errors = vErrors;return errors === 0;}validate74.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema85 = {"type":"object","required":["schema","artifactId","status","reference","mediaType","registered","failure"],"properties":{"schema":{"const":"paperclip.artifact.generated.v1"},"artifactId":{"$ref":"#/$defs/id"},"status":{"enum":["running","completed","failed"]},"reference":{"$ref":"#/$defs/safePath"},"mediaType":{"type":["string","null"],"maxLength":160},"registered":{"type":"boolean"},"transparentBackground":{"type":["boolean","null"]},"failure":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate76(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate76.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.artifactId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "artifactId"},message:"must have required property '"+"artifactId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.status === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.reference === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reference"},message:"must have required property '"+"reference"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.mediaType === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "mediaType"},message:"must have required property '"+"mediaType"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.registered === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "registered"},message:"must have required property '"+"registered"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.failure === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "failure"},message:"must have required property '"+"failure"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema") || (key0 === "artifactId")) || (key0 === "status")) || (key0 === "reference")) || (key0 === "mediaType")) || (key0 === "registered")) || (key0 === "transparentBackground")) || (key0 === "failure"))){const err7 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.schema !== undefined){if("paperclip.artifact.generated.v1" !== data.schema){const err8 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.artifact.generated.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.artifactId !== undefined){let data1 = data.artifactId;if(typeof data1 === "string"){if(func1(data1) > 160){const err9 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(func1(data1) < 1){const err10 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(!pattern4.test(data1)){const err11 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.status !== undefined){let data2 = data.status;if(!(((data2 === "running") || (data2 === "completed")) || (data2 === "failed"))){const err13 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema85.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.reference !== undefined){let data3 = data.reference;if((typeof data3 !== "string") && (data3 !== null)){const err14 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/type",keyword:"type",params:{type: schema57.type},message:"must be string,null"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(typeof data3 === "string"){if(func1(data3) > 4096){const err15 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/maxLength",keyword:"maxLength",params:{limit: 4096},message:"must NOT have more than 4096 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(!pattern18.test(data3)){const err16 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"+"\""};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}if(data.mediaType !== undefined){let data4 = data.mediaType;if((typeof data4 !== "string") && (data4 !== null)){const err17 = {instancePath:instancePath+"/mediaType",schemaPath:"#/properties/mediaType/type",keyword:"type",params:{type: schema85.properties.mediaType.type},message:"must be string,null"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 160){const err18 = {instancePath:instancePath+"/mediaType",schemaPath:"#/properties/mediaType/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}}if(data.registered !== undefined){if(typeof data.registered !== "boolean"){const err19 = {instancePath:instancePath+"/registered",schemaPath:"#/properties/registered/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.transparentBackground !== undefined){let data6 = data.transparentBackground;if((typeof data6 !== "boolean") && (data6 !== null)){const err20 = {instancePath:instancePath+"/transparentBackground",schemaPath:"#/properties/transparentBackground/type",keyword:"type",params:{type: schema85.properties.transparentBackground.type},message:"must be boolean,null"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.failure !== undefined){let data7 = data.failure;if((typeof data7 !== "string") && (data7 !== null)){const err21 = {instancePath:instancePath+"/failure",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(typeof data7 === "string"){if(func1(data7) > 4000){const err22 = {instancePath:instancePath+"/failure",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}}}else {const err23 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}validate76.errors = vErrors;return errors === 0;}validate76.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema89 = {"type":"object","required":["schema","reviewId","state","scope"],"properties":{"schema":{"const":"paperclip.review.mode_changed.v1"},"reviewId":{"$ref":"#/$defs/id"},"state":{"enum":["entered","exited"]},"scope":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate78(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate78.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.reviewId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reviewId"},message:"must have required property '"+"reviewId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.state === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "state"},message:"must have required property '"+"state"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.scope === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "scope"},message:"must have required property '"+"scope"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data){if(!((((key0 === "schema") || (key0 === "reviewId")) || (key0 === "state")) || (key0 === "scope"))){const err4 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.schema !== undefined){if("paperclip.review.mode_changed.v1" !== data.schema){const err5 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.review.mode_changed.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.reviewId !== undefined){let data1 = data.reviewId;if(typeof data1 === "string"){if(func1(data1) > 160){const err6 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(func1(data1) < 1){const err7 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(!pattern4.test(data1)){const err8 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.state !== undefined){let data2 = data.state;if(!((data2 === "entered") || (data2 === "exited"))){const err10 = {instancePath:instancePath+"/state",schemaPath:"#/properties/state/enum",keyword:"enum",params:{allowedValues: schema89.properties.state.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.scope !== undefined){let data3 = data.scope;if((typeof data3 !== "string") && (data3 !== null)){const err11 = {instancePath:instancePath+"/scope",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(typeof data3 === "string"){if(func1(data3) > 4000){const err12 = {instancePath:instancePath+"/scope",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}}else {const err13 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}validate78.errors = vErrors;return errors === 0;}validate78.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema92 = {"type":"object","required":["schema","hookId","event","scope","status","blocking","durationMs","summary"],"properties":{"schema":{"const":"paperclip.hook.v1"},"hookId":{"$ref":"#/$defs/id"},"event":{"type":"string","minLength":1,"maxLength":160},"scope":{"type":"string","minLength":1,"maxLength":160},"status":{"enum":["running","completed","failed","cancelled"]},"blocking":{"type":"boolean"},"durationMs":{"type":["integer","null"],"minimum":0},"summary":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate80(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate80.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.hookId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "hookId"},message:"must have required property '"+"hookId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.event === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "event"},message:"must have required property '"+"event"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.scope === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "scope"},message:"must have required property '"+"scope"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.status === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.blocking === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "blocking"},message:"must have required property '"+"blocking"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.durationMs === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "durationMs"},message:"must have required property '"+"durationMs"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.summary === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema") || (key0 === "hookId")) || (key0 === "event")) || (key0 === "scope")) || (key0 === "status")) || (key0 === "blocking")) || (key0 === "durationMs")) || (key0 === "summary"))){const err8 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.schema !== undefined){if("paperclip.hook.v1" !== data.schema){const err9 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.hook.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.hookId !== undefined){let data1 = data.hookId;if(typeof data1 === "string"){if(func1(data1) > 160){const err10 = {instancePath:instancePath+"/hookId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(func1(data1) < 1){const err11 = {instancePath:instancePath+"/hookId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(!pattern4.test(data1)){const err12 = {instancePath:instancePath+"/hookId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}else {const err13 = {instancePath:instancePath+"/hookId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.event !== undefined){let data2 = data.event;if(typeof data2 === "string"){if(func1(data2) > 160){const err14 = {instancePath:instancePath+"/event",schemaPath:"#/properties/event/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(func1(data2) < 1){const err15 = {instancePath:instancePath+"/event",schemaPath:"#/properties/event/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}else {const err16 = {instancePath:instancePath+"/event",schemaPath:"#/properties/event/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.scope !== undefined){let data3 = data.scope;if(typeof data3 === "string"){if(func1(data3) > 160){const err17 = {instancePath:instancePath+"/scope",schemaPath:"#/properties/scope/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(func1(data3) < 1){const err18 = {instancePath:instancePath+"/scope",schemaPath:"#/properties/scope/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/scope",schemaPath:"#/properties/scope/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.status !== undefined){let data4 = data.status;if(!((((data4 === "running") || (data4 === "completed")) || (data4 === "failed")) || (data4 === "cancelled"))){const err20 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema92.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.blocking !== undefined){if(typeof data.blocking !== "boolean"){const err21 = {instancePath:instancePath+"/blocking",schemaPath:"#/properties/blocking/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data.durationMs !== undefined){let data6 = data.durationMs;if((!(((typeof data6 == "number") && (!(data6 % 1) && !isNaN(data6))) && (isFinite(data6)))) && (data6 !== null)){const err22 = {instancePath:instancePath+"/durationMs",schemaPath:"#/properties/durationMs/type",keyword:"type",params:{type: schema92.properties.durationMs.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if((typeof data6 == "number") && (isFinite(data6))){if(data6 < 0 || isNaN(data6)){const err23 = {instancePath:instancePath+"/durationMs",schemaPath:"#/properties/durationMs/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}if(data.summary !== undefined){let data7 = data.summary;if((typeof data7 !== "string") && (data7 !== null)){const err24 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(typeof data7 === "string"){if(func1(data7) > 4000){const err25 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}}}else {const err26 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}validate80.errors = vErrors;return errors === 0;}validate80.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema95 = {"type":"object","required":["schema","citationId","messageItemId","label","available","reference"],"properties":{"schema":{"const":"paperclip.memory.citation.v1"},"citationId":{"$ref":"#/$defs/id"},"messageItemId":{"$ref":"#/$defs/id"},"label":{"$ref":"#/$defs/shortText"},"available":{"type":"boolean"},"reference":{"type":["string","null"],"maxLength":4096}},"additionalProperties":false};function validate82(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate82.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.citationId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "citationId"},message:"must have required property '"+"citationId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.messageItemId === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "messageItemId"},message:"must have required property '"+"messageItemId"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.label === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.available === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "available"},message:"must have required property '"+"available"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.reference === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reference"},message:"must have required property '"+"reference"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "citationId")) || (key0 === "messageItemId")) || (key0 === "label")) || (key0 === "available")) || (key0 === "reference"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.memory.citation.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.memory.citation.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.citationId !== undefined){let data1 = data.citationId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/citationId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/citationId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/citationId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/citationId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.messageItemId !== undefined){let data2 = data.messageItemId;if(typeof data2 === "string"){if(func1(data2) > 160){const err12 = {instancePath:instancePath+"/messageItemId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(func1(data2) < 1){const err13 = {instancePath:instancePath+"/messageItemId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(!pattern4.test(data2)){const err14 = {instancePath:instancePath+"/messageItemId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/messageItemId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.label !== undefined){let data3 = data.label;if(typeof data3 === "string"){if(func1(data3) > 4000){const err16 = {instancePath:instancePath+"/label",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}else {const err17 = {instancePath:instancePath+"/label",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.available !== undefined){if(typeof data.available !== "boolean"){const err18 = {instancePath:instancePath+"/available",schemaPath:"#/properties/available/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.reference !== undefined){let data5 = data.reference;if((typeof data5 !== "string") && (data5 !== null)){const err19 = {instancePath:instancePath+"/reference",schemaPath:"#/properties/reference/type",keyword:"type",params:{type: schema95.properties.reference.type},message:"must be string,null"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data5 === "string"){if(func1(data5) > 4096){const err20 = {instancePath:instancePath+"/reference",schemaPath:"#/properties/reference/maxLength",keyword:"maxLength",params:{limit: 4096},message:"must NOT have more than 4096 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}}else {const err21 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}validate82.errors = vErrors;return errors === 0;}validate82.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema99 = {"type":"object","required":["schema","reviewId","targetExecutionId","status","decision","summary"],"properties":{"schema":{"const":"paperclip.safety.review.v1"},"reviewId":{"$ref":"#/$defs/id"},"targetExecutionId":{"anyOf":[{"$ref":"#/$defs/id"},{"type":"null"}]},"status":{"enum":["running","completed","failed"]},"decision":{"enum":["pending","allowed","denied","user_required","unknown"]},"summary":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate84(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate84.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.reviewId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reviewId"},message:"must have required property '"+"reviewId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.targetExecutionId === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "targetExecutionId"},message:"must have required property '"+"targetExecutionId"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.status === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.decision === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "decision"},message:"must have required property '"+"decision"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.summary === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "reviewId")) || (key0 === "targetExecutionId")) || (key0 === "status")) || (key0 === "decision")) || (key0 === "summary"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.safety.review.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.safety.review.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.reviewId !== undefined){let data1 = data.reviewId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.targetExecutionId !== undefined){let data2 = data.targetExecutionId;const _errs7 = errors;let valid2 = false;const _errs8 = errors;if(typeof data2 === "string"){if(func1(data2) > 160){const err12 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(func1(data2) < 1){const err13 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(!pattern4.test(data2)){const err14 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}var _valid0 = _errs8 === errors;valid2 = valid2 || _valid0;const _errs11 = errors;if(data2 !== null){const err16 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/properties/targetExecutionId/anyOf/1/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}var _valid0 = _errs11 === errors;valid2 = valid2 || _valid0;if(!valid2){const err17 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/properties/targetExecutionId/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}else {errors = _errs7;if(vErrors !== null){if(_errs7){vErrors.length = _errs7;}else {vErrors = null;}}}}if(data.status !== undefined){let data3 = data.status;if(!(((data3 === "running") || (data3 === "completed")) || (data3 === "failed"))){const err18 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema99.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.decision !== undefined){let data4 = data.decision;if(!(((((data4 === "pending") || (data4 === "allowed")) || (data4 === "denied")) || (data4 === "user_required")) || (data4 === "unknown"))){const err19 = {instancePath:instancePath+"/decision",schemaPath:"#/properties/decision/enum",keyword:"enum",params:{allowedValues: schema99.properties.decision.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.summary !== undefined){let data5 = data.summary;if((typeof data5 !== "string") && (data5 !== null)){const err20 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(typeof data5 === "string"){if(func1(data5) > 4000){const err21 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}}}else {const err22 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}validate84.errors = vErrors;return errors === 0;}validate84.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema103 = {"type":"object","required":["schema","executionId","origin","inputClass","byteCount"],"properties":{"schema":{"const":"paperclip.terminal.input_sent.v1"},"executionId":{"$ref":"#/$defs/id"},"origin":{"enum":["agent","user","system"]},"inputClass":{"enum":["text","control","eof"]},"byteCount":{"type":"integer","minimum":0}},"additionalProperties":false};function validate86(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate86.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.executionId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "executionId"},message:"must have required property '"+"executionId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.origin === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "origin"},message:"must have required property '"+"origin"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.inputClass === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "inputClass"},message:"must have required property '"+"inputClass"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.byteCount === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "byteCount"},message:"must have required property '"+"byteCount"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!(((((key0 === "schema") || (key0 === "executionId")) || (key0 === "origin")) || (key0 === "inputClass")) || (key0 === "byteCount"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema !== undefined){if("paperclip.terminal.input_sent.v1" !== data.schema){const err6 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.terminal.input_sent.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.executionId !== undefined){let data1 = data.executionId;if(typeof data1 === "string"){if(func1(data1) > 160){const err7 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func1(data1) < 1){const err8 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!pattern4.test(data1)){const err9 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.origin !== undefined){let data2 = data.origin;if(!(((data2 === "agent") || (data2 === "user")) || (data2 === "system"))){const err11 = {instancePath:instancePath+"/origin",schemaPath:"#/properties/origin/enum",keyword:"enum",params:{allowedValues: schema103.properties.origin.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.inputClass !== undefined){let data3 = data.inputClass;if(!(((data3 === "text") || (data3 === "control")) || (data3 === "eof"))){const err12 = {instancePath:instancePath+"/inputClass",schemaPath:"#/properties/inputClass/enum",keyword:"enum",params:{allowedValues: schema103.properties.inputClass.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.byteCount !== undefined){let data4 = data.byteCount;if(!(((typeof data4 == "number") && (!(data4 % 1) && !isNaN(data4))) && (isFinite(data4)))){const err13 = {instancePath:instancePath+"/byteCount",schemaPath:"#/properties/byteCount/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if((typeof data4 == "number") && (isFinite(data4))){if(data4 < 0 || isNaN(data4)){const err14 = {instancePath:instancePath+"/byteCount",schemaPath:"#/properties/byteCount/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}}}else {const err15 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}validate86.errors = vErrors;return errors === 0;}validate86.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema105 = {"type":"object","required":["schema","waitId","reason","status","plannedDurationMs","elapsedDurationMs"],"properties":{"schema":{"const":"paperclip.wait.v1"},"waitId":{"$ref":"#/$defs/id"},"reason":{"enum":["timer","provider","rate_limit","unknown"]},"status":{"enum":["running","completed","interrupted","cancelled"]},"plannedDurationMs":{"type":["integer","null"],"minimum":0},"elapsedDurationMs":{"type":["integer","null"],"minimum":0}},"additionalProperties":false};function validate88(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate88.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.waitId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "waitId"},message:"must have required property '"+"waitId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.reason === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reason"},message:"must have required property '"+"reason"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.status === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.plannedDurationMs === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "plannedDurationMs"},message:"must have required property '"+"plannedDurationMs"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.elapsedDurationMs === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "elapsedDurationMs"},message:"must have required property '"+"elapsedDurationMs"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "waitId")) || (key0 === "reason")) || (key0 === "status")) || (key0 === "plannedDurationMs")) || (key0 === "elapsedDurationMs"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.wait.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.wait.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.waitId !== undefined){let data1 = data.waitId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/waitId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/waitId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/waitId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/waitId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.reason !== undefined){let data2 = data.reason;if(!((((data2 === "timer") || (data2 === "provider")) || (data2 === "rate_limit")) || (data2 === "unknown"))){const err12 = {instancePath:instancePath+"/reason",schemaPath:"#/properties/reason/enum",keyword:"enum",params:{allowedValues: schema105.properties.reason.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.status !== undefined){let data3 = data.status;if(!((((data3 === "running") || (data3 === "completed")) || (data3 === "interrupted")) || (data3 === "cancelled"))){const err13 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema105.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.plannedDurationMs !== undefined){let data4 = data.plannedDurationMs;if((!(((typeof data4 == "number") && (!(data4 % 1) && !isNaN(data4))) && (isFinite(data4)))) && (data4 !== null)){const err14 = {instancePath:instancePath+"/plannedDurationMs",schemaPath:"#/properties/plannedDurationMs/type",keyword:"type",params:{type: schema105.properties.plannedDurationMs.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if((typeof data4 == "number") && (isFinite(data4))){if(data4 < 0 || isNaN(data4)){const err15 = {instancePath:instancePath+"/plannedDurationMs",schemaPath:"#/properties/plannedDurationMs/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}if(data.elapsedDurationMs !== undefined){let data5 = data.elapsedDurationMs;if((!(((typeof data5 == "number") && (!(data5 % 1) && !isNaN(data5))) && (isFinite(data5)))) && (data5 !== null)){const err16 = {instancePath:instancePath+"/elapsedDurationMs",schemaPath:"#/properties/elapsedDurationMs/type",keyword:"type",params:{type: schema105.properties.elapsedDurationMs.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if((typeof data5 == "number") && (isFinite(data5))){if(data5 < 0 || isNaN(data5)){const err17 = {instancePath:instancePath+"/elapsedDurationMs",schemaPath:"#/properties/elapsedDurationMs/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}}}else {const err18 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}validate88.errors = vErrors;return errors === 0;}validate88.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema107 = {"type":"object","required":["schema","noticeId","severity","category","scope","recoverable","userActionable","summary"],"properties":{"schema":{"const":"paperclip.provider.notice.v1"},"noticeId":{"$ref":"#/$defs/id"},"severity":{"enum":["info","warning","error"]},"category":{"type":"string","minLength":1,"maxLength":160},"scope":{"enum":["turn","session","environment","account"]},"recoverable":{"type":"boolean"},"userActionable":{"type":"boolean"},"summary":{"$ref":"#/$defs/shortText"}},"additionalProperties":false};function validate90(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate90.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.noticeId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "noticeId"},message:"must have required property '"+"noticeId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.severity === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "severity"},message:"must have required property '"+"severity"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.category === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "category"},message:"must have required property '"+"category"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.scope === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "scope"},message:"must have required property '"+"scope"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.recoverable === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "recoverable"},message:"must have required property '"+"recoverable"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.userActionable === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "userActionable"},message:"must have required property '"+"userActionable"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.summary === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema") || (key0 === "noticeId")) || (key0 === "severity")) || (key0 === "category")) || (key0 === "scope")) || (key0 === "recoverable")) || (key0 === "userActionable")) || (key0 === "summary"))){const err8 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.schema !== undefined){if("paperclip.provider.notice.v1" !== data.schema){const err9 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.provider.notice.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.noticeId !== undefined){let data1 = data.noticeId;if(typeof data1 === "string"){if(func1(data1) > 160){const err10 = {instancePath:instancePath+"/noticeId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(func1(data1) < 1){const err11 = {instancePath:instancePath+"/noticeId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(!pattern4.test(data1)){const err12 = {instancePath:instancePath+"/noticeId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}else {const err13 = {instancePath:instancePath+"/noticeId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.severity !== undefined){let data2 = data.severity;if(!(((data2 === "info") || (data2 === "warning")) || (data2 === "error"))){const err14 = {instancePath:instancePath+"/severity",schemaPath:"#/properties/severity/enum",keyword:"enum",params:{allowedValues: schema107.properties.severity.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.category !== undefined){let data3 = data.category;if(typeof data3 === "string"){if(func1(data3) > 160){const err15 = {instancePath:instancePath+"/category",schemaPath:"#/properties/category/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(func1(data3) < 1){const err16 = {instancePath:instancePath+"/category",schemaPath:"#/properties/category/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}else {const err17 = {instancePath:instancePath+"/category",schemaPath:"#/properties/category/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.scope !== undefined){let data4 = data.scope;if(!((((data4 === "turn") || (data4 === "session")) || (data4 === "environment")) || (data4 === "account"))){const err18 = {instancePath:instancePath+"/scope",schemaPath:"#/properties/scope/enum",keyword:"enum",params:{allowedValues: schema107.properties.scope.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.recoverable !== undefined){if(typeof data.recoverable !== "boolean"){const err19 = {instancePath:instancePath+"/recoverable",schemaPath:"#/properties/recoverable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.userActionable !== undefined){if(typeof data.userActionable !== "boolean"){const err20 = {instancePath:instancePath+"/userActionable",schemaPath:"#/properties/userActionable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.summary !== undefined){let data7 = data.summary;if(typeof data7 === "string"){if(func1(data7) > 4000){const err21 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}}else {const err23 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}validate90.errors = vErrors;return errors === 0;}validate90.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema174 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json","title":"PRP provider-neutral semantic tool envelope","type":"object","required":["schema","schemaVersion","phase","operationId","callId","correlation","idempotencyKey","content"],"properties":{"schema":{"const":"paperclip.prp.semantic_tool.v1"},"schemaVersion":{"const":1},"phase":{"enum":["input","result","reconciled"]},"operationId":{"$ref":"#/$defs/stableId"},"callId":{"$ref":"#/$defs/stableId"},"correlation":{"type":"object","required":["runId","normalizedSessionId","turnId","itemId"],"properties":{"runId":{"$ref":"#/$defs/stableId"},"normalizedSessionId":{"$ref":"#/$defs/stableId"},"turnId":{"$ref":"#/$defs/stableId"},"itemId":{"$ref":"#/$defs/stableId"},"requestId":{"$ref":"#/$defs/stableId"}},"additionalProperties":true},"idempotencyKey":{"type":["string","null"],"minLength":1,"maxLength":240},"content":{"$ref":"#/$defs/safeContent"},"outcome":{"enum":["succeeded","denied","conflict","duplicate","unavailable","failed"]},"code":{"$ref":"#/$defs/stableCode"},"retryable":{"type":"boolean"},"authorizationBoundary":{"enum":["company","actor","active_task","grant","governed_action","lock","revision"]},"operationReceiptId":{"$ref":"#/$defs/stableId"},"auditReceiptId":{"$ref":"#/$defs/stableId"},"currentRevision":{"oneOf":[{"type":"integer","minimum":0},{"$ref":"#/$defs/stableId"}]},"duplicateOfReceiptId":{"$ref":"#/$defs/stableId"},"artifactRefs":{"type":"array","items":{"allOf":[{"$ref":"#/$defs/safeReference"},{"type":"object","properties":{"kind":{"enum":["artifact","work_product"]}}}]},"uniqueItems":true},"targets":{"type":"array","items":{"$ref":"#/$defs/immutableTarget"},"uniqueItems":true},"causalRefs":{"type":"array","items":{"$ref":"#/$defs/safeReference"},"uniqueItems":true}},"allOf":[{"if":{"properties":{"phase":{"enum":["result","reconciled"]}},"required":["phase"]},"then":{"required":["outcome","code","retryable","authorizationBoundary","operationReceiptId"]}}],"additionalProperties":true,"$defs":{"stableId":{"type":"string","minLength":1,"maxLength":240,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"},"stableCode":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z][a-z0-9_.:-]*$"},"safeContent":{"type":"object","required":["digest","redactionDisposition","references"],"properties":{"digest":{"type":"string","pattern":"^sha256:[a-f0-9]{64}$"},"redactionDisposition":{"enum":["digest_only","allowlisted_references","redacted"]},"references":{"type":"array","items":{"$ref":"#/$defs/safeReference"},"uniqueItems":true}},"additionalProperties":true},"safeReference":{"type":"object","required":["kind","id"],"properties":{"kind":{"enum":["task","document_revision","interaction","approval","decision","artifact","work_product","wake","monitor","audit","operation"]},"id":{"$ref":"#/$defs/stableId"}},"additionalProperties":true},"immutableTarget":{"type":"object","required":["kind","id","immutable"],"properties":{"kind":{"enum":["document_revision","interaction","approval","decision"]},"id":{"$ref":"#/$defs/stableId"},"immutable":{"const":true},"revisionId":{"$ref":"#/$defs/stableId"},"decisionId":{"$ref":"#/$defs/stableId"}},"additionalProperties":true}}};const schema175 = {"type":"string","minLength":1,"maxLength":240,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"};const schema185 = {"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z][a-z0-9_.:-]*$"};const pattern14 = new RegExp("^[a-z][a-z0-9_.:-]*$", "u");const schema182 = {"type":"object","required":["digest","redactionDisposition","references"],"properties":{"digest":{"type":"string","pattern":"^sha256:[a-f0-9]{64}$"},"redactionDisposition":{"enum":["digest_only","allowlisted_references","redacted"]},"references":{"type":"array","items":{"$ref":"#/$defs/safeReference"},"uniqueItems":true}},"additionalProperties":true};const schema183 = {"type":"object","required":["kind","id"],"properties":{"kind":{"enum":["task","document_revision","interaction","approval","decision","artifact","work_product","wake","monitor","audit","operation"]},"id":{"$ref":"#/$defs/stableId"}},"additionalProperties":true};function validate94(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate94.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.kind === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.id === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.kind !== undefined){let data0 = data.kind;if(!(((((((((((data0 === "task") || (data0 === "document_revision")) || (data0 === "interaction")) || (data0 === "approval")) || (data0 === "decision")) || (data0 === "artifact")) || (data0 === "work_product")) || (data0 === "wake")) || (data0 === "monitor")) || (data0 === "audit")) || (data0 === "operation"))){const err2 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema183.properties.kind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.id !== undefined){let data1 = data.id;if(typeof data1 === "string"){if(func1(data1) > 240){const err3 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(func1(data1) < 1){const err4 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(!pattern4.test(data1)){const err5 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}else {const err6 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}else {const err7 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}validate94.errors = vErrors;return errors === 0;}validate94.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate93(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate93.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.digest === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "digest"},message:"must have required property '"+"digest"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.redactionDisposition === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "redactionDisposition"},message:"must have required property '"+"redactionDisposition"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.references === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "references"},message:"must have required property '"+"references"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.digest !== undefined){let data0 = data.digest;if(typeof data0 === "string"){if(!pattern19.test(data0)){const err3 = {instancePath:instancePath+"/digest",schemaPath:"#/properties/digest/pattern",keyword:"pattern",params:{pattern: "^sha256:[a-f0-9]{64}$"},message:"must match pattern \""+"^sha256:[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}else {const err4 = {instancePath:instancePath+"/digest",schemaPath:"#/properties/digest/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.redactionDisposition !== undefined){let data1 = data.redactionDisposition;if(!(((data1 === "digest_only") || (data1 === "allowlisted_references")) || (data1 === "redacted"))){const err5 = {instancePath:instancePath+"/redactionDisposition",schemaPath:"#/properties/redactionDisposition/enum",keyword:"enum",params:{allowedValues: schema182.properties.redactionDisposition.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.references !== undefined){let data2 = data.references;if(Array.isArray(data2)){const len0 = data2.length;for(let i0=0; i0 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data2[i1], data2[j0])){const err6 = {instancePath:instancePath+"/references",schemaPath:"#/properties/references/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;break outer0;}}}}}else {const err7 = {instancePath:instancePath+"/references",schemaPath:"#/properties/references/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}}else {const err8 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}validate93.errors = vErrors;return errors === 0;}validate93.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema190 = {"type":"object","required":["kind","id","immutable"],"properties":{"kind":{"enum":["document_revision","interaction","approval","decision"]},"id":{"$ref":"#/$defs/stableId"},"immutable":{"const":true},"revisionId":{"$ref":"#/$defs/stableId"},"decisionId":{"$ref":"#/$defs/stableId"}},"additionalProperties":true};function validate98(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate98.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.kind === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.id === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.immutable === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "immutable"},message:"must have required property '"+"immutable"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.kind !== undefined){let data0 = data.kind;if(!((((data0 === "document_revision") || (data0 === "interaction")) || (data0 === "approval")) || (data0 === "decision"))){const err3 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema190.properties.kind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.id !== undefined){let data1 = data.id;if(typeof data1 === "string"){if(func1(data1) > 240){const err4 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(func1(data1) < 1){const err5 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(!pattern4.test(data1)){const err6 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}else {const err7 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.immutable !== undefined){if(true !== data.immutable){const err8 = {instancePath:instancePath+"/immutable",schemaPath:"#/properties/immutable/const",keyword:"const",params:{allowedValue: true},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.revisionId !== undefined){let data3 = data.revisionId;if(typeof data3 === "string"){if(func1(data3) > 240){const err9 = {instancePath:instancePath+"/revisionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(func1(data3) < 1){const err10 = {instancePath:instancePath+"/revisionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(!pattern4.test(data3)){const err11 = {instancePath:instancePath+"/revisionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/revisionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.decisionId !== undefined){let data4 = data.decisionId;if(typeof data4 === "string"){if(func1(data4) > 240){const err13 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data4) < 1){const err14 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(!pattern4.test(data4)){const err15 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}else {const err16 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate98.errors = vErrors;return errors === 0;}validate98.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate92(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate92.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){let missing0;if((data.phase === undefined) && (missing0 = "phase")){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}else {if(data.phase !== undefined){let data0 = data.phase;if(!((data0 === "result") || (data0 === "reconciled"))){const err1 = {};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.outcome === undefined){const err2 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "outcome"},message:"must have required property '"+"outcome"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.code === undefined){const err3 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "code"},message:"must have required property '"+"code"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.retryable === undefined){const err4 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "retryable"},message:"must have required property '"+"retryable"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.authorizationBoundary === undefined){const err5 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "authorizationBoundary"},message:"must have required property '"+"authorizationBoundary"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.operationReceiptId === undefined){const err6 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "operationReceiptId"},message:"must have required property '"+"operationReceiptId"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}var _valid0 = _errs5 === errors;valid1 = _valid0;}if(!valid1){const err7 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data.schemaVersion === undefined){const err9 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.phase === undefined){const err10 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "phase"},message:"must have required property '"+"phase"+"'"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(data.operationId === undefined){const err11 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "operationId"},message:"must have required property '"+"operationId"+"'"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(data.callId === undefined){const err12 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "callId"},message:"must have required property '"+"callId"+"'"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(data.correlation === undefined){const err13 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "correlation"},message:"must have required property '"+"correlation"+"'"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(data.idempotencyKey === undefined){const err14 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "idempotencyKey"},message:"must have required property '"+"idempotencyKey"+"'"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(data.content === undefined){const err15 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "content"},message:"must have required property '"+"content"+"'"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(data.schema !== undefined){if("paperclip.prp.semantic_tool.v1" !== data.schema){const err16 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.semantic_tool.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.schemaVersion !== undefined){if(1 !== data.schemaVersion){const err17 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.phase !== undefined){let data3 = data.phase;if(!(((data3 === "input") || (data3 === "result")) || (data3 === "reconciled"))){const err18 = {instancePath:instancePath+"/phase",schemaPath:"#/properties/phase/enum",keyword:"enum",params:{allowedValues: schema174.properties.phase.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.operationId !== undefined){let data4 = data.operationId;if(typeof data4 === "string"){if(func1(data4) > 240){const err19 = {instancePath:instancePath+"/operationId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(func1(data4) < 1){const err20 = {instancePath:instancePath+"/operationId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(!pattern4.test(data4)){const err21 = {instancePath:instancePath+"/operationId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/operationId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data.callId !== undefined){let data5 = data.callId;if(typeof data5 === "string"){if(func1(data5) > 240){const err23 = {instancePath:instancePath+"/callId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(func1(data5) < 1){const err24 = {instancePath:instancePath+"/callId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(!pattern4.test(data5)){const err25 = {instancePath:instancePath+"/callId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/callId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data.correlation !== undefined){let data6 = data.correlation;if(data6 && typeof data6 == "object" && !Array.isArray(data6)){if(data6.runId === undefined){const err27 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/required",keyword:"required",params:{missingProperty: "runId"},message:"must have required property '"+"runId"+"'"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data6.normalizedSessionId === undefined){const err28 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/required",keyword:"required",params:{missingProperty: "normalizedSessionId"},message:"must have required property '"+"normalizedSessionId"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data6.turnId === undefined){const err29 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/required",keyword:"required",params:{missingProperty: "turnId"},message:"must have required property '"+"turnId"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(data6.itemId === undefined){const err30 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/required",keyword:"required",params:{missingProperty: "itemId"},message:"must have required property '"+"itemId"+"'"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(data6.runId !== undefined){let data7 = data6.runId;if(typeof data7 === "string"){if(func1(data7) > 240){const err31 = {instancePath:instancePath+"/correlation/runId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func1(data7) < 1){const err32 = {instancePath:instancePath+"/correlation/runId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(!pattern4.test(data7)){const err33 = {instancePath:instancePath+"/correlation/runId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}else {const err34 = {instancePath:instancePath+"/correlation/runId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}if(data6.normalizedSessionId !== undefined){let data8 = data6.normalizedSessionId;if(typeof data8 === "string"){if(func1(data8) > 240){const err35 = {instancePath:instancePath+"/correlation/normalizedSessionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(func1(data8) < 1){const err36 = {instancePath:instancePath+"/correlation/normalizedSessionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}if(!pattern4.test(data8)){const err37 = {instancePath:instancePath+"/correlation/normalizedSessionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}else {const err38 = {instancePath:instancePath+"/correlation/normalizedSessionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data6.turnId !== undefined){let data9 = data6.turnId;if(typeof data9 === "string"){if(func1(data9) > 240){const err39 = {instancePath:instancePath+"/correlation/turnId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}if(func1(data9) < 1){const err40 = {instancePath:instancePath+"/correlation/turnId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if(!pattern4.test(data9)){const err41 = {instancePath:instancePath+"/correlation/turnId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}else {const err42 = {instancePath:instancePath+"/correlation/turnId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data6.itemId !== undefined){let data10 = data6.itemId;if(typeof data10 === "string"){if(func1(data10) > 240){const err43 = {instancePath:instancePath+"/correlation/itemId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}if(func1(data10) < 1){const err44 = {instancePath:instancePath+"/correlation/itemId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}if(!pattern4.test(data10)){const err45 = {instancePath:instancePath+"/correlation/itemId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}else {const err46 = {instancePath:instancePath+"/correlation/itemId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data6.requestId !== undefined){let data11 = data6.requestId;if(typeof data11 === "string"){if(func1(data11) > 240){const err47 = {instancePath:instancePath+"/correlation/requestId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}if(func1(data11) < 1){const err48 = {instancePath:instancePath+"/correlation/requestId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}if(!pattern4.test(data11)){const err49 = {instancePath:instancePath+"/correlation/requestId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}else {const err50 = {instancePath:instancePath+"/correlation/requestId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}else {const err51 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data.idempotencyKey !== undefined){let data12 = data.idempotencyKey;if((typeof data12 !== "string") && (data12 !== null)){const err52 = {instancePath:instancePath+"/idempotencyKey",schemaPath:"#/properties/idempotencyKey/type",keyword:"type",params:{type: schema174.properties.idempotencyKey.type},message:"must be string,null"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(typeof data12 === "string"){if(func1(data12) > 240){const err53 = {instancePath:instancePath+"/idempotencyKey",schemaPath:"#/properties/idempotencyKey/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}if(func1(data12) < 1){const err54 = {instancePath:instancePath+"/idempotencyKey",schemaPath:"#/properties/idempotencyKey/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}}if(data.content !== undefined){if(!(validate93(data.content, {instancePath:instancePath+"/content",parentData:data,parentDataProperty:"content",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate93.errors : vErrors.concat(validate93.errors);errors = vErrors.length;}}if(data.outcome !== undefined){let data14 = data.outcome;if(!((((((data14 === "succeeded") || (data14 === "denied")) || (data14 === "conflict")) || (data14 === "duplicate")) || (data14 === "unavailable")) || (data14 === "failed"))){const err55 = {instancePath:instancePath+"/outcome",schemaPath:"#/properties/outcome/enum",keyword:"enum",params:{allowedValues: schema174.properties.outcome.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}if(data.code !== undefined){let data15 = data.code;if(typeof data15 === "string"){if(func1(data15) > 160){const err56 = {instancePath:instancePath+"/code",schemaPath:"#/$defs/stableCode/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}if(func1(data15) < 1){const err57 = {instancePath:instancePath+"/code",schemaPath:"#/$defs/stableCode/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}if(!pattern14.test(data15)){const err58 = {instancePath:instancePath+"/code",schemaPath:"#/$defs/stableCode/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_.:-]*$"},message:"must match pattern \""+"^[a-z][a-z0-9_.:-]*$"+"\""};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}else {const err59 = {instancePath:instancePath+"/code",schemaPath:"#/$defs/stableCode/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}if(data.retryable !== undefined){if(typeof data.retryable !== "boolean"){const err60 = {instancePath:instancePath+"/retryable",schemaPath:"#/properties/retryable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}if(data.authorizationBoundary !== undefined){let data17 = data.authorizationBoundary;if(!(((((((data17 === "company") || (data17 === "actor")) || (data17 === "active_task")) || (data17 === "grant")) || (data17 === "governed_action")) || (data17 === "lock")) || (data17 === "revision"))){const err61 = {instancePath:instancePath+"/authorizationBoundary",schemaPath:"#/properties/authorizationBoundary/enum",keyword:"enum",params:{allowedValues: schema174.properties.authorizationBoundary.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data.operationReceiptId !== undefined){let data18 = data.operationReceiptId;if(typeof data18 === "string"){if(func1(data18) > 240){const err62 = {instancePath:instancePath+"/operationReceiptId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(func1(data18) < 1){const err63 = {instancePath:instancePath+"/operationReceiptId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}if(!pattern4.test(data18)){const err64 = {instancePath:instancePath+"/operationReceiptId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}else {const err65 = {instancePath:instancePath+"/operationReceiptId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data.auditReceiptId !== undefined){let data19 = data.auditReceiptId;if(typeof data19 === "string"){if(func1(data19) > 240){const err66 = {instancePath:instancePath+"/auditReceiptId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(func1(data19) < 1){const err67 = {instancePath:instancePath+"/auditReceiptId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}if(!pattern4.test(data19)){const err68 = {instancePath:instancePath+"/auditReceiptId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}else {const err69 = {instancePath:instancePath+"/auditReceiptId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}if(data.currentRevision !== undefined){let data20 = data.currentRevision;const _errs51 = errors;let valid15 = false;let passing0 = null;const _errs52 = errors;if(!(((typeof data20 == "number") && (!(data20 % 1) && !isNaN(data20))) && (isFinite(data20)))){const err70 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/properties/currentRevision/oneOf/0/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if((typeof data20 == "number") && (isFinite(data20))){if(data20 < 0 || isNaN(data20)){const err71 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/properties/currentRevision/oneOf/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}var _valid1 = _errs52 === errors;if(_valid1){valid15 = true;passing0 = 0;}const _errs54 = errors;if(typeof data20 === "string"){if(func1(data20) > 240){const err72 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}if(func1(data20) < 1){const err73 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(!pattern4.test(data20)){const err74 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}else {const err75 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}var _valid1 = _errs54 === errors;if(_valid1 && valid15){valid15 = false;passing0 = [passing0, 1];}else {if(_valid1){valid15 = true;passing0 = 1;}}if(!valid15){const err76 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/properties/currentRevision/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}else {errors = _errs51;if(vErrors !== null){if(_errs51){vErrors.length = _errs51;}else {vErrors = null;}}}}if(data.duplicateOfReceiptId !== undefined){let data21 = data.duplicateOfReceiptId;if(typeof data21 === "string"){if(func1(data21) > 240){const err77 = {instancePath:instancePath+"/duplicateOfReceiptId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(func1(data21) < 1){const err78 = {instancePath:instancePath+"/duplicateOfReceiptId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}if(!pattern4.test(data21)){const err79 = {instancePath:instancePath+"/duplicateOfReceiptId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}else {const err80 = {instancePath:instancePath+"/duplicateOfReceiptId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data.artifactRefs !== undefined){let data22 = data.artifactRefs;if(Array.isArray(data22)){const len0 = data22.length;for(let i0=0; i0 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data22[i1], data22[j0])){const err83 = {instancePath:instancePath+"/artifactRefs",schemaPath:"#/properties/artifactRefs/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;break outer0;}}}}}else {const err84 = {instancePath:instancePath+"/artifactRefs",schemaPath:"#/properties/artifactRefs/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}if(data.targets !== undefined){let data25 = data.targets;if(Array.isArray(data25)){const len1 = data25.length;for(let i2=0; i2 1){outer1:for(;i3--;){for(j1 = i3; j1--;){if(func0(data25[i3], data25[j1])){const err85 = {instancePath:instancePath+"/targets",schemaPath:"#/properties/targets/uniqueItems",keyword:"uniqueItems",params:{i: i3, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i3+" are identical)"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;break outer1;}}}}}else {const err86 = {instancePath:instancePath+"/targets",schemaPath:"#/properties/targets/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}if(data.causalRefs !== undefined){let data27 = data.causalRefs;if(Array.isArray(data27)){const len2 = data27.length;for(let i4=0; i4 1){outer2:for(;i5--;){for(j2 = i5; j2--;){if(func0(data27[i5], data27[j2])){const err87 = {instancePath:instancePath+"/causalRefs",schemaPath:"#/properties/causalRefs/uniqueItems",keyword:"uniqueItems",params:{i: i5, j: j2},message:"must NOT have duplicate items (items ## "+j2+" and "+i5+" are identical)"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;break outer2;}}}}}else {const err88 = {instancePath:instancePath+"/causalRefs",schemaPath:"#/properties/causalRefs/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}}else {const err89 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}validate92.errors = vErrors;return errors === 0;}validate92.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema194 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/terminal.schema.json","title":"PRP run terminal state","type":"object","required":["schema","turnTerminalState","runTerminalState","reportedWorkDisposition"],"properties":{"schema":{"const":"paperclip.prp.terminal.v1"},"turnTerminalState":{"enum":["completed","failed","interrupted","cancelled"]},"runTerminalState":{"enum":["succeeded","failed","cancelled"]},"reportedWorkDisposition":{"enum":["done","blocked","needs_review","yielded"]},"workAssessmentId":{"type":"string","minLength":1},"statusDecisionId":{"type":"string","minLength":1},"stopReason":{"$ref":"https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json"}},"additionalProperties":true};const schema195 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json","title":"PRP terminal stop reason receipt","type":"object","required":["schema","schemaVersion","receiptId","kind","code","retryable","decisionId","limitClass","aggregate"],"properties":{"schema":{"const":"paperclip.prp.stop_reason.v1"},"schemaVersion":{"const":1},"receiptId":{"$ref":"#/$defs/stableId"},"kind":{"enum":["budget","cost"]},"code":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z][a-z0-9_.:-]*$"},"retryable":{"type":"boolean"},"decisionId":{"$ref":"#/$defs/stableId"},"limitClass":{"enum":["company_monthly","actor_monthly","project_monthly","run_cost","provider_quota"]},"aggregate":{"type":"object","required":["unit","observed","limit","window"],"properties":{"unit":{"enum":["cents","usd_micros","tokens"]},"observed":{"type":"integer","minimum":0},"limit":{"type":"integer","minimum":0},"window":{"enum":["run","monthly_utc","provider_window"]}},"additionalProperties":true},"relatedReceiptIds":{"type":"array","items":{"$ref":"#/$defs/stableId"},"uniqueItems":true}},"additionalProperties":true,"$defs":{"stableId":{"type":"string","minLength":1,"maxLength":240,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"}}};const schema196 = {"type":"string","minLength":1,"maxLength":240,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"};function validate105(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate105.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.schemaVersion === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.receiptId === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "receiptId"},message:"must have required property '"+"receiptId"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.kind === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.code === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "code"},message:"must have required property '"+"code"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.retryable === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "retryable"},message:"must have required property '"+"retryable"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.decisionId === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "decisionId"},message:"must have required property '"+"decisionId"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.limitClass === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "limitClass"},message:"must have required property '"+"limitClass"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.aggregate === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "aggregate"},message:"must have required property '"+"aggregate"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data.schema !== undefined){if("paperclip.prp.stop_reason.v1" !== data.schema){const err9 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.stop_reason.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.schemaVersion !== undefined){if(1 !== data.schemaVersion){const err10 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.receiptId !== undefined){let data2 = data.receiptId;if(typeof data2 === "string"){if(func1(data2) > 240){const err11 = {instancePath:instancePath+"/receiptId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(func1(data2) < 1){const err12 = {instancePath:instancePath+"/receiptId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(!pattern4.test(data2)){const err13 = {instancePath:instancePath+"/receiptId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}else {const err14 = {instancePath:instancePath+"/receiptId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.kind !== undefined){let data3 = data.kind;if(!((data3 === "budget") || (data3 === "cost"))){const err15 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema195.properties.kind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.code !== undefined){let data4 = data.code;if(typeof data4 === "string"){if(func1(data4) > 160){const err16 = {instancePath:instancePath+"/code",schemaPath:"#/properties/code/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(func1(data4) < 1){const err17 = {instancePath:instancePath+"/code",schemaPath:"#/properties/code/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(!pattern14.test(data4)){const err18 = {instancePath:instancePath+"/code",schemaPath:"#/properties/code/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_.:-]*$"},message:"must match pattern \""+"^[a-z][a-z0-9_.:-]*$"+"\""};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/code",schemaPath:"#/properties/code/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.retryable !== undefined){if(typeof data.retryable !== "boolean"){const err20 = {instancePath:instancePath+"/retryable",schemaPath:"#/properties/retryable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.decisionId !== undefined){let data6 = data.decisionId;if(typeof data6 === "string"){if(func1(data6) > 240){const err21 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data6) < 1){const err22 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(!pattern4.test(data6)){const err23 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.limitClass !== undefined){let data7 = data.limitClass;if(!(((((data7 === "company_monthly") || (data7 === "actor_monthly")) || (data7 === "project_monthly")) || (data7 === "run_cost")) || (data7 === "provider_quota"))){const err25 = {instancePath:instancePath+"/limitClass",schemaPath:"#/properties/limitClass/enum",keyword:"enum",params:{allowedValues: schema195.properties.limitClass.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data.aggregate !== undefined){let data8 = data.aggregate;if(data8 && typeof data8 == "object" && !Array.isArray(data8)){if(data8.unit === undefined){const err26 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/required",keyword:"required",params:{missingProperty: "unit"},message:"must have required property '"+"unit"+"'"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(data8.observed === undefined){const err27 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/required",keyword:"required",params:{missingProperty: "observed"},message:"must have required property '"+"observed"+"'"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data8.limit === undefined){const err28 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/required",keyword:"required",params:{missingProperty: "limit"},message:"must have required property '"+"limit"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data8.window === undefined){const err29 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/required",keyword:"required",params:{missingProperty: "window"},message:"must have required property '"+"window"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(data8.unit !== undefined){let data9 = data8.unit;if(!(((data9 === "cents") || (data9 === "usd_micros")) || (data9 === "tokens"))){const err30 = {instancePath:instancePath+"/aggregate/unit",schemaPath:"#/properties/aggregate/properties/unit/enum",keyword:"enum",params:{allowedValues: schema195.properties.aggregate.properties.unit.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data8.observed !== undefined){let data10 = data8.observed;if(!(((typeof data10 == "number") && (!(data10 % 1) && !isNaN(data10))) && (isFinite(data10)))){const err31 = {instancePath:instancePath+"/aggregate/observed",schemaPath:"#/properties/aggregate/properties/observed/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if((typeof data10 == "number") && (isFinite(data10))){if(data10 < 0 || isNaN(data10)){const err32 = {instancePath:instancePath+"/aggregate/observed",schemaPath:"#/properties/aggregate/properties/observed/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}}if(data8.limit !== undefined){let data11 = data8.limit;if(!(((typeof data11 == "number") && (!(data11 % 1) && !isNaN(data11))) && (isFinite(data11)))){const err33 = {instancePath:instancePath+"/aggregate/limit",schemaPath:"#/properties/aggregate/properties/limit/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if((typeof data11 == "number") && (isFinite(data11))){if(data11 < 0 || isNaN(data11)){const err34 = {instancePath:instancePath+"/aggregate/limit",schemaPath:"#/properties/aggregate/properties/limit/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}if(data8.window !== undefined){let data12 = data8.window;if(!(((data12 === "run") || (data12 === "monthly_utc")) || (data12 === "provider_window"))){const err35 = {instancePath:instancePath+"/aggregate/window",schemaPath:"#/properties/aggregate/properties/window/enum",keyword:"enum",params:{allowedValues: schema195.properties.aggregate.properties.window.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}}else {const err36 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data.relatedReceiptIds !== undefined){let data13 = data.relatedReceiptIds;if(Array.isArray(data13)){const len0 = data13.length;for(let i0=0; i0 240){const err37 = {instancePath:instancePath+"/relatedReceiptIds/" + i0,schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(func1(data14) < 1){const err38 = {instancePath:instancePath+"/relatedReceiptIds/" + i0,schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(!pattern4.test(data14)){const err39 = {instancePath:instancePath+"/relatedReceiptIds/" + i0,schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/relatedReceiptIds/" + i0,schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}let i1 = data13.length;let j0;if(i1 > 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data13[i1], data13[j0])){const err41 = {instancePath:instancePath+"/relatedReceiptIds",schemaPath:"#/properties/relatedReceiptIds/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;break outer0;}}}}}else {const err42 = {instancePath:instancePath+"/relatedReceiptIds",schemaPath:"#/properties/relatedReceiptIds/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}}else {const err43 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}validate105.errors = vErrors;return errors === 0;}validate105.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate104(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/terminal.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate104.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.turnTerminalState === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "turnTerminalState"},message:"must have required property '"+"turnTerminalState"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.runTerminalState === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "runTerminalState"},message:"must have required property '"+"runTerminalState"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.reportedWorkDisposition === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reportedWorkDisposition"},message:"must have required property '"+"reportedWorkDisposition"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.schema !== undefined){if("paperclip.prp.terminal.v1" !== data.schema){const err4 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.terminal.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.turnTerminalState !== undefined){let data1 = data.turnTerminalState;if(!((((data1 === "completed") || (data1 === "failed")) || (data1 === "interrupted")) || (data1 === "cancelled"))){const err5 = {instancePath:instancePath+"/turnTerminalState",schemaPath:"#/properties/turnTerminalState/enum",keyword:"enum",params:{allowedValues: schema194.properties.turnTerminalState.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.runTerminalState !== undefined){let data2 = data.runTerminalState;if(!(((data2 === "succeeded") || (data2 === "failed")) || (data2 === "cancelled"))){const err6 = {instancePath:instancePath+"/runTerminalState",schemaPath:"#/properties/runTerminalState/enum",keyword:"enum",params:{allowedValues: schema194.properties.runTerminalState.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.reportedWorkDisposition !== undefined){let data3 = data.reportedWorkDisposition;if(!((((data3 === "done") || (data3 === "blocked")) || (data3 === "needs_review")) || (data3 === "yielded"))){const err7 = {instancePath:instancePath+"/reportedWorkDisposition",schemaPath:"#/properties/reportedWorkDisposition/enum",keyword:"enum",params:{allowedValues: schema194.properties.reportedWorkDisposition.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.workAssessmentId !== undefined){let data4 = data.workAssessmentId;if(typeof data4 === "string"){if(func1(data4) < 1){const err8 = {instancePath:instancePath+"/workAssessmentId",schemaPath:"#/properties/workAssessmentId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/workAssessmentId",schemaPath:"#/properties/workAssessmentId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.statusDecisionId !== undefined){let data5 = data.statusDecisionId;if(typeof data5 === "string"){if(func1(data5) < 1){const err10 = {instancePath:instancePath+"/statusDecisionId",schemaPath:"#/properties/statusDecisionId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/statusDecisionId",schemaPath:"#/properties/statusDecisionId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.stopReason !== undefined){if(!(validate105(data.stopReason, {instancePath:instancePath+"/stopReason",parentData:data,parentDataProperty:"stopReason",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate105.errors : vErrors.concat(validate105.errors);errors = vErrors.length;}}}else {const err12 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}validate104.errors = vErrors;return errors === 0;}validate104.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const pattern69 = new RegExp("^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$", "u");const formats0 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function validate26(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/event.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate26.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("plan.updated" !== data.eventType){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate60(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate60.errors : vErrors.concat(validate60.errors);errors = vErrors.length;}}}var _valid0 = _errs5 === errors;valid1 = _valid0;if(valid1){var props0 = {};props0.payload = true;props0.eventType = true;}}if(!valid1){const err1 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}const _errs8 = errors;let valid4 = true;const _errs9 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data2 = data.eventType;if(!(((data2 === "tool.execution.started") || (data2 === "tool.execution.progressed")) || (data2 === "tool.execution.completed"))){const err2 = {};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}}var _valid1 = _errs9 === errors;errors = _errs8;if(vErrors !== null){if(_errs8){vErrors.length = _errs8;}else {vErrors = null;}}if(_valid1){const _errs11 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate62(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate62.errors : vErrors.concat(validate62.errors);errors = vErrors.length;}}}var _valid1 = _errs11 === errors;valid4 = _valid1;if(valid4){var props1 = {};props1.payload = true;props1.eventType = true;}}if(!valid4){const err3 = {instancePath,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(props0 !== true && props1 !== undefined){if(props1 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props1);}}const _errs14 = errors;let valid7 = true;const _errs15 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data4 = data.eventType;if(!(((data4 === "research.started") || (data4 === "research.progressed")) || (data4 === "research.completed"))){const err4 = {};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}}var _valid2 = _errs15 === errors;errors = _errs14;if(vErrors !== null){if(_errs14){vErrors.length = _errs14;}else {vErrors = null;}}if(_valid2){const _errs17 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate64(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate64.errors : vErrors.concat(validate64.errors);errors = vErrors.length;}}}var _valid2 = _errs17 === errors;valid7 = _valid2;if(valid7){var props2 = {};props2.payload = true;props2.eventType = true;}}if(!valid7){const err5 = {instancePath,schemaPath:"#/allOf/2/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(props0 !== true && props2 !== undefined){if(props2 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props2);}}const _errs20 = errors;let valid10 = true;const _errs21 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data6 = data.eventType;if(!(((data6 === "delegation.started") || (data6 === "delegation.updated")) || (data6 === "delegation.completed"))){const err6 = {};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}var _valid3 = _errs21 === errors;errors = _errs20;if(vErrors !== null){if(_errs20){vErrors.length = _errs20;}else {vErrors = null;}}if(_valid3){const _errs23 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate66(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate66.errors : vErrors.concat(validate66.errors);errors = vErrors.length;}}}var _valid3 = _errs23 === errors;valid10 = _valid3;if(valid10){var props3 = {};props3.payload = true;props3.eventType = true;}}if(!valid10){const err7 = {instancePath,schemaPath:"#/allOf/3/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(props0 !== true && props3 !== undefined){if(props3 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props3);}}const _errs26 = errors;let valid13 = true;const _errs27 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("model.route.changed" !== data.eventType){const err8 = {};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}}var _valid4 = _errs27 === errors;errors = _errs26;if(vErrors !== null){if(_errs26){vErrors.length = _errs26;}else {vErrors = null;}}if(_valid4){const _errs29 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate68(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate68.errors : vErrors.concat(validate68.errors);errors = vErrors.length;}}}var _valid4 = _errs29 === errors;valid13 = _valid4;if(valid13){var props4 = {};props4.payload = true;props4.eventType = true;}}if(!valid13){const err9 = {instancePath,schemaPath:"#/allOf/4/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(props0 !== true && props4 !== undefined){if(props4 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props4);}}const _errs32 = errors;let valid16 = true;const _errs33 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("model.verification.updated" !== data.eventType){const err10 = {};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}}var _valid5 = _errs33 === errors;errors = _errs32;if(vErrors !== null){if(_errs32){vErrors.length = _errs32;}else {vErrors = null;}}if(_valid5){const _errs35 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate70(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate70.errors : vErrors.concat(validate70.errors);errors = vErrors.length;}}}var _valid5 = _errs35 === errors;valid16 = _valid5;if(valid16){var props5 = {};props5.payload = true;props5.eventType = true;}}if(!valid16){const err11 = {instancePath,schemaPath:"#/allOf/5/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(props0 !== true && props5 !== undefined){if(props5 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props5);}}const _errs38 = errors;let valid19 = true;const _errs39 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("context.compacted" !== data.eventType){const err12 = {};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}var _valid6 = _errs39 === errors;errors = _errs38;if(vErrors !== null){if(_errs38){vErrors.length = _errs38;}else {vErrors = null;}}if(_valid6){const _errs41 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate72(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate72.errors : vErrors.concat(validate72.errors);errors = vErrors.length;}}}var _valid6 = _errs41 === errors;valid19 = _valid6;if(valid19){var props6 = {};props6.payload = true;props6.eventType = true;}}if(!valid19){const err13 = {instancePath,schemaPath:"#/allOf/6/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(props0 !== true && props6 !== undefined){if(props6 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props6);}}const _errs44 = errors;let valid22 = true;const _errs45 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("artifact.viewed" !== data.eventType){const err14 = {};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}}var _valid7 = _errs45 === errors;errors = _errs44;if(vErrors !== null){if(_errs44){vErrors.length = _errs44;}else {vErrors = null;}}if(_valid7){const _errs47 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate74(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate74.errors : vErrors.concat(validate74.errors);errors = vErrors.length;}}}var _valid7 = _errs47 === errors;valid22 = _valid7;if(valid22){var props7 = {};props7.payload = true;props7.eventType = true;}}if(!valid22){const err15 = {instancePath,schemaPath:"#/allOf/7/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(props0 !== true && props7 !== undefined){if(props7 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props7);}}const _errs50 = errors;let valid25 = true;const _errs51 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("artifact.generated" !== data.eventType){const err16 = {};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}var _valid8 = _errs51 === errors;errors = _errs50;if(vErrors !== null){if(_errs50){vErrors.length = _errs50;}else {vErrors = null;}}if(_valid8){const _errs53 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate76(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate76.errors : vErrors.concat(validate76.errors);errors = vErrors.length;}}}var _valid8 = _errs53 === errors;valid25 = _valid8;if(valid25){var props8 = {};props8.payload = true;props8.eventType = true;}}if(!valid25){const err17 = {instancePath,schemaPath:"#/allOf/8/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(props0 !== true && props8 !== undefined){if(props8 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props8);}}const _errs56 = errors;let valid28 = true;const _errs57 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("review.mode.changed" !== data.eventType){const err18 = {};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}}var _valid9 = _errs57 === errors;errors = _errs56;if(vErrors !== null){if(_errs56){vErrors.length = _errs56;}else {vErrors = null;}}if(_valid9){const _errs59 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate78(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate78.errors : vErrors.concat(validate78.errors);errors = vErrors.length;}}}var _valid9 = _errs59 === errors;valid28 = _valid9;if(valid28){var props9 = {};props9.payload = true;props9.eventType = true;}}if(!valid28){const err19 = {instancePath,schemaPath:"#/allOf/9/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(props0 !== true && props9 !== undefined){if(props9 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props9);}}const _errs62 = errors;let valid31 = true;const _errs63 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data20 = data.eventType;if(!((data20 === "hook.started") || (data20 === "hook.completed"))){const err20 = {};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}var _valid10 = _errs63 === errors;errors = _errs62;if(vErrors !== null){if(_errs62){vErrors.length = _errs62;}else {vErrors = null;}}if(_valid10){const _errs65 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate80(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate80.errors : vErrors.concat(validate80.errors);errors = vErrors.length;}}}var _valid10 = _errs65 === errors;valid31 = _valid10;if(valid31){var props10 = {};props10.payload = true;props10.eventType = true;}}if(!valid31){const err21 = {instancePath,schemaPath:"#/allOf/10/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(props0 !== true && props10 !== undefined){if(props10 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props10);}}const _errs68 = errors;let valid34 = true;const _errs69 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("memory.citation.referenced" !== data.eventType){const err22 = {};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}}var _valid11 = _errs69 === errors;errors = _errs68;if(vErrors !== null){if(_errs68){vErrors.length = _errs68;}else {vErrors = null;}}if(_valid11){const _errs71 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate82(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate82.errors : vErrors.concat(validate82.errors);errors = vErrors.length;}}}var _valid11 = _errs71 === errors;valid34 = _valid11;if(valid34){var props11 = {};props11.payload = true;props11.eventType = true;}}if(!valid34){const err23 = {instancePath,schemaPath:"#/allOf/11/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(props0 !== true && props11 !== undefined){if(props11 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props11);}}const _errs74 = errors;let valid37 = true;const _errs75 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data24 = data.eventType;if(!((data24 === "safety.review.started") || (data24 === "safety.review.completed"))){const err24 = {};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}}var _valid12 = _errs75 === errors;errors = _errs74;if(vErrors !== null){if(_errs74){vErrors.length = _errs74;}else {vErrors = null;}}if(_valid12){const _errs77 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate84(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate84.errors : vErrors.concat(validate84.errors);errors = vErrors.length;}}}var _valid12 = _errs77 === errors;valid37 = _valid12;if(valid37){var props12 = {};props12.payload = true;props12.eventType = true;}}if(!valid37){const err25 = {instancePath,schemaPath:"#/allOf/12/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(props0 !== true && props12 !== undefined){if(props12 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props12);}}const _errs80 = errors;let valid40 = true;const _errs81 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("terminal.input.sent" !== data.eventType){const err26 = {};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}}var _valid13 = _errs81 === errors;errors = _errs80;if(vErrors !== null){if(_errs80){vErrors.length = _errs80;}else {vErrors = null;}}if(_valid13){const _errs83 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate86(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate86.errors : vErrors.concat(validate86.errors);errors = vErrors.length;}}}var _valid13 = _errs83 === errors;valid40 = _valid13;if(valid40){var props13 = {};props13.payload = true;props13.eventType = true;}}if(!valid40){const err27 = {instancePath,schemaPath:"#/allOf/13/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(props0 !== true && props13 !== undefined){if(props13 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props13);}}const _errs86 = errors;let valid43 = true;const _errs87 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data28 = data.eventType;if(!((data28 === "wait.started") || (data28 === "wait.completed"))){const err28 = {};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}var _valid14 = _errs87 === errors;errors = _errs86;if(vErrors !== null){if(_errs86){vErrors.length = _errs86;}else {vErrors = null;}}if(_valid14){const _errs89 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate88(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate88.errors : vErrors.concat(validate88.errors);errors = vErrors.length;}}}var _valid14 = _errs89 === errors;valid43 = _valid14;if(valid43){var props14 = {};props14.payload = true;props14.eventType = true;}}if(!valid43){const err29 = {instancePath,schemaPath:"#/allOf/14/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(props0 !== true && props14 !== undefined){if(props14 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props14);}}const _errs92 = errors;let valid46 = true;const _errs93 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("provider.notice.recorded" !== data.eventType){const err30 = {};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}}var _valid15 = _errs93 === errors;errors = _errs92;if(vErrors !== null){if(_errs92){vErrors.length = _errs92;}else {vErrors = null;}}if(_valid15){const _errs95 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate90(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate90.errors : vErrors.concat(validate90.errors);errors = vErrors.length;}}}var _valid15 = _errs95 === errors;valid46 = _valid15;if(valid46){var props15 = {};props15.payload = true;props15.eventType = true;}}if(!valid46){const err31 = {instancePath,schemaPath:"#/allOf/15/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(props0 !== true && props15 !== undefined){if(props15 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props15);}}const _errs98 = errors;let valid49 = true;const _errs99 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("workspace.file.referenced" !== data.eventType){const err32 = {};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}}var _valid16 = _errs99 === errors;errors = _errs98;if(vErrors !== null){if(_errs98){vErrors.length = _errs98;}else {vErrors = null;}}if(_valid16){const _errs101 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data33 = data.payload;if(data33 && typeof data33 == "object" && !Array.isArray(data33)){if(data33.schema === undefined){const err33 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(data33.referenceId === undefined){const err34 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "referenceId"},message:"must have required property '"+"referenceId"+"'"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(data33.source === undefined){const err35 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "source"},message:"must have required property '"+"source"+"'"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(data33.path === undefined){const err36 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "path"},message:"must have required property '"+"path"+"'"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}if(data33.displayName === undefined){const err37 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "displayName"},message:"must have required property '"+"displayName"+"'"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(data33.mediaType === undefined){const err38 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "mediaType"},message:"must have required property '"+"mediaType"+"'"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(data33.presentation === undefined){const err39 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "presentation"},message:"must have required property '"+"presentation"+"'"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}if(data33.line === undefined){const err40 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "line"},message:"must have required property '"+"line"+"'"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if(data33.preview === undefined){const err41 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "preview"},message:"must have required property '"+"preview"+"'"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}if(data33.previewTruncated === undefined){const err42 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "previewTruncated"},message:"must have required property '"+"previewTruncated"+"'"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}if(data33.contentDigest === undefined){const err43 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "contentDigest"},message:"must have required property '"+"contentDigest"+"'"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key0 in data33){if(!(func58.call(schema171.properties, key0))){const err44 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data33.schema !== undefined){if("paperclip.workspace.file_reference.v1" !== data33.schema){const err45 = {instancePath:instancePath+"/payload/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.workspace.file_reference.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}if(data33.referenceId !== undefined){let data35 = data33.referenceId;if(typeof data35 === "string"){if(func1(data35) > 240){const err46 = {instancePath:instancePath+"/payload/referenceId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/referenceId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}if(func1(data35) < 1){const err47 = {instancePath:instancePath+"/payload/referenceId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/referenceId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}else {const err48 = {instancePath:instancePath+"/payload/referenceId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/referenceId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}if(data33.source !== undefined){let data36 = data33.source;if(!((data36 === "harness_reported") || (data36 === "runner_verified"))){const err49 = {instancePath:instancePath+"/payload/source",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/source/enum",keyword:"enum",params:{allowedValues: schema171.properties.source.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data33.path !== undefined){let data37 = data33.path;if(typeof data37 === "string"){if(func1(data37) > 1024){const err50 = {instancePath:instancePath+"/payload/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/path/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}if(func1(data37) < 1){const err51 = {instancePath:instancePath+"/payload/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}if(!pattern69.test(data37)){const err52 = {instancePath:instancePath+"/payload/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/path/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}else {const err53 = {instancePath:instancePath+"/payload/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data33.displayName !== undefined){let data38 = data33.displayName;if(typeof data38 === "string"){if(func1(data38) > 255){const err54 = {instancePath:instancePath+"/payload/displayName",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/displayName/maxLength",keyword:"maxLength",params:{limit: 255},message:"must NOT have more than 255 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}if(func1(data38) < 1){const err55 = {instancePath:instancePath+"/payload/displayName",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/displayName/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}else {const err56 = {instancePath:instancePath+"/payload/displayName",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/displayName/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data33.mediaType !== undefined){let data39 = data33.mediaType;if((typeof data39 !== "string") && (data39 !== null)){const err57 = {instancePath:instancePath+"/payload/mediaType",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/mediaType/type",keyword:"type",params:{type: schema171.properties.mediaType.type},message:"must be string,null"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}if(typeof data39 === "string"){if(func1(data39) > 160){const err58 = {instancePath:instancePath+"/payload/mediaType",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/mediaType/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}}if(data33.presentation !== undefined){let data40 = data33.presentation;if(!((((data40 === "document") || (data40 === "code")) || (data40 === "image")) || (data40 === "generic"))){const err59 = {instancePath:instancePath+"/payload/presentation",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/presentation/enum",keyword:"enum",params:{allowedValues: schema171.properties.presentation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}if(data33.line !== undefined){let data41 = data33.line;if((!(((typeof data41 == "number") && (!(data41 % 1) && !isNaN(data41))) && (isFinite(data41)))) && (data41 !== null)){const err60 = {instancePath:instancePath+"/payload/line",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/line/type",keyword:"type",params:{type: schema171.properties.line.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}if((typeof data41 == "number") && (isFinite(data41))){if(data41 < 1 || isNaN(data41)){const err61 = {instancePath:instancePath+"/payload/line",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/line/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}}if(data33.preview !== undefined){let data42 = data33.preview;if((typeof data42 !== "string") && (data42 !== null)){const err62 = {instancePath:instancePath+"/payload/preview",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/preview/type",keyword:"type",params:{type: schema171.properties.preview.type},message:"must be string,null"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(typeof data42 === "string"){if(func1(data42) > 262144){const err63 = {instancePath:instancePath+"/payload/preview",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/preview/maxLength",keyword:"maxLength",params:{limit: 262144},message:"must NOT have more than 262144 characters"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}}if(data33.previewTruncated !== undefined){if(typeof data33.previewTruncated !== "boolean"){const err64 = {instancePath:instancePath+"/payload/previewTruncated",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/previewTruncated/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}if(data33.contentDigest !== undefined){let data44 = data33.contentDigest;const _errs124 = errors;let valid54 = false;let passing0 = null;const _errs125 = errors;if(data44 !== null){const err65 = {instancePath:instancePath+"/payload/contentDigest",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/contentDigest/oneOf/0/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}var _valid17 = _errs125 === errors;if(_valid17){valid54 = true;passing0 = 0;}const _errs127 = errors;if(typeof data44 === "string"){if(!pattern19.test(data44)){const err66 = {instancePath:instancePath+"/payload/contentDigest",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/contentDigest/oneOf/1/pattern",keyword:"pattern",params:{pattern: "^sha256:[a-f0-9]{64}$"},message:"must match pattern \""+"^sha256:[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}else {const err67 = {instancePath:instancePath+"/payload/contentDigest",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/contentDigest/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}var _valid17 = _errs127 === errors;if(_valid17 && valid54){valid54 = false;passing0 = [passing0, 1];}else {if(_valid17){valid54 = true;passing0 = 1;}}if(!valid54){const err68 = {instancePath:instancePath+"/payload/contentDigest",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/contentDigest/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}else {errors = _errs124;if(vErrors !== null){if(_errs124){vErrors.length = _errs124;}else {vErrors = null;}}}}}else {const err69 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}}var _valid16 = _errs101 === errors;valid49 = _valid16;if(valid49){var props16 = {};props16.payload = true;props16.eventType = true;}}if(!valid49){const err70 = {instancePath,schemaPath:"#/allOf/16/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if(props0 !== true && props16 !== undefined){if(props16 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props16);}}const _errs130 = errors;let valid55 = true;const _errs131 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data45 = data.eventType;if(!((data45 === "workspace.change.updated") || (data45 === "workspace.diff.recorded"))){const err71 = {};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}}var _valid18 = _errs131 === errors;errors = _errs130;if(vErrors !== null){if(_errs130){vErrors.length = _errs130;}else {vErrors = null;}}if(_valid18){const _errs133 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data46 = data.payload;if(data46 && typeof data46 == "object" && !Array.isArray(data46)){if(data46.schema === undefined){const err72 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}if(data46.changeSetId === undefined){const err73 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "changeSetId"},message:"must have required property '"+"changeSetId"+"'"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(data46.revision === undefined){const err74 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "revision"},message:"must have required property '"+"revision"+"'"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}if(data46.source === undefined){const err75 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "source"},message:"must have required property '"+"source"+"'"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}if(data46.complete === undefined){const err76 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "complete"},message:"must have required property '"+"complete"+"'"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}if(data46.files === undefined){const err77 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "files"},message:"must have required property '"+"files"+"'"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(data46.totals === undefined){const err78 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "totals"},message:"must have required property '"+"totals"+"'"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}if(data46.patchArtifactRef === undefined){const err79 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "patchArtifactRef"},message:"must have required property '"+"patchArtifactRef"+"'"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}for(const key1 in data46){if(!((((((((key1 === "schema") || (key1 === "changeSetId")) || (key1 === "revision")) || (key1 === "source")) || (key1 === "complete")) || (key1 === "files")) || (key1 === "totals")) || (key1 === "patchArtifactRef"))){const err80 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data46.schema !== undefined){if("paperclip.workspace.diff.v1" !== data46.schema){const err81 = {instancePath:instancePath+"/payload/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.workspace.diff.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}if(data46.changeSetId !== undefined){let data48 = data46.changeSetId;if(typeof data48 === "string"){if(func1(data48) > 200){const err82 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/maxLength",keyword:"maxLength",params:{limit: 200},message:"must NOT have more than 200 characters"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}if(func1(data48) < 1){const err83 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}else {const err84 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}if(data46.revision !== undefined){let data49 = data46.revision;if(!(((typeof data49 == "number") && (!(data49 % 1) && !isNaN(data49))) && (isFinite(data49)))){const err85 = {instancePath:instancePath+"/payload/revision",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/revision/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}if((typeof data49 == "number") && (isFinite(data49))){if(data49 < 1 || isNaN(data49)){const err86 = {instancePath:instancePath+"/payload/revision",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/revision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}}if(data46.source !== undefined){let data50 = data46.source;if(!((data50 === "harness_reported") || (data50 === "runner_verified"))){const err87 = {instancePath:instancePath+"/payload/source",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/source/enum",keyword:"enum",params:{allowedValues: schema172.properties.source.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}if(data46.complete !== undefined){if(typeof data46.complete !== "boolean"){const err88 = {instancePath:instancePath+"/payload/complete",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/complete/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data46.files !== undefined){let data52 = data46.files;if(Array.isArray(data52)){if(data52.length > 2000){const err89 = {instancePath:instancePath+"/payload/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/maxItems",keyword:"maxItems",params:{limit: 2000},message:"must NOT have more than 2000 items"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}const len0 = data52.length;for(let i0=0; i0 1024){const err98 = {instancePath:instancePath+"/payload/files/" + i0+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}if(func1(data54) < 1){const err99 = {instancePath:instancePath+"/payload/files/" + i0+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}if(!pattern69.test(data54)){const err100 = {instancePath:instancePath+"/payload/files/" + i0+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}else {const err101 = {instancePath:instancePath+"/payload/files/" + i0+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}}if(data53.operation !== undefined){let data55 = data53.operation;if(!(((((data55 === "create") || (data55 === "modify")) || (data55 === "delete")) || (data55 === "rename")) || (data55 === "mode_change"))){const err102 = {instancePath:instancePath+"/payload/files/" + i0+"/operation",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/operation/enum",keyword:"enum",params:{allowedValues: schema172.properties.files.items.properties.operation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}if(data53.previousPath !== undefined){let data56 = data53.previousPath;const _errs155 = errors;let valid63 = false;let passing1 = null;const _errs156 = errors;if(data56 !== null){const err103 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/0/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}var _valid19 = _errs156 === errors;if(_valid19){valid63 = true;passing1 = 0;}const _errs158 = errors;if(typeof data56 === "string"){if(func1(data56) > 1024){const err104 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}if(func1(data56) < 1){const err105 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}if(!pattern69.test(data56)){const err106 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}else {const err107 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}var _valid19 = _errs158 === errors;if(_valid19 && valid63){valid63 = false;passing1 = [passing1, 1];}else {if(_valid19){valid63 = true;passing1 = 1;}}if(!valid63){const err108 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf",keyword:"oneOf",params:{passingSchemas: passing1},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}else {errors = _errs155;if(vErrors !== null){if(_errs155){vErrors.length = _errs155;}else {vErrors = null;}}}}if(data53.additions !== undefined){let data57 = data53.additions;if((!(((typeof data57 == "number") && (!(data57 % 1) && !isNaN(data57))) && (isFinite(data57)))) && (data57 !== null)){const err109 = {instancePath:instancePath+"/payload/files/" + i0+"/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/additions/type",keyword:"type",params:{type: schema172.properties.files.items.properties.additions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}if((typeof data57 == "number") && (isFinite(data57))){if(data57 < 0 || isNaN(data57)){const err110 = {instancePath:instancePath+"/payload/files/" + i0+"/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/additions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}}if(data53.deletions !== undefined){let data58 = data53.deletions;if((!(((typeof data58 == "number") && (!(data58 % 1) && !isNaN(data58))) && (isFinite(data58)))) && (data58 !== null)){const err111 = {instancePath:instancePath+"/payload/files/" + i0+"/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/deletions/type",keyword:"type",params:{type: schema172.properties.files.items.properties.deletions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}if((typeof data58 == "number") && (isFinite(data58))){if(data58 < 0 || isNaN(data58)){const err112 = {instancePath:instancePath+"/payload/files/" + i0+"/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/deletions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}}if(data53.binary !== undefined){if(typeof data53.binary !== "boolean"){const err113 = {instancePath:instancePath+"/payload/files/" + i0+"/binary",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/binary/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}if(data53.diff !== undefined){let data60 = data53.diff;if((typeof data60 !== "string") && (data60 !== null)){const err114 = {instancePath:instancePath+"/payload/files/" + i0+"/diff",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/diff/type",keyword:"type",params:{type: schema172.properties.files.items.properties.diff.type},message:"must be string,null"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}if(typeof data60 === "string"){if(func1(data60) > 262144){const err115 = {instancePath:instancePath+"/payload/files/" + i0+"/diff",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/diff/maxLength",keyword:"maxLength",params:{limit: 262144},message:"must NOT have more than 262144 characters"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}}}else {const err116 = {instancePath:instancePath+"/payload/files/" + i0,schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}}else {const err117 = {instancePath:instancePath+"/payload/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}}if(data46.totals !== undefined){let data61 = data46.totals;if(data61 && typeof data61 == "object" && !Array.isArray(data61)){if(data61.files === undefined){const err118 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "files"},message:"must have required property '"+"files"+"'"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}if(data61.additions === undefined){const err119 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "additions"},message:"must have required property '"+"additions"+"'"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}if(data61.deletions === undefined){const err120 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "deletions"},message:"must have required property '"+"deletions"+"'"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}for(const key3 in data61){if(!(((key3 === "files") || (key3 === "additions")) || (key3 === "deletions"))){const err121 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}if(data61.files !== undefined){let data62 = data61.files;if(!(((typeof data62 == "number") && (!(data62 % 1) && !isNaN(data62))) && (isFinite(data62)))){const err122 = {instancePath:instancePath+"/payload/totals/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/files/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}if((typeof data62 == "number") && (isFinite(data62))){if(data62 < 0 || isNaN(data62)){const err123 = {instancePath:instancePath+"/payload/totals/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/files/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}}if(data61.additions !== undefined){let data63 = data61.additions;if((!(((typeof data63 == "number") && (!(data63 % 1) && !isNaN(data63))) && (isFinite(data63)))) && (data63 !== null)){const err124 = {instancePath:instancePath+"/payload/totals/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/additions/type",keyword:"type",params:{type: schema172.properties.totals.properties.additions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}if((typeof data63 == "number") && (isFinite(data63))){if(data63 < 0 || isNaN(data63)){const err125 = {instancePath:instancePath+"/payload/totals/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/additions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}}if(data61.deletions !== undefined){let data64 = data61.deletions;if((!(((typeof data64 == "number") && (!(data64 % 1) && !isNaN(data64))) && (isFinite(data64)))) && (data64 !== null)){const err126 = {instancePath:instancePath+"/payload/totals/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/deletions/type",keyword:"type",params:{type: schema172.properties.totals.properties.deletions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}if((typeof data64 == "number") && (isFinite(data64))){if(data64 < 0 || isNaN(data64)){const err127 = {instancePath:instancePath+"/payload/totals/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/deletions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}}}else {const err128 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}if(data46.patchArtifactRef !== undefined){let data65 = data46.patchArtifactRef;if((typeof data65 !== "string") && (data65 !== null)){const err129 = {instancePath:instancePath+"/payload/patchArtifactRef",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/patchArtifactRef/type",keyword:"type",params:{type: schema172.properties.patchArtifactRef.type},message:"must be string,null"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}if(typeof data65 === "string"){if(func1(data65) > 2048){const err130 = {instancePath:instancePath+"/payload/patchArtifactRef",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/patchArtifactRef/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}}}}else {const err131 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}}var _valid18 = _errs133 === errors;valid55 = _valid18;if(valid55){var props17 = {};props17.payload = true;props17.eventType = true;}}if(!valid55){const err132 = {instancePath,schemaPath:"#/allOf/17/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}if(props0 !== true && props17 !== undefined){if(props17 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props17);}}const _errs180 = errors;let valid65 = true;const _errs181 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("workspace.diff.recorded" !== data.eventType){const err133 = {};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}}var _valid20 = _errs181 === errors;errors = _errs180;if(vErrors !== null){if(_errs180){vErrors.length = _errs180;}else {vErrors = null;}}if(_valid20){const _errs183 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data67 = data.payload;if(data67 && typeof data67 == "object" && !Array.isArray(data67)){if(data67.schema === undefined){const err134 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}if(data67.changeSetId === undefined){const err135 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "changeSetId"},message:"must have required property '"+"changeSetId"+"'"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}if(data67.revision === undefined){const err136 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "revision"},message:"must have required property '"+"revision"+"'"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}if(data67.source === undefined){const err137 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "source"},message:"must have required property '"+"source"+"'"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}if(data67.complete === undefined){const err138 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "complete"},message:"must have required property '"+"complete"+"'"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}if(data67.files === undefined){const err139 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "files"},message:"must have required property '"+"files"+"'"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}if(data67.totals === undefined){const err140 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "totals"},message:"must have required property '"+"totals"+"'"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}if(data67.patchArtifactRef === undefined){const err141 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "patchArtifactRef"},message:"must have required property '"+"patchArtifactRef"+"'"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}for(const key4 in data67){if(!((((((((key4 === "schema") || (key4 === "changeSetId")) || (key4 === "revision")) || (key4 === "source")) || (key4 === "complete")) || (key4 === "files")) || (key4 === "totals")) || (key4 === "patchArtifactRef"))){const err142 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}if(data67.schema !== undefined){if("paperclip.workspace.diff.v1" !== data67.schema){const err143 = {instancePath:instancePath+"/payload/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.workspace.diff.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}if(data67.changeSetId !== undefined){let data69 = data67.changeSetId;if(typeof data69 === "string"){if(func1(data69) > 200){const err144 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/maxLength",keyword:"maxLength",params:{limit: 200},message:"must NOT have more than 200 characters"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}if(func1(data69) < 1){const err145 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}else {const err146 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}}if(data67.revision !== undefined){let data70 = data67.revision;if(!(((typeof data70 == "number") && (!(data70 % 1) && !isNaN(data70))) && (isFinite(data70)))){const err147 = {instancePath:instancePath+"/payload/revision",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/revision/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}if((typeof data70 == "number") && (isFinite(data70))){if(data70 < 1 || isNaN(data70)){const err148 = {instancePath:instancePath+"/payload/revision",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/revision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}}}if(data67.source !== undefined){let data71 = data67.source;if(!((data71 === "harness_reported") || (data71 === "runner_verified"))){const err149 = {instancePath:instancePath+"/payload/source",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/source/enum",keyword:"enum",params:{allowedValues: schema172.properties.source.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}}if(data67.complete !== undefined){if(typeof data67.complete !== "boolean"){const err150 = {instancePath:instancePath+"/payload/complete",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/complete/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err150];}else {vErrors.push(err150);}errors++;}}if(data67.files !== undefined){let data73 = data67.files;if(Array.isArray(data73)){if(data73.length > 2000){const err151 = {instancePath:instancePath+"/payload/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/maxItems",keyword:"maxItems",params:{limit: 2000},message:"must NOT have more than 2000 items"};if(vErrors === null){vErrors = [err151];}else {vErrors.push(err151);}errors++;}const len1 = data73.length;for(let i1=0; i1 1024){const err160 = {instancePath:instancePath+"/payload/files/" + i1+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err160];}else {vErrors.push(err160);}errors++;}if(func1(data75) < 1){const err161 = {instancePath:instancePath+"/payload/files/" + i1+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err161];}else {vErrors.push(err161);}errors++;}if(!pattern69.test(data75)){const err162 = {instancePath:instancePath+"/payload/files/" + i1+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err162];}else {vErrors.push(err162);}errors++;}}else {const err163 = {instancePath:instancePath+"/payload/files/" + i1+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err163];}else {vErrors.push(err163);}errors++;}}if(data74.operation !== undefined){let data76 = data74.operation;if(!(((((data76 === "create") || (data76 === "modify")) || (data76 === "delete")) || (data76 === "rename")) || (data76 === "mode_change"))){const err164 = {instancePath:instancePath+"/payload/files/" + i1+"/operation",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/operation/enum",keyword:"enum",params:{allowedValues: schema172.properties.files.items.properties.operation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err164];}else {vErrors.push(err164);}errors++;}}if(data74.previousPath !== undefined){let data77 = data74.previousPath;const _errs206 = errors;let valid74 = false;let passing2 = null;const _errs207 = errors;if(data77 !== null){const err165 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/0/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err165];}else {vErrors.push(err165);}errors++;}var _valid21 = _errs207 === errors;if(_valid21){valid74 = true;passing2 = 0;}const _errs209 = errors;if(typeof data77 === "string"){if(func1(data77) > 1024){const err166 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err166];}else {vErrors.push(err166);}errors++;}if(func1(data77) < 1){const err167 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err167];}else {vErrors.push(err167);}errors++;}if(!pattern69.test(data77)){const err168 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err168];}else {vErrors.push(err168);}errors++;}}else {const err169 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err169];}else {vErrors.push(err169);}errors++;}var _valid21 = _errs209 === errors;if(_valid21 && valid74){valid74 = false;passing2 = [passing2, 1];}else {if(_valid21){valid74 = true;passing2 = 1;}}if(!valid74){const err170 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf",keyword:"oneOf",params:{passingSchemas: passing2},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err170];}else {vErrors.push(err170);}errors++;}else {errors = _errs206;if(vErrors !== null){if(_errs206){vErrors.length = _errs206;}else {vErrors = null;}}}}if(data74.additions !== undefined){let data78 = data74.additions;if((!(((typeof data78 == "number") && (!(data78 % 1) && !isNaN(data78))) && (isFinite(data78)))) && (data78 !== null)){const err171 = {instancePath:instancePath+"/payload/files/" + i1+"/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/additions/type",keyword:"type",params:{type: schema172.properties.files.items.properties.additions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err171];}else {vErrors.push(err171);}errors++;}if((typeof data78 == "number") && (isFinite(data78))){if(data78 < 0 || isNaN(data78)){const err172 = {instancePath:instancePath+"/payload/files/" + i1+"/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/additions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err172];}else {vErrors.push(err172);}errors++;}}}if(data74.deletions !== undefined){let data79 = data74.deletions;if((!(((typeof data79 == "number") && (!(data79 % 1) && !isNaN(data79))) && (isFinite(data79)))) && (data79 !== null)){const err173 = {instancePath:instancePath+"/payload/files/" + i1+"/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/deletions/type",keyword:"type",params:{type: schema172.properties.files.items.properties.deletions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err173];}else {vErrors.push(err173);}errors++;}if((typeof data79 == "number") && (isFinite(data79))){if(data79 < 0 || isNaN(data79)){const err174 = {instancePath:instancePath+"/payload/files/" + i1+"/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/deletions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err174];}else {vErrors.push(err174);}errors++;}}}if(data74.binary !== undefined){if(typeof data74.binary !== "boolean"){const err175 = {instancePath:instancePath+"/payload/files/" + i1+"/binary",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/binary/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err175];}else {vErrors.push(err175);}errors++;}}if(data74.diff !== undefined){let data81 = data74.diff;if((typeof data81 !== "string") && (data81 !== null)){const err176 = {instancePath:instancePath+"/payload/files/" + i1+"/diff",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/diff/type",keyword:"type",params:{type: schema172.properties.files.items.properties.diff.type},message:"must be string,null"};if(vErrors === null){vErrors = [err176];}else {vErrors.push(err176);}errors++;}if(typeof data81 === "string"){if(func1(data81) > 262144){const err177 = {instancePath:instancePath+"/payload/files/" + i1+"/diff",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/diff/maxLength",keyword:"maxLength",params:{limit: 262144},message:"must NOT have more than 262144 characters"};if(vErrors === null){vErrors = [err177];}else {vErrors.push(err177);}errors++;}}}}else {const err178 = {instancePath:instancePath+"/payload/files/" + i1,schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err178];}else {vErrors.push(err178);}errors++;}}}else {const err179 = {instancePath:instancePath+"/payload/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err179];}else {vErrors.push(err179);}errors++;}}if(data67.totals !== undefined){let data82 = data67.totals;if(data82 && typeof data82 == "object" && !Array.isArray(data82)){if(data82.files === undefined){const err180 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "files"},message:"must have required property '"+"files"+"'"};if(vErrors === null){vErrors = [err180];}else {vErrors.push(err180);}errors++;}if(data82.additions === undefined){const err181 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "additions"},message:"must have required property '"+"additions"+"'"};if(vErrors === null){vErrors = [err181];}else {vErrors.push(err181);}errors++;}if(data82.deletions === undefined){const err182 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "deletions"},message:"must have required property '"+"deletions"+"'"};if(vErrors === null){vErrors = [err182];}else {vErrors.push(err182);}errors++;}for(const key6 in data82){if(!(((key6 === "files") || (key6 === "additions")) || (key6 === "deletions"))){const err183 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err183];}else {vErrors.push(err183);}errors++;}}if(data82.files !== undefined){let data83 = data82.files;if(!(((typeof data83 == "number") && (!(data83 % 1) && !isNaN(data83))) && (isFinite(data83)))){const err184 = {instancePath:instancePath+"/payload/totals/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/files/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err184];}else {vErrors.push(err184);}errors++;}if((typeof data83 == "number") && (isFinite(data83))){if(data83 < 0 || isNaN(data83)){const err185 = {instancePath:instancePath+"/payload/totals/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/files/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err185];}else {vErrors.push(err185);}errors++;}}}if(data82.additions !== undefined){let data84 = data82.additions;if((!(((typeof data84 == "number") && (!(data84 % 1) && !isNaN(data84))) && (isFinite(data84)))) && (data84 !== null)){const err186 = {instancePath:instancePath+"/payload/totals/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/additions/type",keyword:"type",params:{type: schema172.properties.totals.properties.additions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err186];}else {vErrors.push(err186);}errors++;}if((typeof data84 == "number") && (isFinite(data84))){if(data84 < 0 || isNaN(data84)){const err187 = {instancePath:instancePath+"/payload/totals/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/additions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err187];}else {vErrors.push(err187);}errors++;}}}if(data82.deletions !== undefined){let data85 = data82.deletions;if((!(((typeof data85 == "number") && (!(data85 % 1) && !isNaN(data85))) && (isFinite(data85)))) && (data85 !== null)){const err188 = {instancePath:instancePath+"/payload/totals/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/deletions/type",keyword:"type",params:{type: schema172.properties.totals.properties.deletions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err188];}else {vErrors.push(err188);}errors++;}if((typeof data85 == "number") && (isFinite(data85))){if(data85 < 0 || isNaN(data85)){const err189 = {instancePath:instancePath+"/payload/totals/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/deletions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err189];}else {vErrors.push(err189);}errors++;}}}}else {const err190 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err190];}else {vErrors.push(err190);}errors++;}}if(data67.patchArtifactRef !== undefined){let data86 = data67.patchArtifactRef;if((typeof data86 !== "string") && (data86 !== null)){const err191 = {instancePath:instancePath+"/payload/patchArtifactRef",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/patchArtifactRef/type",keyword:"type",params:{type: schema172.properties.patchArtifactRef.type},message:"must be string,null"};if(vErrors === null){vErrors = [err191];}else {vErrors.push(err191);}errors++;}if(typeof data86 === "string"){if(func1(data86) > 2048){const err192 = {instancePath:instancePath+"/payload/patchArtifactRef",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/patchArtifactRef/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err192];}else {vErrors.push(err192);}errors++;}}}}else {const err193 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err193];}else {vErrors.push(err193);}errors++;}if(data67 && typeof data67 == "object" && !Array.isArray(data67)){if(data67.complete !== undefined){if(true !== data67.complete){const err194 = {instancePath:instancePath+"/payload/complete",schemaPath:"#/allOf/18/then/properties/payload/allOf/1/properties/complete/const",keyword:"const",params:{allowedValue: true},message:"must be equal to constant"};if(vErrors === null){vErrors = [err194];}else {vErrors.push(err194);}errors++;}}}else {const err195 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/18/then/properties/payload/allOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err195];}else {vErrors.push(err195);}errors++;}}}var _valid20 = _errs183 === errors;valid65 = _valid20;if(valid65){var props18 = {};props18.payload = true;props18.eventType = true;}}if(!valid65){const err196 = {instancePath,schemaPath:"#/allOf/18/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err196];}else {vErrors.push(err196);}errors++;}if(props0 !== true && props18 !== undefined){if(props18 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props18);}}const _errs234 = errors;let valid77 = true;const _errs235 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data88 = data.eventType;if(!((data88 === "semantic_tool.input") || (data88 === "mcp_app.tool_input"))){const err197 = {};if(vErrors === null){vErrors = [err197];}else {vErrors.push(err197);}errors++;}}}var _valid22 = _errs235 === errors;errors = _errs234;if(vErrors !== null){if(_errs234){vErrors.length = _errs234;}else {vErrors = null;}}if(_valid22){const _errs237 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data89 = data.payload;if(data89 && typeof data89 == "object" && !Array.isArray(data89)){if(data89.semantic_tool !== undefined){let data90 = data89.semantic_tool;if(!(validate92(data90, {instancePath:instancePath+"/payload/semantic_tool",parentData:data89,parentDataProperty:"semantic_tool",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate92.errors : vErrors.concat(validate92.errors);errors = vErrors.length;}if(data90 && typeof data90 == "object" && !Array.isArray(data90)){if(data90.phase !== undefined){if("input" !== data90.phase){const err198 = {instancePath:instancePath+"/payload/semantic_tool/phase",schemaPath:"#/allOf/19/then/properties/payload/properties/semantic_tool/allOf/1/properties/phase/const",keyword:"const",params:{allowedValue: "input"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err198];}else {vErrors.push(err198);}errors++;}}}else {const err199 = {instancePath:instancePath+"/payload/semantic_tool",schemaPath:"#/allOf/19/then/properties/payload/properties/semantic_tool/allOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err199];}else {vErrors.push(err199);}errors++;}}}else {const err200 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/19/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err200];}else {vErrors.push(err200);}errors++;}}}var _valid22 = _errs237 === errors;valid77 = _valid22;if(valid77){var props19 = {};props19.payload = true;props19.eventType = true;}}if(!valid77){const err201 = {instancePath,schemaPath:"#/allOf/19/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err201];}else {vErrors.push(err201);}errors++;}if(props0 !== true && props19 !== undefined){if(props19 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props19);}}const _errs247 = errors;let valid83 = true;const _errs248 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data92 = data.eventType;if(!((data92 === "semantic_tool.result") || (data92 === "mcp_app.tool_result"))){const err202 = {};if(vErrors === null){vErrors = [err202];}else {vErrors.push(err202);}errors++;}}}var _valid23 = _errs248 === errors;errors = _errs247;if(vErrors !== null){if(_errs247){vErrors.length = _errs247;}else {vErrors = null;}}if(_valid23){const _errs250 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data93 = data.payload;if(data93 && typeof data93 == "object" && !Array.isArray(data93)){if(data93.semantic_tool !== undefined){let data94 = data93.semantic_tool;if(!(validate92(data94, {instancePath:instancePath+"/payload/semantic_tool",parentData:data93,parentDataProperty:"semantic_tool",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate92.errors : vErrors.concat(validate92.errors);errors = vErrors.length;}if(data94 && typeof data94 == "object" && !Array.isArray(data94)){if(data94.phase !== undefined){if("result" !== data94.phase){const err203 = {instancePath:instancePath+"/payload/semantic_tool/phase",schemaPath:"#/allOf/20/then/properties/payload/properties/semantic_tool/allOf/1/properties/phase/const",keyword:"const",params:{allowedValue: "result"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err203];}else {vErrors.push(err203);}errors++;}}}else {const err204 = {instancePath:instancePath+"/payload/semantic_tool",schemaPath:"#/allOf/20/then/properties/payload/properties/semantic_tool/allOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err204];}else {vErrors.push(err204);}errors++;}}}else {const err205 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/20/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err205];}else {vErrors.push(err205);}errors++;}}}var _valid23 = _errs250 === errors;valid83 = _valid23;if(valid83){var props20 = {};props20.payload = true;props20.eventType = true;}}if(!valid83){const err206 = {instancePath,schemaPath:"#/allOf/20/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err206];}else {vErrors.push(err206);}errors++;}if(props0 !== true && props20 !== undefined){if(props20 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props20);}}const _errs260 = errors;let valid89 = true;const _errs261 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("semantic_tool.reconciled" !== data.eventType){const err207 = {};if(vErrors === null){vErrors = [err207];}else {vErrors.push(err207);}errors++;}}}var _valid24 = _errs261 === errors;errors = _errs260;if(vErrors !== null){if(_errs260){vErrors.length = _errs260;}else {vErrors = null;}}if(_valid24){const _errs263 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data97 = data.payload;if(data97 && typeof data97 == "object" && !Array.isArray(data97)){if(data97.semantic_tool !== undefined){let data98 = data97.semantic_tool;if(!(validate92(data98, {instancePath:instancePath+"/payload/semantic_tool",parentData:data97,parentDataProperty:"semantic_tool",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate92.errors : vErrors.concat(validate92.errors);errors = vErrors.length;}if(data98 && typeof data98 == "object" && !Array.isArray(data98)){if(data98.phase !== undefined){if("reconciled" !== data98.phase){const err208 = {instancePath:instancePath+"/payload/semantic_tool/phase",schemaPath:"#/allOf/21/then/properties/payload/properties/semantic_tool/allOf/1/properties/phase/const",keyword:"const",params:{allowedValue: "reconciled"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err208];}else {vErrors.push(err208);}errors++;}}}else {const err209 = {instancePath:instancePath+"/payload/semantic_tool",schemaPath:"#/allOf/21/then/properties/payload/properties/semantic_tool/allOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err209];}else {vErrors.push(err209);}errors++;}}}else {const err210 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/21/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err210];}else {vErrors.push(err210);}errors++;}}}var _valid24 = _errs263 === errors;valid89 = _valid24;if(valid89){var props21 = {};props21.payload = true;props21.eventType = true;}}if(!valid89){const err211 = {instancePath,schemaPath:"#/allOf/21/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err211];}else {vErrors.push(err211);}errors++;}if(props0 !== true && props21 !== undefined){if(props21 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props21);}}const _errs273 = errors;let valid95 = true;const _errs274 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("run.terminal" !== data.eventType){const err212 = {};if(vErrors === null){vErrors = [err212];}else {vErrors.push(err212);}errors++;}}}var _valid25 = _errs274 === errors;errors = _errs273;if(vErrors !== null){if(_errs273){vErrors.length = _errs273;}else {vErrors = null;}}if(_valid25){const _errs276 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate104(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate104.errors : vErrors.concat(validate104.errors);errors = vErrors.length;}}}var _valid25 = _errs276 === errors;valid95 = _valid25;if(valid95){var props22 = {};props22.payload = true;props22.eventType = true;}}if(!valid95){const err213 = {instancePath,schemaPath:"#/allOf/22/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err213];}else {vErrors.push(err213);}errors++;}if(props0 !== true && props22 !== undefined){if(props22 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props22);}}const _errs279 = errors;let valid98 = true;const _errs280 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("run.result.proposed" !== data.eventType){const err214 = {};if(vErrors === null){vErrors = [err214];}else {vErrors.push(err214);}errors++;}}}var _valid26 = _errs280 === errors;errors = _errs279;if(vErrors !== null){if(_errs279){vErrors.length = _errs279;}else {vErrors = null;}}if(_valid26){const _errs282 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data103 = data.payload;const _errs287 = errors;let valid103 = true;const _errs288 = errors;if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.reportedWorkDisposition !== undefined){if("blocked" !== data103.reportedWorkDisposition){const err215 = {};if(vErrors === null){vErrors = [err215];}else {vErrors.push(err215);}errors++;}}}var _valid27 = _errs288 === errors;errors = _errs287;if(vErrors !== null){if(_errs287){vErrors.length = _errs287;}else {vErrors = null;}}if(_valid27){const _errs290 = errors;if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.blocker === undefined){const err216 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/allOf/0/then/required",keyword:"required",params:{missingProperty: "blocker"},message:"must have required property '"+"blocker"+"'"};if(vErrors === null){vErrors = [err216];}else {vErrors.push(err216);}errors++;}}var _valid27 = _errs290 === errors;valid103 = _valid27;}if(!valid103){const err217 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err217];}else {vErrors.push(err217);}errors++;}const _errs292 = errors;let valid105 = true;const _errs293 = errors;if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.reportedWorkDisposition !== undefined){if("yielded" !== data103.reportedWorkDisposition){const err218 = {};if(vErrors === null){vErrors = [err218];}else {vErrors.push(err218);}errors++;}}}var _valid28 = _errs293 === errors;errors = _errs292;if(vErrors !== null){if(_errs292){vErrors.length = _errs292;}else {vErrors = null;}}if(_valid28){const _errs295 = errors;if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.continuation === undefined){const err219 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/allOf/1/then/required",keyword:"required",params:{missingProperty: "continuation"},message:"must have required property '"+"continuation"+"'"};if(vErrors === null){vErrors = [err219];}else {vErrors.push(err219);}errors++;}}var _valid28 = _errs295 === errors;valid105 = _valid28;}if(!valid105){const err220 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err220];}else {vErrors.push(err220);}errors++;}if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.schema === undefined){const err221 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err221];}else {vErrors.push(err221);}errors++;}if(data103.reportedWorkDisposition === undefined){const err222 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "reportedWorkDisposition"},message:"must have required property '"+"reportedWorkDisposition"+"'"};if(vErrors === null){vErrors = [err222];}else {vErrors.push(err222);}errors++;}if(data103.summary === undefined){const err223 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err223];}else {vErrors.push(err223);}errors++;}if(data103.completionClaim === undefined){const err224 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "completionClaim"},message:"must have required property '"+"completionClaim"+"'"};if(vErrors === null){vErrors = [err224];}else {vErrors.push(err224);}errors++;}if(data103.evidence === undefined){const err225 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "evidence"},message:"must have required property '"+"evidence"+"'"};if(vErrors === null){vErrors = [err225];}else {vErrors.push(err225);}errors++;}if(data103.verification === undefined){const err226 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "verification"},message:"must have required property '"+"verification"+"'"};if(vErrors === null){vErrors = [err226];}else {vErrors.push(err226);}errors++;}if(data103.attentionRequests === undefined){const err227 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "attentionRequests"},message:"must have required property '"+"attentionRequests"+"'"};if(vErrors === null){vErrors = [err227];}else {vErrors.push(err227);}errors++;}if(data103.artifacts === undefined){const err228 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "artifacts"},message:"must have required property '"+"artifacts"+"'"};if(vErrors === null){vErrors = [err228];}else {vErrors.push(err228);}errors++;}if(data103.schema !== undefined){if("paperclip.run_result.v1" !== data103.schema){const err229 = {instancePath:instancePath+"/payload/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.run_result.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err229];}else {vErrors.push(err229);}errors++;}}if(data103.reportedWorkDisposition !== undefined){let data107 = data103.reportedWorkDisposition;if(!((((data107 === "done") || (data107 === "blocked")) || (data107 === "needs_review")) || (data107 === "yielded"))){const err230 = {instancePath:instancePath+"/payload/reportedWorkDisposition",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/reportedWorkDisposition/enum",keyword:"enum",params:{allowedValues: schema199.properties.reportedWorkDisposition.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err230];}else {vErrors.push(err230);}errors++;}}if(data103.summary !== undefined){let data108 = data103.summary;if(typeof data108 === "string"){if(func1(data108) > 12000){const err231 = {instancePath:instancePath+"/payload/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/maxLength",keyword:"maxLength",params:{limit: 12000},message:"must NOT have more than 12000 characters"};if(vErrors === null){vErrors = [err231];}else {vErrors.push(err231);}errors++;}if(func1(data108) < 1){const err232 = {instancePath:instancePath+"/payload/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err232];}else {vErrors.push(err232);}errors++;}}else {const err233 = {instancePath:instancePath+"/payload/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err233];}else {vErrors.push(err233);}errors++;}}if(data103.completionClaim !== undefined){let data109 = data103.completionClaim;if(data109 && typeof data109 == "object" && !Array.isArray(data109)){if(data109.contractRevision === undefined){const err234 = {instancePath:instancePath+"/payload/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "contractRevision"},message:"must have required property '"+"contractRevision"+"'"};if(vErrors === null){vErrors = [err234];}else {vErrors.push(err234);}errors++;}if(data109.objectiveSatisfied === undefined){const err235 = {instancePath:instancePath+"/payload/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "objectiveSatisfied"},message:"must have required property '"+"objectiveSatisfied"+"'"};if(vErrors === null){vErrors = [err235];}else {vErrors.push(err235);}errors++;}if(data109.criteria === undefined){const err236 = {instancePath:instancePath+"/payload/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "criteria"},message:"must have required property '"+"criteria"+"'"};if(vErrors === null){vErrors = [err236];}else {vErrors.push(err236);}errors++;}if(data109.remainingWork === undefined){const err237 = {instancePath:instancePath+"/payload/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "remainingWork"},message:"must have required property '"+"remainingWork"+"'"};if(vErrors === null){vErrors = [err237];}else {vErrors.push(err237);}errors++;}if(data109.contractRevision !== undefined){let data110 = data109.contractRevision;if(typeof data110 === "string"){if(func1(data110) < 1){const err238 = {instancePath:instancePath+"/payload/completionClaim/contractRevision",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/contractRevision/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err238];}else {vErrors.push(err238);}errors++;}}else {const err239 = {instancePath:instancePath+"/payload/completionClaim/contractRevision",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/contractRevision/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err239];}else {vErrors.push(err239);}errors++;}}if(data109.objectiveSatisfied !== undefined){if(typeof data109.objectiveSatisfied !== "boolean"){const err240 = {instancePath:instancePath+"/payload/completionClaim/objectiveSatisfied",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/objectiveSatisfied/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err240];}else {vErrors.push(err240);}errors++;}}if(data109.criteria !== undefined){let data112 = data109.criteria;if(Array.isArray(data112)){const len2 = data112.length;for(let i2=0; i2 160){const err329 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err329];}else {vErrors.push(err329);}errors++;}if(func1(data155) < 1){const err330 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err330];}else {vErrors.push(err330);}errors++;}}else {const err331 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err331];}else {vErrors.push(err331);}errors++;}}if(data.sourceSeq !== undefined){let data156 = data.sourceSeq;if(!(((typeof data156 == "number") && (!(data156 % 1) && !isNaN(data156))) && (isFinite(data156)))){const err332 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err332];}else {vErrors.push(err332);}errors++;}if((typeof data156 == "number") && (isFinite(data156))){if(data156 > 9007199254740991 || isNaN(data156)){const err333 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/maximum",keyword:"maximum",params:{comparison: "<=", limit: 9007199254740991},message:"must be <= 9007199254740991"};if(vErrors === null){vErrors = [err333];}else {vErrors.push(err333);}errors++;}if(data156 < 1 || isNaN(data156)){const err334 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err334];}else {vErrors.push(err334);}errors++;}}}if(data.sourceInstanceId !== undefined){let data157 = data.sourceInstanceId;if(typeof data157 === "string"){if(func1(data157) > 160){const err335 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err335];}else {vErrors.push(err335);}errors++;}if(func1(data157) < 1){const err336 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err336];}else {vErrors.push(err336);}errors++;}}else {const err337 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err337];}else {vErrors.push(err337);}errors++;}}if(data.sourceKind !== undefined){let data158 = data.sourceKind;if(!((data158 === "runner") || (data158 === "control_plane"))){const err338 = {instancePath:instancePath+"/sourceKind",schemaPath:"#/properties/sourceKind/enum",keyword:"enum",params:{allowedValues: schema47.properties.sourceKind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err338];}else {vErrors.push(err338);}errors++;}}if(data.runId !== undefined){let data159 = data.runId;if(typeof data159 === "string"){if(func1(data159) > 160){const err339 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err339];}else {vErrors.push(err339);}errors++;}if(func1(data159) < 1){const err340 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err340];}else {vErrors.push(err340);}errors++;}}else {const err341 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err341];}else {vErrors.push(err341);}errors++;}}if(data.normalizedSessionId !== undefined){let data160 = data.normalizedSessionId;if(typeof data160 === "string"){if(func1(data160) > 160){const err342 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err342];}else {vErrors.push(err342);}errors++;}if(func1(data160) < 1){const err343 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err343];}else {vErrors.push(err343);}errors++;}}else {const err344 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err344];}else {vErrors.push(err344);}errors++;}}if(data.turnId !== undefined){let data161 = data.turnId;if(typeof data161 === "string"){if(func1(data161) > 240){const err345 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err345];}else {vErrors.push(err345);}errors++;}if(func1(data161) < 1){const err346 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err346];}else {vErrors.push(err346);}errors++;}}else {const err347 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err347];}else {vErrors.push(err347);}errors++;}}if(data.itemId !== undefined){let data162 = data.itemId;if(typeof data162 === "string"){if(func1(data162) > 240){const err348 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err348];}else {vErrors.push(err348);}errors++;}if(func1(data162) < 1){const err349 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err349];}else {vErrors.push(err349);}errors++;}}else {const err350 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err350];}else {vErrors.push(err350);}errors++;}}if(data.eventType !== undefined){let data163 = data.eventType;if(!(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((data163 === "runner.connected") || (data163 === "runner.reconnected")) || (data163 === "runner.reconciled")) || (data163 === "runner.disconnected")) || (data163 === "runner.draining")) || (data163 === "runner.backpressure")) || (data163 === "runner.suspending")) || (data163 === "runner.suspended")) || (data163 === "runner.stopped")) || (data163 === "runner.diagnostic")) || (data163 === "runtime.phase.changed")) || (data163 === "sandbox.metric")) || (data163 === "workspace.ready")) || (data163 === "workspace.change.updated")) || (data163 === "workspace.diff.recorded")) || (data163 === "workspace.file.referenced")) || (data163 === "harness.starting")) || (data163 === "harness.ready")) || (data163 === "harness.exited")) || (data163 === "harness.diagnostic")) || (data163 === "plan.updated")) || (data163 === "tool.execution.started")) || (data163 === "tool.execution.progressed")) || (data163 === "tool.execution.completed")) || (data163 === "research.started")) || (data163 === "research.progressed")) || (data163 === "research.completed")) || (data163 === "delegation.started")) || (data163 === "delegation.updated")) || (data163 === "delegation.completed")) || (data163 === "model.route.changed")) || (data163 === "model.verification.updated")) || (data163 === "context.compacted")) || (data163 === "artifact.viewed")) || (data163 === "artifact.generated")) || (data163 === "review.mode.changed")) || (data163 === "hook.started")) || (data163 === "hook.completed")) || (data163 === "memory.citation.referenced")) || (data163 === "safety.review.started")) || (data163 === "safety.review.completed")) || (data163 === "terminal.input.sent")) || (data163 === "wait.started")) || (data163 === "wait.completed")) || (data163 === "provider.notice.recorded")) || (data163 === "session.starting")) || (data163 === "session.started")) || (data163 === "session.resuming")) || (data163 === "session.resumed")) || (data163 === "session.reconciled")) || (data163 === "session.updated")) || (data163 === "session.closed")) || (data163 === "session.failed")) || (data163 === "turn.submitted")) || (data163 === "turn.accepted")) || (data163 === "turn.started")) || (data163 === "turn.completed")) || (data163 === "turn.failed")) || (data163 === "turn.interrupted")) || (data163 === "turn.cancelled")) || (data163 === "item.started")) || (data163 === "item.delta")) || (data163 === "item.completed")) || (data163 === "item.failed")) || (data163 === "usage.reported")) || (data163 === "semantic_tool.input")) || (data163 === "semantic_tool.result")) || (data163 === "semantic_tool.reconciled")) || (data163 === "mcp_app.discovered")) || (data163 === "mcp_app.resource.resolved")) || (data163 === "mcp_app.initializing")) || (data163 === "mcp_app.ready")) || (data163 === "mcp_app.tool_input")) || (data163 === "mcp_app.tool_result")) || (data163 === "mcp_app.action.requested")) || (data163 === "mcp_app.action.resolved")) || (data163 === "mcp_app.host_context.changed")) || (data163 === "mcp_app.failed")) || (data163 === "mcp_app.teardown")) || (data163 === "runtime_request.created")) || (data163 === "runtime_request.resolved")) || (data163 === "runtime_request.expired")) || (data163 === "runtime_request.cancelled")) || (data163 === "interaction.request.proposed")) || (data163 === "interaction.request.materialized")) || (data163 === "interaction.request.rejected")) || (data163 === "interaction.response.progressed")) || (data163 === "interaction.response.resolved")) || (data163 === "interaction.response.delivered")) || (data163 === "run.attached")) || (data163 === "run.detached")) || (data163 === "run.result.proposed")) || (data163 === "run.result.accepted")) || (data163 === "run.result.rejected")) || (data163 === "attention.request.proposed")) || (data163 === "attention.request.routed")) || (data163 === "attention.request.resolved")) || (data163 === "attention.request.expired")) || (data163 === "attention.request.superseded")) || (data163 === "work.assessment.recorded")) || (data163 === "issue.status.decision.recorded")) || (data163 === "issue.status.decision.applied")) || (data163 === "issue.status.decision.rejected")) || (data163 === "issue.status.decision.superseded")) || (data163 === "run.terminal"))){const err351 = {instancePath:instancePath+"/eventType",schemaPath:"#/properties/eventType/enum",keyword:"enum",params:{allowedValues: schema47.properties.eventType.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err351];}else {vErrors.push(err351);}errors++;}}if(data.schemaVersion !== undefined){if(1 !== data.schemaVersion){const err352 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err352];}else {vErrors.push(err352);}errors++;}}if(data.priority !== undefined){let data165 = data.priority;if(!(((data165 === 0) || (data165 === 1)) || (data165 === 2))){const err353 = {instancePath:instancePath+"/priority",schemaPath:"#/properties/priority/enum",keyword:"enum",params:{allowedValues: schema47.properties.priority.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err353];}else {vErrors.push(err353);}errors++;}}if(data.emittedAt !== undefined){let data166 = data.emittedAt;if(typeof data166 === "string"){if(!(formats0.test(data166))){const err354 = {instancePath:instancePath+"/emittedAt",schemaPath:"#/properties/emittedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err354];}else {vErrors.push(err354);}errors++;}}else {const err355 = {instancePath:instancePath+"/emittedAt",schemaPath:"#/properties/emittedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err355];}else {vErrors.push(err355);}errors++;}}if(data.observedAt !== undefined){let data167 = data.observedAt;if(typeof data167 === "string"){if(!(formats0.test(data167))){const err356 = {instancePath:instancePath+"/observedAt",schemaPath:"#/properties/observedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err356];}else {vErrors.push(err356);}errors++;}}else {const err357 = {instancePath:instancePath+"/observedAt",schemaPath:"#/properties/observedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err357];}else {vErrors.push(err357);}errors++;}}if(data.payload !== undefined){let data168 = data.payload;if(data168 && typeof data168 == "object" && !Array.isArray(data168)){if(data168.channel !== undefined){let data169 = data168.channel;if(!(((((data169 === "progress") || (data169 === "final")) || (data169 === "summary")) || (data169 === "detail")) || (data169 === "unknown"))){const err358 = {instancePath:instancePath+"/payload/channel",schemaPath:"#/properties/payload/properties/channel/enum",keyword:"enum",params:{allowedValues: schema47.properties.payload.properties.channel.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err358];}else {vErrors.push(err358);}errors++;}}if(data168.providerPhase !== undefined){if(typeof data168.providerPhase !== "string"){const err359 = {instancePath:instancePath+"/payload/providerPhase",schemaPath:"#/properties/payload/properties/providerPhase/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err359];}else {vErrors.push(err359);}errors++;}}if(data168.providerMethod !== undefined){if(typeof data168.providerMethod !== "string"){const err360 = {instancePath:instancePath+"/payload/providerMethod",schemaPath:"#/properties/payload/properties/providerMethod/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err360];}else {vErrors.push(err360);}errors++;}}}else {const err361 = {instancePath:instancePath+"/payload",schemaPath:"#/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err361];}else {vErrors.push(err361);}errors++;}}if(data.debug !== undefined){let data172 = data.debug;if(data172 && typeof data172 == "object" && !Array.isArray(data172)){}else {const err362 = {instancePath:instancePath+"/debug",schemaPath:"#/properties/debug/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err362];}else {vErrors.push(err362);}errors++;}}}else {const err363 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err363];}else {vErrors.push(err363);}errors++;}validate26.errors = vErrors;return errors === 0;}validate26.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema200 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v2/event.schema.json","title":"PRP v2 native event","type":"object","required":["schema","sourceEventId","sourceSeq","sourceInstanceId","sourceKind","runId","eventType","schemaVersion","priority","emittedAt","payload"],"properties":{"schema":{"const":"paperclip.prp.event.v2"},"sourceEventId":{"type":"string","minLength":1,"maxLength":160},"sourceSeq":{"type":"integer","minimum":1,"maximum":9007199254740991},"sourceInstanceId":{"type":"string","minLength":1,"maxLength":160},"sourceKind":{"enum":["runner","control_plane"]},"runId":{"type":"string","minLength":1,"maxLength":160},"normalizedSessionId":{"type":"string","minLength":1,"maxLength":160},"turnId":{"type":"string","minLength":1,"maxLength":160},"itemId":{"type":"string","minLength":1,"maxLength":160},"eventType":{"enum":["runner.connected","runner.reconnected","runner.reconciled","runner.disconnected","runner.draining","runner.suspending","runner.suspended","runner.stopped","runner.diagnostic","runtime.phase.changed","sandbox.metric","workspace.ready","workspace.change.updated","workspace.diff.recorded","workspace.file.referenced","harness.starting","harness.ready","harness.exited","harness.diagnostic","plan.updated","tool.execution.started","tool.execution.progressed","tool.execution.completed","research.started","research.progressed","research.completed","delegation.started","delegation.updated","delegation.completed","model.route.changed","model.verification.updated","context.compacted","artifact.viewed","artifact.generated","review.mode.changed","hook.started","hook.completed","memory.citation.referenced","safety.review.started","safety.review.completed","terminal.input.sent","wait.started","wait.completed","provider.notice.recorded","session.starting","session.started","session.resuming","session.resumed","session.reconciled","session.updated","session.closed","session.failed","session.capabilities.updated","session.goal.snapshot","session.goal.updated","session.goal.cleared","turn.submitted","turn.accepted","turn.started","turn.completed","turn.failed","turn.interrupted","turn.cancelled","item.started","item.delta","item.completed","item.failed","usage.reported","semantic_tool.input","semantic_tool.result","mcp_app.discovered","mcp_app.resource.resolved","mcp_app.initializing","mcp_app.ready","mcp_app.tool_input","mcp_app.tool_result","mcp_app.action.requested","mcp_app.action.resolved","mcp_app.host_context.changed","mcp_app.failed","mcp_app.teardown","runtime_request.created","runtime_request.resolved","runtime_request.expired","runtime_request.cancelled","interaction.request.proposed","interaction.request.materialized","interaction.request.rejected","interaction.response.progressed","interaction.response.resolved","interaction.response.delivered","run.attached","run.detached","run.result.proposed","run.result.accepted","run.result.rejected","attention.request.proposed","attention.request.routed","attention.request.resolved","attention.request.expired","attention.request.superseded","work.assessment.recorded","issue.status.decision.recorded","issue.status.decision.applied","issue.status.decision.rejected","issue.status.decision.superseded","run.terminal"]},"schemaVersion":{"const":2},"priority":{"enum":[0,1,2]},"emittedAt":{"type":"string","format":"date-time"},"observedAt":{"type":"string","format":"date-time"},"payload":{"type":"object","additionalProperties":true},"debug":{"type":"object","additionalProperties":true}},"allOf":[{"if":{"properties":{"eventType":{"enum":["session.goal.snapshot","session.goal.updated"]}}},"then":{"properties":{"payload":{"type":"object","required":["goal"],"properties":{"goal":{"oneOf":[{"$ref":"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot"},{"type":"null"}]}},"additionalProperties":true}}}},{"if":{"properties":{"eventType":{"const":"session.capabilities.updated"}}},"then":{"properties":{"payload":{"type":"object","required":["sessionGoals"],"properties":{"sessionGoals":{"$ref":"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability"}},"additionalProperties":true}}}}],"additionalProperties":true};const schema201 = {"type":"object","required":["objective","status","tokenBudget","tokensUsed","elapsedSeconds","iterations","lastReason","createdAt","updatedAt","completedAt","workingNow"],"properties":{"objective":{"type":"string","minLength":1,"maxLength":4000},"status":{"enum":["active","paused","blocked","limited","usage_limited","budget_limited","complete"]},"tokenBudget":{"type":["integer","null"],"minimum":1},"tokensUsed":{"type":["integer","null"],"minimum":0},"elapsedSeconds":{"type":["number","null"],"minimum":0},"iterations":{"type":["integer","null"],"minimum":0},"lastReason":{"type":["string","null"],"maxLength":4000},"createdAt":{"type":["string","null"],"format":"date-time"},"updatedAt":{"type":["string","null"],"format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"workingNow":{"type":"boolean"}},"additionalProperties":true};function validate109(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v2/event.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate109.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data0 = data.eventType;if(!((data0 === "session.goal.snapshot") || (data0 === "session.goal.updated"))){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data1 = data.payload;if(data1 && typeof data1 == "object" && !Array.isArray(data1)){if(data1.goal === undefined){const err1 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/0/then/properties/payload/required",keyword:"required",params:{missingProperty: "goal"},message:"must have required property '"+"goal"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data1.goal !== undefined){let data2 = data1.goal;const _errs10 = errors;let valid5 = false;let passing0 = null;const _errs11 = errors;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.objective === undefined){const err2 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "objective"},message:"must have required property '"+"objective"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data2.status === undefined){const err3 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data2.tokenBudget === undefined){const err4 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "tokenBudget"},message:"must have required property '"+"tokenBudget"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data2.tokensUsed === undefined){const err5 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "tokensUsed"},message:"must have required property '"+"tokensUsed"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data2.elapsedSeconds === undefined){const err6 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "elapsedSeconds"},message:"must have required property '"+"elapsedSeconds"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data2.iterations === undefined){const err7 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "iterations"},message:"must have required property '"+"iterations"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data2.lastReason === undefined){const err8 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "lastReason"},message:"must have required property '"+"lastReason"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data2.createdAt === undefined){const err9 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "createdAt"},message:"must have required property '"+"createdAt"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data2.updatedAt === undefined){const err10 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "updatedAt"},message:"must have required property '"+"updatedAt"+"'"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(data2.completedAt === undefined){const err11 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "completedAt"},message:"must have required property '"+"completedAt"+"'"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(data2.workingNow === undefined){const err12 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "workingNow"},message:"must have required property '"+"workingNow"+"'"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(data2.objective !== undefined){let data3 = data2.objective;if(typeof data3 === "string"){if(func1(data3) > 4000){const err13 = {instancePath:instancePath+"/payload/goal/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/objective/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data3) < 1){const err14 = {instancePath:instancePath+"/payload/goal/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/objective/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/payload/goal/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/objective/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.status !== undefined){let data4 = data2.status;if(!(((((((data4 === "active") || (data4 === "paused")) || (data4 === "blocked")) || (data4 === "limited")) || (data4 === "usage_limited")) || (data4 === "budget_limited")) || (data4 === "complete"))){const err16 = {instancePath:instancePath+"/payload/goal/status",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/status/enum",keyword:"enum",params:{allowedValues: schema201.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.tokenBudget !== undefined){let data5 = data2.tokenBudget;if((!(((typeof data5 == "number") && (!(data5 % 1) && !isNaN(data5))) && (isFinite(data5)))) && (data5 !== null)){const err17 = {instancePath:instancePath+"/payload/goal/tokenBudget",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/tokenBudget/type",keyword:"type",params:{type: schema201.properties.tokenBudget.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if((typeof data5 == "number") && (isFinite(data5))){if(data5 < 1 || isNaN(data5)){const err18 = {instancePath:instancePath+"/payload/goal/tokenBudget",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/tokenBudget/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}}if(data2.tokensUsed !== undefined){let data6 = data2.tokensUsed;if((!(((typeof data6 == "number") && (!(data6 % 1) && !isNaN(data6))) && (isFinite(data6)))) && (data6 !== null)){const err19 = {instancePath:instancePath+"/payload/goal/tokensUsed",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/tokensUsed/type",keyword:"type",params:{type: schema201.properties.tokensUsed.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if((typeof data6 == "number") && (isFinite(data6))){if(data6 < 0 || isNaN(data6)){const err20 = {instancePath:instancePath+"/payload/goal/tokensUsed",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/tokensUsed/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}if(data2.elapsedSeconds !== undefined){let data7 = data2.elapsedSeconds;if((!((typeof data7 == "number") && (isFinite(data7)))) && (data7 !== null)){const err21 = {instancePath:instancePath+"/payload/goal/elapsedSeconds",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/elapsedSeconds/type",keyword:"type",params:{type: schema201.properties.elapsedSeconds.type},message:"must be number,null"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if((typeof data7 == "number") && (isFinite(data7))){if(data7 < 0 || isNaN(data7)){const err22 = {instancePath:instancePath+"/payload/goal/elapsedSeconds",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/elapsedSeconds/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}}if(data2.iterations !== undefined){let data8 = data2.iterations;if((!(((typeof data8 == "number") && (!(data8 % 1) && !isNaN(data8))) && (isFinite(data8)))) && (data8 !== null)){const err23 = {instancePath:instancePath+"/payload/goal/iterations",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/iterations/type",keyword:"type",params:{type: schema201.properties.iterations.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if((typeof data8 == "number") && (isFinite(data8))){if(data8 < 0 || isNaN(data8)){const err24 = {instancePath:instancePath+"/payload/goal/iterations",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/iterations/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}}if(data2.lastReason !== undefined){let data9 = data2.lastReason;if((typeof data9 !== "string") && (data9 !== null)){const err25 = {instancePath:instancePath+"/payload/goal/lastReason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/lastReason/type",keyword:"type",params:{type: schema201.properties.lastReason.type},message:"must be string,null"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(typeof data9 === "string"){if(func1(data9) > 4000){const err26 = {instancePath:instancePath+"/payload/goal/lastReason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/lastReason/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}}if(data2.createdAt !== undefined){let data10 = data2.createdAt;if((typeof data10 !== "string") && (data10 !== null)){const err27 = {instancePath:instancePath+"/payload/goal/createdAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/createdAt/type",keyword:"type",params:{type: schema201.properties.createdAt.type},message:"must be string,null"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(typeof data10 === "string"){if(!(formats0.test(data10))){const err28 = {instancePath:instancePath+"/payload/goal/createdAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/createdAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}if(data2.updatedAt !== undefined){let data11 = data2.updatedAt;if((typeof data11 !== "string") && (data11 !== null)){const err29 = {instancePath:instancePath+"/payload/goal/updatedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/updatedAt/type",keyword:"type",params:{type: schema201.properties.updatedAt.type},message:"must be string,null"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(typeof data11 === "string"){if(!(formats0.test(data11))){const err30 = {instancePath:instancePath+"/payload/goal/updatedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/updatedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}}if(data2.completedAt !== undefined){let data12 = data2.completedAt;if((typeof data12 !== "string") && (data12 !== null)){const err31 = {instancePath:instancePath+"/payload/goal/completedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/completedAt/type",keyword:"type",params:{type: schema201.properties.completedAt.type},message:"must be string,null"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(typeof data12 === "string"){if(!(formats0.test(data12))){const err32 = {instancePath:instancePath+"/payload/goal/completedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/completedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}}if(data2.workingNow !== undefined){if(typeof data2.workingNow !== "boolean"){const err33 = {instancePath:instancePath+"/payload/goal/workingNow",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/workingNow/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}else {const err34 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}var _valid1 = _errs11 === errors;if(_valid1){valid5 = true;passing0 = 0;}const _errs36 = errors;if(data2 !== null){const err35 = {instancePath:instancePath+"/payload/goal",schemaPath:"#/allOf/0/then/properties/payload/properties/goal/oneOf/1/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}var _valid1 = _errs36 === errors;if(_valid1 && valid5){valid5 = false;passing0 = [passing0, 1];}else {if(_valid1){valid5 = true;passing0 = 1;}}if(!valid5){const err36 = {instancePath:instancePath+"/payload/goal",schemaPath:"#/allOf/0/then/properties/payload/properties/goal/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}else {errors = _errs10;if(vErrors !== null){if(_errs10){vErrors.length = _errs10;}else {vErrors = null;}}}}}else {const err37 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/0/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}}var _valid0 = _errs5 === errors;valid1 = _valid0;if(valid1){var props1 = {};props1.payload = true;props1.eventType = true;}}if(!valid1){const err38 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}const _errs39 = errors;let valid8 = true;const _errs40 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("session.capabilities.updated" !== data.eventType){const err39 = {};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}}var _valid2 = _errs40 === errors;errors = _errs39;if(vErrors !== null){if(_errs39){vErrors.length = _errs39;}else {vErrors = null;}}if(_valid2){const _errs42 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data15 = data.payload;if(data15 && typeof data15 == "object" && !Array.isArray(data15)){if(data15.sessionGoals === undefined){const err40 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/1/then/properties/payload/required",keyword:"required",params:{missingProperty: "sessionGoals"},message:"must have required property '"+"sessionGoals"+"'"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if(data15.sessionGoals !== undefined){let data16 = data15.sessionGoals;if(data16 && typeof data16 == "object" && !Array.isArray(data16)){if(data16.availability === undefined){const err41 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "availability"},message:"must have required property '"+"availability"+"'"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}if(data16.actions === undefined){const err42 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "actions"},message:"must have required property '"+"actions"+"'"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}if(data16.autonomousUpdates === undefined){const err43 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "autonomousUpdates"},message:"must have required property '"+"autonomousUpdates"+"'"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}if(data16.persistentAcrossResume === undefined){const err44 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "persistentAcrossResume"},message:"must have required property '"+"persistentAcrossResume"+"'"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}if(data16.maxObjectiveChars === undefined){const err45 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "maxObjectiveChars"},message:"must have required property '"+"maxObjectiveChars"+"'"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(data16.tokenBudgetControl === undefined){const err46 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "tokenBudgetControl"},message:"must have required property '"+"tokenBudgetControl"+"'"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}if(data16.usageReporting === undefined){const err47 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "usageReporting"},message:"must have required property '"+"usageReporting"+"'"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}if(data16.availability !== undefined){let data17 = data16.availability;if(!(((data17 === "available") || (data17 === "unsupported")) || (data17 === "policy_disabled"))){const err48 = {instancePath:instancePath+"/payload/sessionGoals/availability",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/availability/enum",keyword:"enum",params:{allowedValues: schema44.properties.availability.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}if(data16.actions !== undefined){let data18 = data16.actions;if(Array.isArray(data18)){const len0 = data18.length;for(let i0=0; i0 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data18[i1], data18[j0])){const err50 = {instancePath:instancePath+"/payload/sessionGoals/actions",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/actions/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;break outer0;}}}}}else {const err51 = {instancePath:instancePath+"/payload/sessionGoals/actions",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/actions/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data16.autonomousUpdates !== undefined){if(typeof data16.autonomousUpdates !== "boolean"){const err52 = {instancePath:instancePath+"/payload/sessionGoals/autonomousUpdates",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/autonomousUpdates/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}if(data16.persistentAcrossResume !== undefined){if(typeof data16.persistentAcrossResume !== "boolean"){const err53 = {instancePath:instancePath+"/payload/sessionGoals/persistentAcrossResume",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/persistentAcrossResume/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data16.maxObjectiveChars !== undefined){let data22 = data16.maxObjectiveChars;if(!(((typeof data22 == "number") && (!(data22 % 1) && !isNaN(data22))) && (isFinite(data22)))){const err54 = {instancePath:instancePath+"/payload/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}if((typeof data22 == "number") && (isFinite(data22))){if(data22 > 4000 || isNaN(data22)){const err55 = {instancePath:instancePath+"/payload/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/maximum",keyword:"maximum",params:{comparison: "<=", limit: 4000},message:"must be <= 4000"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}if(data22 < 1 || isNaN(data22)){const err56 = {instancePath:instancePath+"/payload/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}}if(data16.tokenBudgetControl !== undefined){if(typeof data16.tokenBudgetControl !== "boolean"){const err57 = {instancePath:instancePath+"/payload/sessionGoals/tokenBudgetControl",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/tokenBudgetControl/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}if(data16.usageReporting !== undefined){if(typeof data16.usageReporting !== "boolean"){const err58 = {instancePath:instancePath+"/payload/sessionGoals/usageReporting",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/usageReporting/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data16.reasonCode !== undefined){let data25 = data16.reasonCode;if(typeof data25 === "string"){if(func1(data25) > 160){const err59 = {instancePath:instancePath+"/payload/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(func1(data25) < 1){const err60 = {instancePath:instancePath+"/payload/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}else {const err61 = {instancePath:instancePath+"/payload/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data16.reason !== undefined){let data26 = data16.reason;if(typeof data26 === "string"){if(func1(data26) > 1000){const err62 = {instancePath:instancePath+"/payload/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(func1(data26) < 1){const err63 = {instancePath:instancePath+"/payload/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}else {const err64 = {instancePath:instancePath+"/payload/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}}else {const err65 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}}else {const err66 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/1/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}}var _valid2 = _errs42 === errors;valid8 = _valid2;if(valid8){var props2 = {};props2.payload = true;props2.eventType = true;}}if(!valid8){const err67 = {instancePath,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}if(props1 !== true && props2 !== undefined){if(props2 === true){props1 = true;}else {props1 = props1 || {};Object.assign(props1, props2);}}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err68 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}if(data.sourceEventId === undefined){const err69 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sourceEventId"},message:"must have required property '"+"sourceEventId"+"'"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}if(data.sourceSeq === undefined){const err70 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sourceSeq"},message:"must have required property '"+"sourceSeq"+"'"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if(data.sourceInstanceId === undefined){const err71 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sourceInstanceId"},message:"must have required property '"+"sourceInstanceId"+"'"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}if(data.sourceKind === undefined){const err72 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sourceKind"},message:"must have required property '"+"sourceKind"+"'"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}if(data.runId === undefined){const err73 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "runId"},message:"must have required property '"+"runId"+"'"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(data.eventType === undefined){const err74 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "eventType"},message:"must have required property '"+"eventType"+"'"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}if(data.schemaVersion === undefined){const err75 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}if(data.priority === undefined){const err76 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "priority"},message:"must have required property '"+"priority"+"'"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}if(data.emittedAt === undefined){const err77 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "emittedAt"},message:"must have required property '"+"emittedAt"+"'"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(data.payload === undefined){const err78 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "payload"},message:"must have required property '"+"payload"+"'"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}if(data.schema !== undefined){if("paperclip.prp.event.v2" !== data.schema){const err79 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.event.v2"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}if(data.sourceEventId !== undefined){let data28 = data.sourceEventId;if(typeof data28 === "string"){if(func1(data28) > 160){const err80 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}if(func1(data28) < 1){const err81 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}else {const err82 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}if(data.sourceSeq !== undefined){let data29 = data.sourceSeq;if(!(((typeof data29 == "number") && (!(data29 % 1) && !isNaN(data29))) && (isFinite(data29)))){const err83 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}if((typeof data29 == "number") && (isFinite(data29))){if(data29 > 9007199254740991 || isNaN(data29)){const err84 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/maximum",keyword:"maximum",params:{comparison: "<=", limit: 9007199254740991},message:"must be <= 9007199254740991"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}if(data29 < 1 || isNaN(data29)){const err85 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}}if(data.sourceInstanceId !== undefined){let data30 = data.sourceInstanceId;if(typeof data30 === "string"){if(func1(data30) > 160){const err86 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}if(func1(data30) < 1){const err87 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}else {const err88 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data.sourceKind !== undefined){let data31 = data.sourceKind;if(!((data31 === "runner") || (data31 === "control_plane"))){const err89 = {instancePath:instancePath+"/sourceKind",schemaPath:"#/properties/sourceKind/enum",keyword:"enum",params:{allowedValues: schema200.properties.sourceKind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}if(data.runId !== undefined){let data32 = data.runId;if(typeof data32 === "string"){if(func1(data32) > 160){const err90 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}if(func1(data32) < 1){const err91 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}else {const err92 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}if(data.normalizedSessionId !== undefined){let data33 = data.normalizedSessionId;if(typeof data33 === "string"){if(func1(data33) > 160){const err93 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}if(func1(data33) < 1){const err94 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}else {const err95 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}if(data.turnId !== undefined){let data34 = data.turnId;if(typeof data34 === "string"){if(func1(data34) > 160){const err96 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}if(func1(data34) < 1){const err97 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}else {const err98 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}if(data.itemId !== undefined){let data35 = data.itemId;if(typeof data35 === "string"){if(func1(data35) > 160){const err99 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}if(func1(data35) < 1){const err100 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}else {const err101 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}}if(data.eventType !== undefined){let data36 = data.eventType;if(!(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((data36 === "runner.connected") || (data36 === "runner.reconnected")) || (data36 === "runner.reconciled")) || (data36 === "runner.disconnected")) || (data36 === "runner.draining")) || (data36 === "runner.suspending")) || (data36 === "runner.suspended")) || (data36 === "runner.stopped")) || (data36 === "runner.diagnostic")) || (data36 === "runtime.phase.changed")) || (data36 === "sandbox.metric")) || (data36 === "workspace.ready")) || (data36 === "workspace.change.updated")) || (data36 === "workspace.diff.recorded")) || (data36 === "workspace.file.referenced")) || (data36 === "harness.starting")) || (data36 === "harness.ready")) || (data36 === "harness.exited")) || (data36 === "harness.diagnostic")) || (data36 === "plan.updated")) || (data36 === "tool.execution.started")) || (data36 === "tool.execution.progressed")) || (data36 === "tool.execution.completed")) || (data36 === "research.started")) || (data36 === "research.progressed")) || (data36 === "research.completed")) || (data36 === "delegation.started")) || (data36 === "delegation.updated")) || (data36 === "delegation.completed")) || (data36 === "model.route.changed")) || (data36 === "model.verification.updated")) || (data36 === "context.compacted")) || (data36 === "artifact.viewed")) || (data36 === "artifact.generated")) || (data36 === "review.mode.changed")) || (data36 === "hook.started")) || (data36 === "hook.completed")) || (data36 === "memory.citation.referenced")) || (data36 === "safety.review.started")) || (data36 === "safety.review.completed")) || (data36 === "terminal.input.sent")) || (data36 === "wait.started")) || (data36 === "wait.completed")) || (data36 === "provider.notice.recorded")) || (data36 === "session.starting")) || (data36 === "session.started")) || (data36 === "session.resuming")) || (data36 === "session.resumed")) || (data36 === "session.reconciled")) || (data36 === "session.updated")) || (data36 === "session.closed")) || (data36 === "session.failed")) || (data36 === "session.capabilities.updated")) || (data36 === "session.goal.snapshot")) || (data36 === "session.goal.updated")) || (data36 === "session.goal.cleared")) || (data36 === "turn.submitted")) || (data36 === "turn.accepted")) || (data36 === "turn.started")) || (data36 === "turn.completed")) || (data36 === "turn.failed")) || (data36 === "turn.interrupted")) || (data36 === "turn.cancelled")) || (data36 === "item.started")) || (data36 === "item.delta")) || (data36 === "item.completed")) || (data36 === "item.failed")) || (data36 === "usage.reported")) || (data36 === "semantic_tool.input")) || (data36 === "semantic_tool.result")) || (data36 === "mcp_app.discovered")) || (data36 === "mcp_app.resource.resolved")) || (data36 === "mcp_app.initializing")) || (data36 === "mcp_app.ready")) || (data36 === "mcp_app.tool_input")) || (data36 === "mcp_app.tool_result")) || (data36 === "mcp_app.action.requested")) || (data36 === "mcp_app.action.resolved")) || (data36 === "mcp_app.host_context.changed")) || (data36 === "mcp_app.failed")) || (data36 === "mcp_app.teardown")) || (data36 === "runtime_request.created")) || (data36 === "runtime_request.resolved")) || (data36 === "runtime_request.expired")) || (data36 === "runtime_request.cancelled")) || (data36 === "interaction.request.proposed")) || (data36 === "interaction.request.materialized")) || (data36 === "interaction.request.rejected")) || (data36 === "interaction.response.progressed")) || (data36 === "interaction.response.resolved")) || (data36 === "interaction.response.delivered")) || (data36 === "run.attached")) || (data36 === "run.detached")) || (data36 === "run.result.proposed")) || (data36 === "run.result.accepted")) || (data36 === "run.result.rejected")) || (data36 === "attention.request.proposed")) || (data36 === "attention.request.routed")) || (data36 === "attention.request.resolved")) || (data36 === "attention.request.expired")) || (data36 === "attention.request.superseded")) || (data36 === "work.assessment.recorded")) || (data36 === "issue.status.decision.recorded")) || (data36 === "issue.status.decision.applied")) || (data36 === "issue.status.decision.rejected")) || (data36 === "issue.status.decision.superseded")) || (data36 === "run.terminal"))){const err102 = {instancePath:instancePath+"/eventType",schemaPath:"#/properties/eventType/enum",keyword:"enum",params:{allowedValues: schema200.properties.eventType.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}if(data.schemaVersion !== undefined){if(2 !== data.schemaVersion){const err103 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 2},message:"must be equal to constant"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}if(data.priority !== undefined){let data38 = data.priority;if(!(((data38 === 0) || (data38 === 1)) || (data38 === 2))){const err104 = {instancePath:instancePath+"/priority",schemaPath:"#/properties/priority/enum",keyword:"enum",params:{allowedValues: schema200.properties.priority.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}if(data.emittedAt !== undefined){let data39 = data.emittedAt;if(typeof data39 === "string"){if(!(formats0.test(data39))){const err105 = {instancePath:instancePath+"/emittedAt",schemaPath:"#/properties/emittedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}else {const err106 = {instancePath:instancePath+"/emittedAt",schemaPath:"#/properties/emittedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}if(data.observedAt !== undefined){let data40 = data.observedAt;if(typeof data40 === "string"){if(!(formats0.test(data40))){const err107 = {instancePath:instancePath+"/observedAt",schemaPath:"#/properties/observedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/observedAt",schemaPath:"#/properties/observedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}if(data.payload !== undefined){let data41 = data.payload;if(data41 && typeof data41 == "object" && !Array.isArray(data41)){}else {const err109 = {instancePath:instancePath+"/payload",schemaPath:"#/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}if(data.debug !== undefined){let data42 = data.debug;if(data42 && typeof data42 == "object" && !Array.isArray(data42)){}else {const err110 = {instancePath:instancePath+"/debug",schemaPath:"#/properties/debug/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}}else {const err111 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}validate109.errors = vErrors;return errors === 0;}validate109.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema203 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/request.schema.json","title":"PRP request","oneOf":[{"type":"object","required":["schema","requestKind","requestId","type","status","prompt","input"],"properties":{"schema":{"const":"paperclip.runtime_request.v2"},"requestKind":{"const":"runtime"},"requestId":{"type":"string","minLength":1,"maxLength":160},"type":{"const":"input"},"status":{"enum":["pending","resolved","expired","cancelled"]},"prompt":{"type":"string","minLength":1,"maxLength":4000},"input":{"$ref":"https://paperclip.dev/schemas/prp/v1/question-set.schema.json"},"origin":{"type":"object","required":["adapter"],"properties":{"adapter":{"type":"string","minLength":1,"maxLength":160},"provider":{"type":"string","minLength":1,"maxLength":160},"method":{"type":"string","minLength":1,"maxLength":500}},"additionalProperties":false},"turnId":{"type":"string","minLength":1,"maxLength":240},"itemId":{"type":"string","minLength":1,"maxLength":240}},"additionalProperties":false},{"type":"object","required":["schema","requestKind","requestId","type","status","prompt"],"properties":{"schema":{"const":"paperclip.runtime_request.v1"},"requestKind":{"const":"runtime"},"requestId":{"type":"string","minLength":1,"maxLength":160},"type":{"enum":["permission","input"]},"status":{"enum":["pending","resolved","expired","cancelled"]},"prompt":{"type":"string","minLength":1,"maxLength":4000},"choices":{"type":"array","items":{"type":"object","required":["key","label"],"properties":{"key":{"type":"string","minLength":1},"label":{"type":"string","minLength":1}},"additionalProperties":true}}},"additionalProperties":true},{"type":"object","required":["schema","requestKind","requestId","kind","blocking","payload"],"properties":{"schema":{"const":"paperclip.interaction_request.v1"},"requestKind":{"const":"issue_thread"},"requestId":{"type":"string","minLength":1,"maxLength":160},"kind":{"enum":["request_confirmation","request_checkbox_confirmation","request_item_verdicts","ask_user_questions","suggest_tasks"]},"blocking":{"type":"boolean"},"payload":{"type":"object","additionalProperties":true}},"additionalProperties":true}]};const schema204 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/question-set.schema.json","title":"Paperclip question set","type":"object","required":["schema","questions"],"properties":{"schema":{"const":"paperclip.question_set.v1"},"title":{"type":"string","maxLength":1000},"description":{"type":"string","maxLength":4000},"submitLabel":{"type":"string","maxLength":200},"questions":{"type":"array","minItems":1,"maxItems":64,"items":{"$ref":"#/$defs/question"}}},"$defs":{"option":{"type":"object","required":["id","label"],"properties":{"id":{"type":"string","minLength":1,"maxLength":160},"label":{"type":"string","minLength":1,"maxLength":1000},"description":{"type":"string","maxLength":4000},"recommended":{"type":"boolean"}},"additionalProperties":false},"customAnswer":{"type":"object","required":["enabled"],"properties":{"enabled":{"const":true},"label":{"type":"string","maxLength":1000},"placeholder":{"type":"string","maxLength":1000}},"additionalProperties":false},"textValidation":{"type":"object","properties":{"minLength":{"type":"integer","minimum":0,"maximum":100000},"maxLength":{"type":"integer","minimum":0,"maximum":100000},"pattern":{"type":"string","maxLength":1000},"inputType":{"enum":["text","number","integer"]},"minimum":{"type":"number"},"maximum":{"type":"number"}},"additionalProperties":false},"question":{"type":"object","required":["id","prompt","required","answerMode"],"properties":{"id":{"type":"string","minLength":1,"maxLength":160},"header":{"type":"string","maxLength":1000},"prompt":{"type":"string","minLength":1,"maxLength":4000},"helpText":{"type":"string","maxLength":4000},"required":{"type":"boolean"},"answerMode":{"enum":["single_select","multi_select","text"]},"options":{"type":"array","maxItems":128,"items":{"$ref":"#/$defs/option"}},"customAnswer":{"$ref":"#/$defs/customAnswer"},"textValidation":{"$ref":"#/$defs/textValidation"}},"allOf":[{"if":{"properties":{"answerMode":{"const":"text"}},"required":["answerMode"]},"then":{"properties":{"options":{"type":"array","maxItems":0}},"not":{"required":["customAnswer"]}}},{"if":{"properties":{"answerMode":{"enum":["single_select","multi_select"]}},"required":["answerMode"]},"then":{"required":["options"],"properties":{"options":{"type":"array","minItems":1}}}}],"additionalProperties":false}},"additionalProperties":false};const schema205 = {"type":"object","required":["id","prompt","required","answerMode"],"properties":{"id":{"type":"string","minLength":1,"maxLength":160},"header":{"type":"string","maxLength":1000},"prompt":{"type":"string","minLength":1,"maxLength":4000},"helpText":{"type":"string","maxLength":4000},"required":{"type":"boolean"},"answerMode":{"enum":["single_select","multi_select","text"]},"options":{"type":"array","maxItems":128,"items":{"$ref":"#/$defs/option"}},"customAnswer":{"$ref":"#/$defs/customAnswer"},"textValidation":{"$ref":"#/$defs/textValidation"}},"allOf":[{"if":{"properties":{"answerMode":{"const":"text"}},"required":["answerMode"]},"then":{"properties":{"options":{"type":"array","maxItems":0}},"not":{"required":["customAnswer"]}}},{"if":{"properties":{"answerMode":{"enum":["single_select","multi_select"]}},"required":["answerMode"]},"then":{"required":["options"],"properties":{"options":{"type":"array","minItems":1}}}}],"additionalProperties":false};const schema206 = {"type":"object","required":["id","label"],"properties":{"id":{"type":"string","minLength":1,"maxLength":160},"label":{"type":"string","minLength":1,"maxLength":1000},"description":{"type":"string","maxLength":4000},"recommended":{"type":"boolean"}},"additionalProperties":false};const schema207 = {"type":"object","required":["enabled"],"properties":{"enabled":{"const":true},"label":{"type":"string","maxLength":1000},"placeholder":{"type":"string","maxLength":1000}},"additionalProperties":false};const schema208 = {"type":"object","properties":{"minLength":{"type":"integer","minimum":0,"maximum":100000},"maxLength":{"type":"integer","minimum":0,"maximum":100000},"pattern":{"type":"string","maxLength":1000},"inputType":{"enum":["text","number","integer"]},"minimum":{"type":"number"},"maximum":{"type":"number"}},"additionalProperties":false};function validate113(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate113.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){let missing0;if((data.answerMode === undefined) && (missing0 = "answerMode")){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}else {if(data.answerMode !== undefined){if("text" !== data.answerMode){const err1 = {};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;const _errs6 = errors;const _errs7 = errors;if(data && typeof data == "object" && !Array.isArray(data)){let missing1;if((data.customAnswer === undefined) && (missing1 = "customAnswer")){const err2 = {};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}var valid3 = _errs7 === errors;if(valid3){const err3 = {instancePath,schemaPath:"#/allOf/0/then/not",keyword:"not",params:{},message:"must NOT be valid"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}else {errors = _errs6;if(vErrors !== null){if(_errs6){vErrors.length = _errs6;}else {vErrors = null;}}}if(data && typeof data == "object" && !Array.isArray(data)){if(data.options !== undefined){let data1 = data.options;if(Array.isArray(data1)){if(data1.length > 0){const err4 = {instancePath:instancePath+"/options",schemaPath:"#/allOf/0/then/properties/options/maxItems",keyword:"maxItems",params:{limit: 0},message:"must NOT have more than 0 items"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}else {const err5 = {instancePath:instancePath+"/options",schemaPath:"#/allOf/0/then/properties/options/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}}var _valid0 = _errs5 === errors;valid1 = _valid0;if(valid1){var props0 = {};props0.options = true;props0.answerMode = true;}}if(!valid1){const err6 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}const _errs11 = errors;let valid5 = true;const _errs12 = errors;if(data && typeof data == "object" && !Array.isArray(data)){let missing2;if((data.answerMode === undefined) && (missing2 = "answerMode")){const err7 = {};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}else {if(data.answerMode !== undefined){let data2 = data.answerMode;if(!((data2 === "single_select") || (data2 === "multi_select"))){const err8 = {};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}}}var _valid1 = _errs12 === errors;errors = _errs11;if(vErrors !== null){if(_errs11){vErrors.length = _errs11;}else {vErrors = null;}}if(_valid1){const _errs14 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.options === undefined){const err9 = {instancePath,schemaPath:"#/allOf/1/then/required",keyword:"required",params:{missingProperty: "options"},message:"must have required property '"+"options"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.options !== undefined){let data3 = data.options;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/options",schemaPath:"#/allOf/1/then/properties/options/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/options",schemaPath:"#/allOf/1/then/properties/options/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}}var _valid1 = _errs14 === errors;valid5 = _valid1;if(valid5){var props1 = {};props1.options = true;props1.answerMode = true;}}if(!valid5){const err12 = {instancePath,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(props0 !== true && props1 !== undefined){if(props1 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props1);}}if(data && typeof data == "object" && !Array.isArray(data)){if(data.id === undefined){const err13 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(data.prompt === undefined){const err14 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "prompt"},message:"must have required property '"+"prompt"+"'"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(data.required === undefined){const err15 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "required"},message:"must have required property '"+"required"+"'"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(data.answerMode === undefined){const err16 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "answerMode"},message:"must have required property '"+"answerMode"+"'"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}for(const key0 in data){if(!(func58.call(schema205.properties, key0))){const err17 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.id !== undefined){let data4 = data.id;if(typeof data4 === "string"){if(func1(data4) > 160){const err18 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(func1(data4) < 1){const err19 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}else {const err20 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.header !== undefined){let data5 = data.header;if(typeof data5 === "string"){if(func1(data5) > 1000){const err21 = {instancePath:instancePath+"/header",schemaPath:"#/properties/header/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/header",schemaPath:"#/properties/header/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data.prompt !== undefined){let data6 = data.prompt;if(typeof data6 === "string"){if(func1(data6) > 4000){const err23 = {instancePath:instancePath+"/prompt",schemaPath:"#/properties/prompt/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(func1(data6) < 1){const err24 = {instancePath:instancePath+"/prompt",schemaPath:"#/properties/prompt/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}else {const err25 = {instancePath:instancePath+"/prompt",schemaPath:"#/properties/prompt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data.helpText !== undefined){let data7 = data.helpText;if(typeof data7 === "string"){if(func1(data7) > 4000){const err26 = {instancePath:instancePath+"/helpText",schemaPath:"#/properties/helpText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}else {const err27 = {instancePath:instancePath+"/helpText",schemaPath:"#/properties/helpText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data.required !== undefined){if(typeof data.required !== "boolean"){const err28 = {instancePath:instancePath+"/required",schemaPath:"#/properties/required/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data.answerMode !== undefined){let data9 = data.answerMode;if(!(((data9 === "single_select") || (data9 === "multi_select")) || (data9 === "text"))){const err29 = {instancePath:instancePath+"/answerMode",schemaPath:"#/properties/answerMode/enum",keyword:"enum",params:{allowedValues: schema205.properties.answerMode.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data.options !== undefined){let data10 = data.options;if(Array.isArray(data10)){if(data10.length > 128){const err30 = {instancePath:instancePath+"/options",schemaPath:"#/properties/options/maxItems",keyword:"maxItems",params:{limit: 128},message:"must NOT have more than 128 items"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}const len0 = data10.length;for(let i0=0; i0 160){const err34 = {instancePath:instancePath+"/options/" + i0+"/id",schemaPath:"#/$defs/option/properties/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(func1(data12) < 1){const err35 = {instancePath:instancePath+"/options/" + i0+"/id",schemaPath:"#/$defs/option/properties/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}else {const err36 = {instancePath:instancePath+"/options/" + i0+"/id",schemaPath:"#/$defs/option/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data11.label !== undefined){let data13 = data11.label;if(typeof data13 === "string"){if(func1(data13) > 1000){const err37 = {instancePath:instancePath+"/options/" + i0+"/label",schemaPath:"#/$defs/option/properties/label/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(func1(data13) < 1){const err38 = {instancePath:instancePath+"/options/" + i0+"/label",schemaPath:"#/$defs/option/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}else {const err39 = {instancePath:instancePath+"/options/" + i0+"/label",schemaPath:"#/$defs/option/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data11.description !== undefined){let data14 = data11.description;if(typeof data14 === "string"){if(func1(data14) > 4000){const err40 = {instancePath:instancePath+"/options/" + i0+"/description",schemaPath:"#/$defs/option/properties/description/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}else {const err41 = {instancePath:instancePath+"/options/" + i0+"/description",schemaPath:"#/$defs/option/properties/description/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}if(data11.recommended !== undefined){if(typeof data11.recommended !== "boolean"){const err42 = {instancePath:instancePath+"/options/" + i0+"/recommended",schemaPath:"#/$defs/option/properties/recommended/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}}else {const err43 = {instancePath:instancePath+"/options/" + i0,schemaPath:"#/$defs/option/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}}else {const err44 = {instancePath:instancePath+"/options",schemaPath:"#/properties/options/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data.customAnswer !== undefined){let data16 = data.customAnswer;if(data16 && typeof data16 == "object" && !Array.isArray(data16)){if(data16.enabled === undefined){const err45 = {instancePath:instancePath+"/customAnswer",schemaPath:"#/$defs/customAnswer/required",keyword:"required",params:{missingProperty: "enabled"},message:"must have required property '"+"enabled"+"'"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}for(const key2 in data16){if(!(((key2 === "enabled") || (key2 === "label")) || (key2 === "placeholder"))){const err46 = {instancePath:instancePath+"/customAnswer",schemaPath:"#/$defs/customAnswer/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data16.enabled !== undefined){if(true !== data16.enabled){const err47 = {instancePath:instancePath+"/customAnswer/enabled",schemaPath:"#/$defs/customAnswer/properties/enabled/const",keyword:"const",params:{allowedValue: true},message:"must be equal to constant"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data16.label !== undefined){let data18 = data16.label;if(typeof data18 === "string"){if(func1(data18) > 1000){const err48 = {instancePath:instancePath+"/customAnswer/label",schemaPath:"#/$defs/customAnswer/properties/label/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}else {const err49 = {instancePath:instancePath+"/customAnswer/label",schemaPath:"#/$defs/customAnswer/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data16.placeholder !== undefined){let data19 = data16.placeholder;if(typeof data19 === "string"){if(func1(data19) > 1000){const err50 = {instancePath:instancePath+"/customAnswer/placeholder",schemaPath:"#/$defs/customAnswer/properties/placeholder/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}else {const err51 = {instancePath:instancePath+"/customAnswer/placeholder",schemaPath:"#/$defs/customAnswer/properties/placeholder/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}}else {const err52 = {instancePath:instancePath+"/customAnswer",schemaPath:"#/$defs/customAnswer/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}if(data.textValidation !== undefined){let data20 = data.textValidation;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){for(const key3 in data20){if(!((((((key3 === "minLength") || (key3 === "maxLength")) || (key3 === "pattern")) || (key3 === "inputType")) || (key3 === "minimum")) || (key3 === "maximum"))){const err53 = {instancePath:instancePath+"/textValidation",schemaPath:"#/$defs/textValidation/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data20.minLength !== undefined){let data21 = data20.minLength;if(!(((typeof data21 == "number") && (!(data21 % 1) && !isNaN(data21))) && (isFinite(data21)))){const err54 = {instancePath:instancePath+"/textValidation/minLength",schemaPath:"#/$defs/textValidation/properties/minLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}if((typeof data21 == "number") && (isFinite(data21))){if(data21 > 100000 || isNaN(data21)){const err55 = {instancePath:instancePath+"/textValidation/minLength",schemaPath:"#/$defs/textValidation/properties/minLength/maximum",keyword:"maximum",params:{comparison: "<=", limit: 100000},message:"must be <= 100000"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}if(data21 < 0 || isNaN(data21)){const err56 = {instancePath:instancePath+"/textValidation/minLength",schemaPath:"#/$defs/textValidation/properties/minLength/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}}if(data20.maxLength !== undefined){let data22 = data20.maxLength;if(!(((typeof data22 == "number") && (!(data22 % 1) && !isNaN(data22))) && (isFinite(data22)))){const err57 = {instancePath:instancePath+"/textValidation/maxLength",schemaPath:"#/$defs/textValidation/properties/maxLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}if((typeof data22 == "number") && (isFinite(data22))){if(data22 > 100000 || isNaN(data22)){const err58 = {instancePath:instancePath+"/textValidation/maxLength",schemaPath:"#/$defs/textValidation/properties/maxLength/maximum",keyword:"maximum",params:{comparison: "<=", limit: 100000},message:"must be <= 100000"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}if(data22 < 0 || isNaN(data22)){const err59 = {instancePath:instancePath+"/textValidation/maxLength",schemaPath:"#/$defs/textValidation/properties/maxLength/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}}if(data20.pattern !== undefined){let data23 = data20.pattern;if(typeof data23 === "string"){if(func1(data23) > 1000){const err60 = {instancePath:instancePath+"/textValidation/pattern",schemaPath:"#/$defs/textValidation/properties/pattern/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}else {const err61 = {instancePath:instancePath+"/textValidation/pattern",schemaPath:"#/$defs/textValidation/properties/pattern/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data20.inputType !== undefined){let data24 = data20.inputType;if(!(((data24 === "text") || (data24 === "number")) || (data24 === "integer"))){const err62 = {instancePath:instancePath+"/textValidation/inputType",schemaPath:"#/$defs/textValidation/properties/inputType/enum",keyword:"enum",params:{allowedValues: schema208.properties.inputType.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(data20.minimum !== undefined){let data25 = data20.minimum;if(!((typeof data25 == "number") && (isFinite(data25)))){const err63 = {instancePath:instancePath+"/textValidation/minimum",schemaPath:"#/$defs/textValidation/properties/minimum/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data20.maximum !== undefined){let data26 = data20.maximum;if(!((typeof data26 == "number") && (isFinite(data26)))){const err64 = {instancePath:instancePath+"/textValidation/maximum",schemaPath:"#/$defs/textValidation/properties/maximum/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}}else {const err65 = {instancePath:instancePath+"/textValidation",schemaPath:"#/$defs/textValidation/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}}else {const err66 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}validate113.errors = vErrors;return errors === 0;}validate113.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate112(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/question-set.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate112.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.questions === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "questions"},message:"must have required property '"+"questions"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}for(const key0 in data){if(!(((((key0 === "schema") || (key0 === "title")) || (key0 === "description")) || (key0 === "submitLabel")) || (key0 === "questions"))){const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.schema !== undefined){if("paperclip.question_set.v1" !== data.schema){const err3 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.question_set.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.title !== undefined){let data1 = data.title;if(typeof data1 === "string"){if(func1(data1) > 1000){const err4 = {instancePath:instancePath+"/title",schemaPath:"#/properties/title/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}else {const err5 = {instancePath:instancePath+"/title",schemaPath:"#/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.description !== undefined){let data2 = data.description;if(typeof data2 === "string"){if(func1(data2) > 4000){const err6 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}else {const err7 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.submitLabel !== undefined){let data3 = data.submitLabel;if(typeof data3 === "string"){if(func1(data3) > 200){const err8 = {instancePath:instancePath+"/submitLabel",schemaPath:"#/properties/submitLabel/maxLength",keyword:"maxLength",params:{limit: 200},message:"must NOT have more than 200 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/submitLabel",schemaPath:"#/properties/submitLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.questions !== undefined){let data4 = data.questions;if(Array.isArray(data4)){if(data4.length > 64){const err10 = {instancePath:instancePath+"/questions",schemaPath:"#/properties/questions/maxItems",keyword:"maxItems",params:{limit: 64},message:"must NOT have more than 64 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(data4.length < 1){const err11 = {instancePath:instancePath+"/questions",schemaPath:"#/properties/questions/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}const len0 = data4.length;for(let i0=0; i0 160){const err10 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/0/properties/requestId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(func1(data2) < 1){const err11 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/0/properties/requestId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/0/properties/requestId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.type !== undefined){if("input" !== data.type){const err13 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/0/properties/type/const",keyword:"const",params:{allowedValue: "input"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.status !== undefined){let data4 = data.status;if(!((((data4 === "pending") || (data4 === "resolved")) || (data4 === "expired")) || (data4 === "cancelled"))){const err14 = {instancePath:instancePath+"/status",schemaPath:"#/oneOf/0/properties/status/enum",keyword:"enum",params:{allowedValues: schema203.oneOf[0].properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.prompt !== undefined){let data5 = data.prompt;if(typeof data5 === "string"){if(func1(data5) > 4000){const err15 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/0/properties/prompt/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(func1(data5) < 1){const err16 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/0/properties/prompt/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}else {const err17 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/0/properties/prompt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.input !== undefined){if(!(validate112(data.input, {instancePath:instancePath+"/input",parentData:data,parentDataProperty:"input",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate112.errors : vErrors.concat(validate112.errors);errors = vErrors.length;}}if(data.origin !== undefined){let data7 = data.origin;if(data7 && typeof data7 == "object" && !Array.isArray(data7)){if(data7.adapter === undefined){const err18 = {instancePath:instancePath+"/origin",schemaPath:"#/oneOf/0/properties/origin/required",keyword:"required",params:{missingProperty: "adapter"},message:"must have required property '"+"adapter"+"'"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}for(const key1 in data7){if(!(((key1 === "adapter") || (key1 === "provider")) || (key1 === "method"))){const err19 = {instancePath:instancePath+"/origin",schemaPath:"#/oneOf/0/properties/origin/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data7.adapter !== undefined){let data8 = data7.adapter;if(typeof data8 === "string"){if(func1(data8) > 160){const err20 = {instancePath:instancePath+"/origin/adapter",schemaPath:"#/oneOf/0/properties/origin/properties/adapter/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(func1(data8) < 1){const err21 = {instancePath:instancePath+"/origin/adapter",schemaPath:"#/oneOf/0/properties/origin/properties/adapter/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/origin/adapter",schemaPath:"#/oneOf/0/properties/origin/properties/adapter/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data7.provider !== undefined){let data9 = data7.provider;if(typeof data9 === "string"){if(func1(data9) > 160){const err23 = {instancePath:instancePath+"/origin/provider",schemaPath:"#/oneOf/0/properties/origin/properties/provider/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(func1(data9) < 1){const err24 = {instancePath:instancePath+"/origin/provider",schemaPath:"#/oneOf/0/properties/origin/properties/provider/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}else {const err25 = {instancePath:instancePath+"/origin/provider",schemaPath:"#/oneOf/0/properties/origin/properties/provider/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data7.method !== undefined){let data10 = data7.method;if(typeof data10 === "string"){if(func1(data10) > 500){const err26 = {instancePath:instancePath+"/origin/method",schemaPath:"#/oneOf/0/properties/origin/properties/method/maxLength",keyword:"maxLength",params:{limit: 500},message:"must NOT have more than 500 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(func1(data10) < 1){const err27 = {instancePath:instancePath+"/origin/method",schemaPath:"#/oneOf/0/properties/origin/properties/method/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}else {const err28 = {instancePath:instancePath+"/origin/method",schemaPath:"#/oneOf/0/properties/origin/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}else {const err29 = {instancePath:instancePath+"/origin",schemaPath:"#/oneOf/0/properties/origin/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data.turnId !== undefined){let data11 = data.turnId;if(typeof data11 === "string"){if(func1(data11) > 240){const err30 = {instancePath:instancePath+"/turnId",schemaPath:"#/oneOf/0/properties/turnId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(func1(data11) < 1){const err31 = {instancePath:instancePath+"/turnId",schemaPath:"#/oneOf/0/properties/turnId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}else {const err32 = {instancePath:instancePath+"/turnId",schemaPath:"#/oneOf/0/properties/turnId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}if(data.itemId !== undefined){let data12 = data.itemId;if(typeof data12 === "string"){if(func1(data12) > 240){const err33 = {instancePath:instancePath+"/itemId",schemaPath:"#/oneOf/0/properties/itemId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(func1(data12) < 1){const err34 = {instancePath:instancePath+"/itemId",schemaPath:"#/oneOf/0/properties/itemId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}else {const err35 = {instancePath:instancePath+"/itemId",schemaPath:"#/oneOf/0/properties/itemId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}}else {const err36 = {instancePath,schemaPath:"#/oneOf/0/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}var _valid0 = _errs1 === errors;if(_valid0){valid0 = true;passing0 = 0;var props0 = true;}const _errs26 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err37 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(data.requestKind === undefined){const err38 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "requestKind"},message:"must have required property '"+"requestKind"+"'"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(data.requestId === undefined){const err39 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "requestId"},message:"must have required property '"+"requestId"+"'"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}if(data.type === undefined){const err40 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if(data.status === undefined){const err41 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}if(data.prompt === undefined){const err42 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "prompt"},message:"must have required property '"+"prompt"+"'"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}if(data.schema !== undefined){if("paperclip.runtime_request.v1" !== data.schema){const err43 = {instancePath:instancePath+"/schema",schemaPath:"#/oneOf/1/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.runtime_request.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data.requestKind !== undefined){if("runtime" !== data.requestKind){const err44 = {instancePath:instancePath+"/requestKind",schemaPath:"#/oneOf/1/properties/requestKind/const",keyword:"const",params:{allowedValue: "runtime"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data.requestId !== undefined){let data15 = data.requestId;if(typeof data15 === "string"){if(func1(data15) > 160){const err45 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/1/properties/requestId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func1(data15) < 1){const err46 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/1/properties/requestId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/1/properties/requestId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data.type !== undefined){let data16 = data.type;if(!((data16 === "permission") || (data16 === "input"))){const err48 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/1/properties/type/enum",keyword:"enum",params:{allowedValues: schema203.oneOf[1].properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}if(data.status !== undefined){let data17 = data.status;if(!((((data17 === "pending") || (data17 === "resolved")) || (data17 === "expired")) || (data17 === "cancelled"))){const err49 = {instancePath:instancePath+"/status",schemaPath:"#/oneOf/1/properties/status/enum",keyword:"enum",params:{allowedValues: schema203.oneOf[1].properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data.prompt !== undefined){let data18 = data.prompt;if(typeof data18 === "string"){if(func1(data18) > 4000){const err50 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/1/properties/prompt/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}if(func1(data18) < 1){const err51 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/1/properties/prompt/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}else {const err52 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/1/properties/prompt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}if(data.choices !== undefined){let data19 = data.choices;if(Array.isArray(data19)){const len0 = data19.length;for(let i0=0; i0 160){const err70 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/2/properties/requestId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if(func1(data25) < 1){const err71 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/2/properties/requestId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}else {const err72 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/2/properties/requestId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data.kind !== undefined){let data26 = data.kind;if(!(((((data26 === "request_confirmation") || (data26 === "request_checkbox_confirmation")) || (data26 === "request_item_verdicts")) || (data26 === "ask_user_questions")) || (data26 === "suggest_tasks"))){const err73 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/2/properties/kind/enum",keyword:"enum",params:{allowedValues: schema203.oneOf[2].properties.kind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}if(data.blocking !== undefined){if(typeof data.blocking !== "boolean"){const err74 = {instancePath:instancePath+"/blocking",schemaPath:"#/oneOf/2/properties/blocking/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}if(data.payload !== undefined){let data28 = data.payload;if(data28 && typeof data28 == "object" && !Array.isArray(data28)){}else {const err75 = {instancePath:instancePath+"/payload",schemaPath:"#/oneOf/2/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}}else {const err76 = {instancePath,schemaPath:"#/oneOf/2/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}var _valid0 = _errs46 === errors;if(_valid0 && valid0){valid0 = false;passing0 = [passing0, 2];}else {if(_valid0){valid0 = true;passing0 = 2;if(props0 !== true){props0 = true;}}}}if(!valid0){const err77 = {instancePath,schemaPath:"#/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}else {errors = _errs0;if(vErrors !== null){if(_errs0){vErrors.length = _errs0;}else {vErrors = null;}}}validate111.errors = vErrors;evaluated0.props = props0;return errors === 0;}validate111.evaluated = {"dynamicProps":true,"dynamicItems":false};const pattern12 = new RegExp("^[a-z][a-z0-9_.-]*$", "u");function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/fixture.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate20.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.fixtureVersion === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "fixtureVersion"},message:"must have required property '"+"fixtureVersion"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.protocolVersion === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "protocolVersion"},message:"must have required property '"+"protocolVersion"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.name === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "name"},message:"must have required property '"+"name"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.description === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "description"},message:"must have required property '"+"description"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.identity === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "identity"},message:"must have required property '"+"identity"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.capabilities === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "capabilities"},message:"must have required property '"+"capabilities"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.commands === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "commands"},message:"must have required property '"+"commands"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.events === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "events"},message:"must have required property '"+"events"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data.result === undefined){const err9 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "result"},message:"must have required property '"+"result"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.schema !== undefined){if("paperclip.prp.fixture.v1" !== data.schema){const err10 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.fixture.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.fixtureVersion !== undefined){if(1 !== data.fixtureVersion){const err11 = {instancePath:instancePath+"/fixtureVersion",schemaPath:"#/properties/fixtureVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.protocolVersion !== undefined){let data2 = data.protocolVersion;if(!((data2 === 1) || (data2 === 2))){const err12 = {instancePath:instancePath+"/protocolVersion",schemaPath:"#/properties/protocolVersion/enum",keyword:"enum",params:{allowedValues: schema31.properties.protocolVersion.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.name !== undefined){let data3 = data.name;if(typeof data3 === "string"){if(func1(data3) > 120){const err13 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/maxLength",keyword:"maxLength",params:{limit: 120},message:"must NOT have more than 120 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data3) < 1){const err14 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.description !== undefined){let data4 = data.description;if(typeof data4 === "string"){if(func1(data4) > 1000){const err16 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(func1(data4) < 1){const err17 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}else {const err18 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.identity !== undefined){if(!(validate21(data.identity, {instancePath:instancePath+"/identity",parentData:data,parentDataProperty:"identity",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate21.errors : vErrors.concat(validate21.errors);errors = vErrors.length;}}if(data.capabilities !== undefined){let data6 = data.capabilities;const _errs11 = errors;let valid1 = false;let passing0 = null;const _errs12 = errors;if(data6 && typeof data6 == "object" && !Array.isArray(data6)){if(data6.schema === undefined){const err19 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(data6.sessionReusePolicy === undefined){const err20 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "sessionReusePolicy"},message:"must have required property '"+"sessionReusePolicy"+"'"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(data6.driver === undefined){const err21 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "driver"},message:"must have required property '"+"driver"+"'"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(data6.steer === undefined){const err22 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "steer"},message:"must have required property '"+"steer"+"'"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(data6.interrupt === undefined){const err23 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "interrupt"},message:"must have required property '"+"interrupt"+"'"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(data6.resume === undefined){const err24 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "resume"},message:"must have required property '"+"resume"+"'"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(data6.runtimeRequests === undefined){const err25 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "runtimeRequests"},message:"must have required property '"+"runtimeRequests"+"'"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(data6.structuredResult === undefined){const err26 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "structuredResult"},message:"must have required property '"+"structuredResult"+"'"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(data6.typedEvents === undefined){const err27 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "typedEvents"},message:"must have required property '"+"typedEvents"+"'"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data6.schema !== undefined){if("paperclip.prp.capabilities.v1" !== data6.schema){const err28 = {instancePath:instancePath+"/capabilities/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.capabilities.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data6.sessionReusePolicy !== undefined){let data8 = data6.sessionReusePolicy;if(!(((data8 === "new_per_run") || (data8 === "reuse_per_issue")) || (data8 === "reuse_per_workspace"))){const err29 = {instancePath:instancePath+"/capabilities/sessionReusePolicy",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/sessionReusePolicy/enum",keyword:"enum",params:{allowedValues: schema41.properties.sessionReusePolicy.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data6.driver !== undefined){let data9 = data6.driver;if(data9 && typeof data9 == "object" && !Array.isArray(data9)){if(data9.kind === undefined){const err30 = {instancePath:instancePath+"/capabilities/driver",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(data9.version === undefined){const err31 = {instancePath:instancePath+"/capabilities/driver",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(data9.kind !== undefined){let data10 = data9.kind;if(typeof data10 === "string"){if(func1(data10) > 80){const err32 = {instancePath:instancePath+"/capabilities/driver/kind",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/kind/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(func1(data10) < 1){const err33 = {instancePath:instancePath+"/capabilities/driver/kind",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/kind/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}else {const err34 = {instancePath:instancePath+"/capabilities/driver/kind",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/kind/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}if(data9.version !== undefined){let data11 = data9.version;if(typeof data11 === "string"){if(func1(data11) > 80){const err35 = {instancePath:instancePath+"/capabilities/driver/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/version/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(func1(data11) < 1){const err36 = {instancePath:instancePath+"/capabilities/driver/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/version/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}else {const err37 = {instancePath:instancePath+"/capabilities/driver/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/version/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}}else {const err38 = {instancePath:instancePath+"/capabilities/driver",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data6.steer !== undefined){if(typeof data6.steer !== "boolean"){const err39 = {instancePath:instancePath+"/capabilities/steer",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/steer/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data6.interrupt !== undefined){if(typeof data6.interrupt !== "boolean"){const err40 = {instancePath:instancePath+"/capabilities/interrupt",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/interrupt/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data6.resume !== undefined){if(typeof data6.resume !== "boolean"){const err41 = {instancePath:instancePath+"/capabilities/resume",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/resume/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}if(data6.runtimeRequests !== undefined){if(typeof data6.runtimeRequests !== "boolean"){const err42 = {instancePath:instancePath+"/capabilities/runtimeRequests",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/runtimeRequests/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data6.structuredResult !== undefined){if(typeof data6.structuredResult !== "boolean"){const err43 = {instancePath:instancePath+"/capabilities/structuredResult",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/structuredResult/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data6.typedEvents !== undefined){if(typeof data6.typedEvents !== "boolean"){const err44 = {instancePath:instancePath+"/capabilities/typedEvents",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEvents/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data6.typedEventFamilies !== undefined){let data18 = data6.typedEventFamilies;if(Array.isArray(data18)){if(data18.length > 64){const err45 = {instancePath:instancePath+"/capabilities/typedEventFamilies",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/maxItems",keyword:"maxItems",params:{limit: 64},message:"must NOT have more than 64 items"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}const len0 = data18.length;for(let i0=0; i0 160){const err51 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/family",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/family/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}if(func1(data20) < 1){const err52 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/family",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/family/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(!pattern12.test(data20)){const err53 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/family",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/family/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_.-]*$"},message:"must match pattern \""+"^[a-z][a-z0-9_.-]*$"+"\""};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/family",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/family/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data19.version !== undefined){if(1 !== data19.version){const err55 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}if(data19.availability !== undefined){let data22 = data19.availability;if(!(((data22 === "available") || (data22 === "unsupported")) || (data22 === "policy_disabled"))){const err56 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/availability",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/availability/enum",keyword:"enum",params:{allowedValues: schema41.properties.typedEventFamilies.items.properties.availability.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data19.detailLevel !== undefined){let data23 = data19.detailLevel;if(!((data23 === "summary") || (data23 === "structured"))){const err57 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/detailLevel",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/detailLevel/enum",keyword:"enum",params:{allowedValues: schema41.properties.typedEventFamilies.items.properties.detailLevel.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}}else {const err58 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}}else {const err59 = {instancePath:instancePath+"/capabilities/typedEventFamilies",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}if(data6.semanticTools !== undefined){let data24 = data6.semanticTools;if(data24 && typeof data24 == "object" && !Array.isArray(data24)){if(data24.schema === undefined){const err60 = {instancePath:instancePath+"/capabilities/semanticTools",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}if(data24.schemaVersion === undefined){const err61 = {instancePath:instancePath+"/capabilities/semanticTools",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}if(data24.operations === undefined){const err62 = {instancePath:instancePath+"/capabilities/semanticTools",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/required",keyword:"required",params:{missingProperty: "operations"},message:"must have required property '"+"operations"+"'"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(data24.schema !== undefined){if("paperclip.prp.semantic_tools.v1" !== data24.schema){const err63 = {instancePath:instancePath+"/capabilities/semanticTools/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.semantic_tools.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data24.schemaVersion !== undefined){if(1 !== data24.schemaVersion){const err64 = {instancePath:instancePath+"/capabilities/semanticTools/schemaVersion",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}if(data24.operations !== undefined){let data27 = data24.operations;if(Array.isArray(data27)){const len1 = data27.length;for(let i1=0; i1 160){const err69 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/operationId",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/operationId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}if(func1(data29) < 1){const err70 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/operationId",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/operationId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if(!pattern4.test(data29)){const err71 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/operationId",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/operationId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}else {const err72 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/operationId",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/operationId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data28.version !== undefined){if(1 !== data28.version){const err73 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}if(data28.availability !== undefined){let data31 = data28.availability;if(!(((data31 === "available") || (data31 === "denied")) || (data31 === "unsupported"))){const err74 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/availability",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/availability/enum",keyword:"enum",params:{allowedValues: schema41.properties.semanticTools.properties.operations.items.properties.availability.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}if(data28.redactionDisposition !== undefined){if("digest_only" !== data28.redactionDisposition){const err75 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/redactionDisposition",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/redactionDisposition/const",keyword:"const",params:{allowedValue: "digest_only"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data28.requiredClaims !== undefined){let data33 = data28.requiredClaims;if(Array.isArray(data33)){const len2 = data33.length;for(let i2=0; i2 160){const err76 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims/" + i2,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/items/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}if(func1(data34) < 1){const err77 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims/" + i2,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/items/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}else {const err78 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims/" + i2,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}let i3 = data33.length;let j0;if(i3 > 1){const indices0 = {};for(;i3--;){let item0 = data33[i3];if(typeof item0 !== "string"){continue;}if(typeof indices0[item0] == "number"){j0 = indices0[item0];const err79 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/uniqueItems",keyword:"uniqueItems",params:{i: i3, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i3+" are identical)"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;break;}indices0[item0] = i3;}}}else {const err80 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data28.reasonCode !== undefined){let data35 = data28.reasonCode;if(typeof data35 === "string"){if(func1(data35) > 160){const err81 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/reasonCode/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}if(func1(data35) < 1){const err82 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/reasonCode/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}if(!pattern14.test(data35)){const err83 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/reasonCode/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_.:-]*$"},message:"must match pattern \""+"^[a-z][a-z0-9_.:-]*$"+"\""};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}else {const err84 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/reasonCode/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}}else {const err85 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}}else {const err86 = {instancePath:instancePath+"/capabilities/semanticTools/operations",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}}else {const err87 = {instancePath:instancePath+"/capabilities/semanticTools",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}if(data6.unsupported !== undefined){let data36 = data6.unsupported;if(Array.isArray(data36)){const len3 = data36.length;for(let i4=0; i4 1){const indices1 = {};for(;i5--;){let item1 = data36[i5];if(typeof item1 !== "string"){continue;}if(typeof indices1[item1] == "number"){j1 = indices1[item1];const err90 = {instancePath:instancePath+"/capabilities/unsupported",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/unsupported/uniqueItems",keyword:"uniqueItems",params:{i: i5, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i5+" are identical)"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;break;}indices1[item1] = i5;}}}else {const err91 = {instancePath:instancePath+"/capabilities/unsupported",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/unsupported/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}}else {const err92 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}var _valid0 = _errs12 === errors;if(_valid0){valid1 = true;passing0 = 0;var props0 = true;}const _errs72 = errors;if(!(validate23(data6, {instancePath:instancePath+"/capabilities",parentData:data,parentDataProperty:"capabilities",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate23.errors : vErrors.concat(validate23.errors);errors = vErrors.length;}var _valid0 = _errs72 === errors;if(_valid0 && valid1){valid1 = false;passing0 = [passing0, 1];}else {if(_valid0){valid1 = true;passing0 = 1;if(props0 !== true){props0 = true;}}}if(!valid1){const err93 = {instancePath:instancePath+"/capabilities",schemaPath:"#/properties/capabilities/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}else {errors = _errs11;if(vErrors !== null){if(_errs11){vErrors.length = _errs11;}else {vErrors = null;}}}}if(data.commands !== undefined){let data38 = data.commands;if(Array.isArray(data38)){const len4 = data38.length;for(let i6=0; i6 160){const err101 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/commandId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}if(func1(data41) < 1){const err102 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/commandId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}else {const err103 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/commandId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}if(data39.controllerSeq !== undefined){let data42 = data39.controllerSeq;if(!(((typeof data42 == "number") && (!(data42 % 1) && !isNaN(data42))) && (isFinite(data42)))){const err104 = {instancePath:instancePath+"/commands/" + i6+"/controllerSeq",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/controllerSeq/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}if((typeof data42 == "number") && (isFinite(data42))){if(data42 < 1 || isNaN(data42)){const err105 = {instancePath:instancePath+"/commands/" + i6+"/controllerSeq",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/controllerSeq/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}}if(data39.type !== undefined){let data43 = data39.type;if(!((((((((((((((((((data43 === "run.prepare") || (data43 === "run.attach")) || (data43 === "session.open")) || (data43 === "turn.start")) || (data43 === "turn.steer")) || (data43 === "turn.interrupt")) || (data43 === "turn.stop")) || (data43 === "request.resolve")) || (data43 === "interaction.receipt")) || (data43 === "semantic_tool.result")) || (data43 === "session.snapshot")) || (data43 === "session.close")) || (data43 === "session.budget.increase")) || (data43 === "session.destroy")) || (data43 === "run.cancel")) || (data43 === "runner.drain")) || (data43 === "runner.suspend")) || (data43 === "runner.shutdown"))){const err106 = {instancePath:instancePath+"/commands/" + i6+"/type",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/type/enum",keyword:"enum",params:{allowedValues: schema45.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}if(data39.issuedAt !== undefined){let data44 = data39.issuedAt;if(typeof data44 === "string"){if(!(formats0.test(data44))){const err107 = {instancePath:instancePath+"/commands/" + i6+"/issuedAt",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/issuedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/commands/" + i6+"/issuedAt",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/issuedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}if(data39.deadlineAt !== undefined){let data45 = data39.deadlineAt;if(typeof data45 === "string"){if(!(formats0.test(data45))){const err109 = {instancePath:instancePath+"/commands/" + i6+"/deadlineAt",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/deadlineAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}else {const err110 = {instancePath:instancePath+"/commands/" + i6+"/deadlineAt",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/deadlineAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}if(data39.precondition !== undefined){let data46 = data39.precondition;if(data46 && typeof data46 == "object" && !Array.isArray(data46)){if(data46.runnerState !== undefined){let data47 = data46.runnerState;if(Array.isArray(data47)){const len5 = data47.length;for(let i7=0; i7 160){const err122 = {instancePath:instancePath+"/commands/" + i6+"/payload/requestId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/requestId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}if(func1(data57) < 1){const err123 = {instancePath:instancePath+"/commands/" + i6+"/payload/requestId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/requestId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}else {const err124 = {instancePath:instancePath+"/commands/" + i6+"/payload/requestId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/requestId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}if(data56.objective !== undefined){let data58 = data56.objective;if(typeof data58 === "string"){if(func1(data58) > 4000){const err125 = {instancePath:instancePath+"/commands/" + i6+"/payload/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/objective/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}if(func1(data58) < 1){const err126 = {instancePath:instancePath+"/commands/" + i6+"/payload/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/objective/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}else {const err127 = {instancePath:instancePath+"/commands/" + i6+"/payload/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/objective/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}if(data56.status !== undefined){let data59 = data56.status;if(!((data59 === "active") || (data59 === "paused"))){const err128 = {instancePath:instancePath+"/commands/" + i6+"/payload/status",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/status/enum",keyword:"enum",params:{allowedValues: schema46.allOf[0].then.properties.payload.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}if(data56.tokenBudget !== undefined){let data60 = data56.tokenBudget;if((!(((typeof data60 == "number") && (!(data60 % 1) && !isNaN(data60))) && (isFinite(data60)))) && (data60 !== null)){const err129 = {instancePath:instancePath+"/commands/" + i6+"/payload/tokenBudget",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/tokenBudget/type",keyword:"type",params:{type: schema46.allOf[0].then.properties.payload.properties.tokenBudget.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}if((typeof data60 == "number") && (isFinite(data60))){if(data60 < 1 || isNaN(data60)){const err130 = {instancePath:instancePath+"/commands/" + i6+"/payload/tokenBudget",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/tokenBudget/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}}}}else {const err131 = {instancePath:instancePath+"/commands/" + i6+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}}var _valid2 = _errs118 === errors;valid32 = _valid2;if(valid32){var props2 = {};props2.payload = true;props2.type = true;}}if(!valid32){const err132 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}if(data39 && typeof data39 == "object" && !Array.isArray(data39)){if(data39.schema === undefined){const err133 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}if(data39.commandId === undefined){const err134 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "commandId"},message:"must have required property '"+"commandId"+"'"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}if(data39.controllerSeq === undefined){const err135 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "controllerSeq"},message:"must have required property '"+"controllerSeq"+"'"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}if(data39.type === undefined){const err136 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}if(data39.issuedAt === undefined){const err137 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "issuedAt"},message:"must have required property '"+"issuedAt"+"'"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}if(data39.payload === undefined){const err138 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "payload"},message:"must have required property '"+"payload"+"'"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}if(data39.schema !== undefined){if("paperclip.prp.command.v2" !== data39.schema){const err139 = {instancePath:instancePath+"/commands/" + i6+"/schema",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.command.v2"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}if(data39.commandId !== undefined){let data62 = data39.commandId;if(typeof data62 === "string"){if(func1(data62) > 160){const err140 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/commandId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}if(func1(data62) < 1){const err141 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/commandId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}else {const err142 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/commandId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}if(data39.controllerSeq !== undefined){let data63 = data39.controllerSeq;if(!(((typeof data63 == "number") && (!(data63 % 1) && !isNaN(data63))) && (isFinite(data63)))){const err143 = {instancePath:instancePath+"/commands/" + i6+"/controllerSeq",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/controllerSeq/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}if((typeof data63 == "number") && (isFinite(data63))){if(data63 < 1 || isNaN(data63)){const err144 = {instancePath:instancePath+"/commands/" + i6+"/controllerSeq",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/controllerSeq/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}}if(data39.type !== undefined){let data64 = data39.type;if(!(((((((((((((((((((((data64 === "run.prepare") || (data64 === "run.attach")) || (data64 === "session.open")) || (data64 === "turn.start")) || (data64 === "turn.steer")) || (data64 === "turn.interrupt")) || (data64 === "turn.stop")) || (data64 === "request.resolve")) || (data64 === "interaction.receipt")) || (data64 === "semantic_tool.result")) || (data64 === "session.snapshot")) || (data64 === "session.close")) || (data64 === "session.budget.increase")) || (data64 === "session.destroy")) || (data64 === "run.cancel")) || (data64 === "runner.drain")) || (data64 === "runner.suspend")) || (data64 === "runner.shutdown")) || (data64 === "session.goal.get")) || (data64 === "session.goal.set")) || (data64 === "session.goal.clear"))){const err145 = {instancePath:instancePath+"/commands/" + i6+"/type",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/type/enum",keyword:"enum",params:{allowedValues: schema46.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}if(data39.issuedAt !== undefined){let data65 = data39.issuedAt;if(typeof data65 === "string"){if(!(formats0.test(data65))){const err146 = {instancePath:instancePath+"/commands/" + i6+"/issuedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/issuedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}}else {const err147 = {instancePath:instancePath+"/commands/" + i6+"/issuedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/issuedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}}if(data39.deadlineAt !== undefined){let data66 = data39.deadlineAt;if(typeof data66 === "string"){if(!(formats0.test(data66))){const err148 = {instancePath:instancePath+"/commands/" + i6+"/deadlineAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/deadlineAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}}else {const err149 = {instancePath:instancePath+"/commands/" + i6+"/deadlineAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/deadlineAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}}if(data39.precondition !== undefined){let data67 = data39.precondition;if(data67 && typeof data67 == "object" && !Array.isArray(data67)){if(data67.runnerState !== undefined){let data68 = data67.runnerState;if(Array.isArray(data68)){const len8 = data68.length;for(let i10=0; i10 12000){const err182 = {instancePath:instancePath+"/result/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/maxLength",keyword:"maxLength",params:{limit: 12000},message:"must NOT have more than 12000 characters"};if(vErrors === null){vErrors = [err182];}else {vErrors.push(err182);}errors++;}if(func1(data85) < 1){const err183 = {instancePath:instancePath+"/result/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err183];}else {vErrors.push(err183);}errors++;}}else {const err184 = {instancePath:instancePath+"/result/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err184];}else {vErrors.push(err184);}errors++;}}if(data80.completionClaim !== undefined){let data86 = data80.completionClaim;if(data86 && typeof data86 == "object" && !Array.isArray(data86)){if(data86.contractRevision === undefined){const err185 = {instancePath:instancePath+"/result/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "contractRevision"},message:"must have required property '"+"contractRevision"+"'"};if(vErrors === null){vErrors = [err185];}else {vErrors.push(err185);}errors++;}if(data86.objectiveSatisfied === undefined){const err186 = {instancePath:instancePath+"/result/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "objectiveSatisfied"},message:"must have required property '"+"objectiveSatisfied"+"'"};if(vErrors === null){vErrors = [err186];}else {vErrors.push(err186);}errors++;}if(data86.criteria === undefined){const err187 = {instancePath:instancePath+"/result/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "criteria"},message:"must have required property '"+"criteria"+"'"};if(vErrors === null){vErrors = [err187];}else {vErrors.push(err187);}errors++;}if(data86.remainingWork === undefined){const err188 = {instancePath:instancePath+"/result/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "remainingWork"},message:"must have required property '"+"remainingWork"+"'"};if(vErrors === null){vErrors = [err188];}else {vErrors.push(err188);}errors++;}if(data86.contractRevision !== undefined){let data87 = data86.contractRevision;if(typeof data87 === "string"){if(func1(data87) < 1){const err189 = {instancePath:instancePath+"/result/completionClaim/contractRevision",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/contractRevision/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err189];}else {vErrors.push(err189);}errors++;}}else {const err190 = {instancePath:instancePath+"/result/completionClaim/contractRevision",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/contractRevision/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err190];}else {vErrors.push(err190);}errors++;}}if(data86.objectiveSatisfied !== undefined){if(typeof data86.objectiveSatisfied !== "boolean"){const err191 = {instancePath:instancePath+"/result/completionClaim/objectiveSatisfied",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/objectiveSatisfied/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err191];}else {vErrors.push(err191);}errors++;}}if(data86.criteria !== undefined){let data89 = data86.criteria;if(Array.isArray(data89)){const len13 = data89.length;for(let i15=0; i15 12000){const err16 = {instancePath:instancePath+"/summary",schemaPath:"#/properties/summary/maxLength",keyword:"maxLength",params:{limit: 12000},message:"must NOT have more than 12000 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(func1(data4) < 1){const err17 = {instancePath:instancePath+"/summary",schemaPath:"#/properties/summary/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}else {const err18 = {instancePath:instancePath+"/summary",schemaPath:"#/properties/summary/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.completionClaim !== undefined){let data5 = data.completionClaim;if(data5 && typeof data5 == "object" && !Array.isArray(data5)){if(data5.contractRevision === undefined){const err19 = {instancePath:instancePath+"/completionClaim",schemaPath:"#/properties/completionClaim/required",keyword:"required",params:{missingProperty: "contractRevision"},message:"must have required property '"+"contractRevision"+"'"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(data5.objectiveSatisfied === undefined){const err20 = {instancePath:instancePath+"/completionClaim",schemaPath:"#/properties/completionClaim/required",keyword:"required",params:{missingProperty: "objectiveSatisfied"},message:"must have required property '"+"objectiveSatisfied"+"'"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(data5.criteria === undefined){const err21 = {instancePath:instancePath+"/completionClaim",schemaPath:"#/properties/completionClaim/required",keyword:"required",params:{missingProperty: "criteria"},message:"must have required property '"+"criteria"+"'"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(data5.remainingWork === undefined){const err22 = {instancePath:instancePath+"/completionClaim",schemaPath:"#/properties/completionClaim/required",keyword:"required",params:{missingProperty: "remainingWork"},message:"must have required property '"+"remainingWork"+"'"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(data5.contractRevision !== undefined){let data6 = data5.contractRevision;if(typeof data6 === "string"){if(func1(data6) < 1){const err23 = {instancePath:instancePath+"/completionClaim/contractRevision",schemaPath:"#/properties/completionClaim/properties/contractRevision/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/completionClaim/contractRevision",schemaPath:"#/properties/completionClaim/properties/contractRevision/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data5.objectiveSatisfied !== undefined){if(typeof data5.objectiveSatisfied !== "boolean"){const err25 = {instancePath:instancePath+"/completionClaim/objectiveSatisfied",schemaPath:"#/properties/completionClaim/properties/objectiveSatisfied/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data5.criteria !== undefined){let data8 = data5.criteria;if(Array.isArray(data8)){const len0 = data8.length;for(let i0=0; i0 160){const err9 = {instancePath:instancePath+"/companyId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(func1(data1) < 1){const err10 = {instancePath:instancePath+"/companyId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(!pattern4.test(data1)){const err11 = {instancePath:instancePath+"/companyId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/companyId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.issueId !== undefined){let data2 = data.issueId;if(typeof data2 === "string"){if(func1(data2) > 160){const err13 = {instancePath:instancePath+"/issueId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data2) < 1){const err14 = {instancePath:instancePath+"/issueId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(!pattern4.test(data2)){const err15 = {instancePath:instancePath+"/issueId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}else {const err16 = {instancePath:instancePath+"/issueId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.runId !== undefined){let data3 = data.runId;if(typeof data3 === "string"){if(func1(data3) > 160){const err17 = {instancePath:instancePath+"/runId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(func1(data3) < 1){const err18 = {instancePath:instancePath+"/runId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(!pattern4.test(data3)){const err19 = {instancePath:instancePath+"/runId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}else {const err20 = {instancePath:instancePath+"/runId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.environmentLeaseId !== undefined){let data4 = data.environmentLeaseId;if(typeof data4 === "string"){if(func1(data4) > 160){const err21 = {instancePath:instancePath+"/environmentLeaseId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data4) < 1){const err22 = {instancePath:instancePath+"/environmentLeaseId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(!pattern4.test(data4)){const err23 = {instancePath:instancePath+"/environmentLeaseId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/environmentLeaseId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.runnerInstanceId !== undefined){let data5 = data.runnerInstanceId;if(typeof data5 === "string"){if(func1(data5) > 160){const err25 = {instancePath:instancePath+"/runnerInstanceId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(func1(data5) < 1){const err26 = {instancePath:instancePath+"/runnerInstanceId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(!pattern4.test(data5)){const err27 = {instancePath:instancePath+"/runnerInstanceId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}else {const err28 = {instancePath:instancePath+"/runnerInstanceId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data.normalizedSessionId !== undefined){let data6 = data.normalizedSessionId;if(typeof data6 === "string"){if(func1(data6) > 160){const err29 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(func1(data6) < 1){const err30 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(!pattern4.test(data6)){const err31 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}else {const err32 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}if(data.driverSessionId !== undefined){let data7 = data.driverSessionId;if(typeof data7 === "string"){if(func1(data7) > 160){const err33 = {instancePath:instancePath+"/driverSessionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(func1(data7) < 1){const err34 = {instancePath:instancePath+"/driverSessionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(!pattern4.test(data7)){const err35 = {instancePath:instancePath+"/driverSessionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}else {const err36 = {instancePath:instancePath+"/driverSessionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data.providerSessionId !== undefined){let data8 = data.providerSessionId;if(typeof data8 === "string"){if(func1(data8) > 160){const err37 = {instancePath:instancePath+"/providerSessionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(func1(data8) < 1){const err38 = {instancePath:instancePath+"/providerSessionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(!pattern4.test(data8)){const err39 = {instancePath:instancePath+"/providerSessionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/providerSessionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}}else {const err41 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}validate21.errors = vErrors;return errors === 0;}validate21.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema42 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v2/capabilities.schema.json","title":"PRP v2 negotiated capabilities","type":"object","required":["schema","sessionReusePolicy","driver","steer","interrupt","resume","runtimeRequests","structuredResult","typedEvents","sessionGoals"],"properties":{"schema":{"const":"paperclip.prp.capabilities.v2"},"sessionReusePolicy":{"enum":["new_per_run","reuse_per_issue","reuse_per_workspace"]},"driver":{"type":"object","required":["kind","version"],"properties":{"kind":{"type":"string","minLength":1,"maxLength":80},"version":{"type":"string","minLength":1,"maxLength":80}},"additionalProperties":true},"steer":{"type":"boolean"},"interrupt":{"type":"boolean"},"resume":{"type":"boolean"},"runtimeRequests":{"type":"boolean"},"structuredResult":{"type":"boolean"},"typedEvents":{"type":"boolean"},"sessionGoals":{"$ref":"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability"},"unsupported":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true}},"additionalProperties":true};const schema44 = {"type":"object","required":["availability","actions","autonomousUpdates","persistentAcrossResume","maxObjectiveChars","tokenBudgetControl","usageReporting"],"properties":{"availability":{"enum":["available","unsupported","policy_disabled"]},"actions":{"type":"array","items":{"enum":["set","pause","resume","clear"]},"uniqueItems":true},"autonomousUpdates":{"type":"boolean"},"persistentAcrossResume":{"type":"boolean"},"maxObjectiveChars":{"type":"integer","minimum":1,"maximum":4000},"tokenBudgetControl":{"type":"boolean"},"usageReporting":{"type":"boolean"},"reasonCode":{"type":"string","minLength":1,"maxLength":160},"reason":{"type":"string","minLength":1,"maxLength":1000}},"additionalProperties":true};const func0 = typeof equalModule === "function" ? equalModule : equalModule.default;function validate23(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v2/capabilities.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate23.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.sessionReusePolicy === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sessionReusePolicy"},message:"must have required property '"+"sessionReusePolicy"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.driver === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "driver"},message:"must have required property '"+"driver"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.steer === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "steer"},message:"must have required property '"+"steer"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.interrupt === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "interrupt"},message:"must have required property '"+"interrupt"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.resume === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "resume"},message:"must have required property '"+"resume"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.runtimeRequests === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "runtimeRequests"},message:"must have required property '"+"runtimeRequests"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.structuredResult === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "structuredResult"},message:"must have required property '"+"structuredResult"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.typedEvents === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "typedEvents"},message:"must have required property '"+"typedEvents"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data.sessionGoals === undefined){const err9 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sessionGoals"},message:"must have required property '"+"sessionGoals"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.schema !== undefined){if("paperclip.prp.capabilities.v2" !== data.schema){const err10 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.capabilities.v2"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.sessionReusePolicy !== undefined){let data1 = data.sessionReusePolicy;if(!(((data1 === "new_per_run") || (data1 === "reuse_per_issue")) || (data1 === "reuse_per_workspace"))){const err11 = {instancePath:instancePath+"/sessionReusePolicy",schemaPath:"#/properties/sessionReusePolicy/enum",keyword:"enum",params:{allowedValues: schema42.properties.sessionReusePolicy.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.driver !== undefined){let data2 = data.driver;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.kind === undefined){const err12 = {instancePath:instancePath+"/driver",schemaPath:"#/properties/driver/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(data2.version === undefined){const err13 = {instancePath:instancePath+"/driver",schemaPath:"#/properties/driver/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(data2.kind !== undefined){let data3 = data2.kind;if(typeof data3 === "string"){if(func1(data3) > 80){const err14 = {instancePath:instancePath+"/driver/kind",schemaPath:"#/properties/driver/properties/kind/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(func1(data3) < 1){const err15 = {instancePath:instancePath+"/driver/kind",schemaPath:"#/properties/driver/properties/kind/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}else {const err16 = {instancePath:instancePath+"/driver/kind",schemaPath:"#/properties/driver/properties/kind/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.version !== undefined){let data4 = data2.version;if(typeof data4 === "string"){if(func1(data4) > 80){const err17 = {instancePath:instancePath+"/driver/version",schemaPath:"#/properties/driver/properties/version/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(func1(data4) < 1){const err18 = {instancePath:instancePath+"/driver/version",schemaPath:"#/properties/driver/properties/version/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/driver/version",schemaPath:"#/properties/driver/properties/version/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}}else {const err20 = {instancePath:instancePath+"/driver",schemaPath:"#/properties/driver/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.steer !== undefined){if(typeof data.steer !== "boolean"){const err21 = {instancePath:instancePath+"/steer",schemaPath:"#/properties/steer/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data.interrupt !== undefined){if(typeof data.interrupt !== "boolean"){const err22 = {instancePath:instancePath+"/interrupt",schemaPath:"#/properties/interrupt/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data.resume !== undefined){if(typeof data.resume !== "boolean"){const err23 = {instancePath:instancePath+"/resume",schemaPath:"#/properties/resume/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data.runtimeRequests !== undefined){if(typeof data.runtimeRequests !== "boolean"){const err24 = {instancePath:instancePath+"/runtimeRequests",schemaPath:"#/properties/runtimeRequests/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.structuredResult !== undefined){if(typeof data.structuredResult !== "boolean"){const err25 = {instancePath:instancePath+"/structuredResult",schemaPath:"#/properties/structuredResult/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data.typedEvents !== undefined){if(typeof data.typedEvents !== "boolean"){const err26 = {instancePath:instancePath+"/typedEvents",schemaPath:"#/properties/typedEvents/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data.sessionGoals !== undefined){let data11 = data.sessionGoals;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){if(data11.availability === undefined){const err27 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "availability"},message:"must have required property '"+"availability"+"'"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data11.actions === undefined){const err28 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "actions"},message:"must have required property '"+"actions"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data11.autonomousUpdates === undefined){const err29 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "autonomousUpdates"},message:"must have required property '"+"autonomousUpdates"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(data11.persistentAcrossResume === undefined){const err30 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "persistentAcrossResume"},message:"must have required property '"+"persistentAcrossResume"+"'"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(data11.maxObjectiveChars === undefined){const err31 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "maxObjectiveChars"},message:"must have required property '"+"maxObjectiveChars"+"'"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(data11.tokenBudgetControl === undefined){const err32 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "tokenBudgetControl"},message:"must have required property '"+"tokenBudgetControl"+"'"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(data11.usageReporting === undefined){const err33 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "usageReporting"},message:"must have required property '"+"usageReporting"+"'"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(data11.availability !== undefined){let data12 = data11.availability;if(!(((data12 === "available") || (data12 === "unsupported")) || (data12 === "policy_disabled"))){const err34 = {instancePath:instancePath+"/sessionGoals/availability",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/availability/enum",keyword:"enum",params:{allowedValues: schema44.properties.availability.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}if(data11.actions !== undefined){let data13 = data11.actions;if(Array.isArray(data13)){const len0 = data13.length;for(let i0=0; i0 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data13[i1], data13[j0])){const err36 = {instancePath:instancePath+"/sessionGoals/actions",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/actions/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;break outer0;}}}}}else {const err37 = {instancePath:instancePath+"/sessionGoals/actions",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/actions/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data11.autonomousUpdates !== undefined){if(typeof data11.autonomousUpdates !== "boolean"){const err38 = {instancePath:instancePath+"/sessionGoals/autonomousUpdates",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/autonomousUpdates/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data11.persistentAcrossResume !== undefined){if(typeof data11.persistentAcrossResume !== "boolean"){const err39 = {instancePath:instancePath+"/sessionGoals/persistentAcrossResume",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/persistentAcrossResume/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data11.maxObjectiveChars !== undefined){let data17 = data11.maxObjectiveChars;if(!(((typeof data17 == "number") && (!(data17 % 1) && !isNaN(data17))) && (isFinite(data17)))){const err40 = {instancePath:instancePath+"/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if((typeof data17 == "number") && (isFinite(data17))){if(data17 > 4000 || isNaN(data17)){const err41 = {instancePath:instancePath+"/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/maximum",keyword:"maximum",params:{comparison: "<=", limit: 4000},message:"must be <= 4000"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}if(data17 < 1 || isNaN(data17)){const err42 = {instancePath:instancePath+"/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}}if(data11.tokenBudgetControl !== undefined){if(typeof data11.tokenBudgetControl !== "boolean"){const err43 = {instancePath:instancePath+"/sessionGoals/tokenBudgetControl",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/tokenBudgetControl/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data11.usageReporting !== undefined){if(typeof data11.usageReporting !== "boolean"){const err44 = {instancePath:instancePath+"/sessionGoals/usageReporting",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/usageReporting/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data11.reasonCode !== undefined){let data20 = data11.reasonCode;if(typeof data20 === "string"){if(func1(data20) > 160){const err45 = {instancePath:instancePath+"/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func1(data20) < 1){const err46 = {instancePath:instancePath+"/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data11.reason !== undefined){let data21 = data11.reason;if(typeof data21 === "string"){if(func1(data21) > 1000){const err48 = {instancePath:instancePath+"/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}if(func1(data21) < 1){const err49 = {instancePath:instancePath+"/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}else {const err50 = {instancePath:instancePath+"/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}else {const err51 = {instancePath:instancePath+"/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data.unsupported !== undefined){let data22 = data.unsupported;if(Array.isArray(data22)){const len1 = data22.length;for(let i2=0; i2 1){const indices0 = {};for(;i3--;){let item0 = data22[i3];if(typeof item0 !== "string"){continue;}if(typeof indices0[item0] == "number"){j1 = indices0[item0];const err54 = {instancePath:instancePath+"/unsupported",schemaPath:"#/properties/unsupported/uniqueItems",keyword:"uniqueItems",params:{i: i3, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i3+" are identical)"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;break;}indices0[item0] = i3;}}}else {const err55 = {instancePath:instancePath+"/unsupported",schemaPath:"#/properties/unsupported/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}validate23.errors = vErrors;return errors === 0;}validate23.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema47 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/event.schema.json","title":"PRP native event","type":"object","required":["schema","sourceEventId","sourceSeq","sourceInstanceId","sourceKind","runId","eventType","schemaVersion","priority","emittedAt","payload"],"properties":{"schema":{"const":"paperclip.prp.event.v1"},"sourceEventId":{"type":"string","minLength":1,"maxLength":160},"sourceSeq":{"type":"integer","minimum":1,"maximum":9007199254740991},"sourceInstanceId":{"type":"string","minLength":1,"maxLength":160},"sourceKind":{"enum":["runner","control_plane"]},"runId":{"type":"string","minLength":1,"maxLength":160},"normalizedSessionId":{"type":"string","minLength":1,"maxLength":160},"turnId":{"type":"string","minLength":1,"maxLength":240},"itemId":{"type":"string","minLength":1,"maxLength":240},"eventType":{"enum":["runner.connected","runner.reconnected","runner.reconciled","runner.disconnected","runner.draining","runner.backpressure","runner.suspending","runner.suspended","runner.stopped","runner.diagnostic","runtime.phase.changed","sandbox.metric","workspace.ready","workspace.change.updated","workspace.diff.recorded","workspace.file.referenced","harness.starting","harness.ready","harness.exited","harness.diagnostic","plan.updated","tool.execution.started","tool.execution.progressed","tool.execution.completed","research.started","research.progressed","research.completed","delegation.started","delegation.updated","delegation.completed","model.route.changed","model.verification.updated","context.compacted","artifact.viewed","artifact.generated","review.mode.changed","hook.started","hook.completed","memory.citation.referenced","safety.review.started","safety.review.completed","terminal.input.sent","wait.started","wait.completed","provider.notice.recorded","session.starting","session.started","session.resuming","session.resumed","session.reconciled","session.updated","session.closed","session.failed","turn.submitted","turn.accepted","turn.started","turn.completed","turn.failed","turn.interrupted","turn.cancelled","item.started","item.delta","item.completed","item.failed","usage.reported","semantic_tool.input","semantic_tool.result","semantic_tool.reconciled","mcp_app.discovered","mcp_app.resource.resolved","mcp_app.initializing","mcp_app.ready","mcp_app.tool_input","mcp_app.tool_result","mcp_app.action.requested","mcp_app.action.resolved","mcp_app.host_context.changed","mcp_app.failed","mcp_app.teardown","runtime_request.created","runtime_request.resolved","runtime_request.expired","runtime_request.cancelled","interaction.request.proposed","interaction.request.materialized","interaction.request.rejected","interaction.response.progressed","interaction.response.resolved","interaction.response.delivered","run.attached","run.detached","run.result.proposed","run.result.accepted","run.result.rejected","attention.request.proposed","attention.request.routed","attention.request.resolved","attention.request.expired","attention.request.superseded","work.assessment.recorded","issue.status.decision.recorded","issue.status.decision.applied","issue.status.decision.rejected","issue.status.decision.superseded","run.terminal"]},"schemaVersion":{"const":1},"priority":{"enum":[0,1,2]},"emittedAt":{"type":"string","format":"date-time"},"observedAt":{"type":"string","format":"date-time"},"payload":{"type":"object","properties":{"channel":{"enum":["progress","final","summary","detail","unknown"]},"providerPhase":{"type":"string"},"providerMethod":{"type":"string"}},"additionalProperties":true},"debug":{"type":"object","additionalProperties":true}},"allOf":[{"if":{"properties":{"eventType":{"const":"plan.updated"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/plan"}}}},{"if":{"properties":{"eventType":{"enum":["tool.execution.started","tool.execution.progressed","tool.execution.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/toolExecution"}}}},{"if":{"properties":{"eventType":{"enum":["research.started","research.progressed","research.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/research"}}}},{"if":{"properties":{"eventType":{"enum":["delegation.started","delegation.updated","delegation.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/delegation"}}}},{"if":{"properties":{"eventType":{"const":"model.route.changed"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/modelRoute"}}}},{"if":{"properties":{"eventType":{"const":"model.verification.updated"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/modelVerification"}}}},{"if":{"properties":{"eventType":{"const":"context.compacted"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/contextCompacted"}}}},{"if":{"properties":{"eventType":{"const":"artifact.viewed"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/artifactViewed"}}}},{"if":{"properties":{"eventType":{"const":"artifact.generated"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/artifactGenerated"}}}},{"if":{"properties":{"eventType":{"const":"review.mode.changed"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/reviewMode"}}}},{"if":{"properties":{"eventType":{"enum":["hook.started","hook.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/hook"}}}},{"if":{"properties":{"eventType":{"const":"memory.citation.referenced"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/memoryCitation"}}}},{"if":{"properties":{"eventType":{"enum":["safety.review.started","safety.review.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/safetyReview"}}}},{"if":{"properties":{"eventType":{"const":"terminal.input.sent"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/terminalInput"}}}},{"if":{"properties":{"eventType":{"enum":["wait.started","wait.completed"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/wait"}}}},{"if":{"properties":{"eventType":{"const":"provider.notice.recorded"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/providerNotice"}}}},{"if":{"properties":{"eventType":{"const":"workspace.file.referenced"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json"}}}},{"if":{"properties":{"eventType":{"enum":["workspace.change.updated","workspace.diff.recorded"]}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json"}}}},{"if":{"properties":{"eventType":{"const":"workspace.diff.recorded"}}},"then":{"properties":{"payload":{"allOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json"},{"type":"object","properties":{"complete":{"const":true}}}]}}}},{"if":{"properties":{"eventType":{"enum":["semantic_tool.input","mcp_app.tool_input"]}}},"then":{"properties":{"payload":{"type":"object","properties":{"semantic_tool":{"allOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json"},{"type":"object","properties":{"phase":{"const":"input"}}}]}},"additionalProperties":true}}}},{"if":{"properties":{"eventType":{"enum":["semantic_tool.result","mcp_app.tool_result"]}}},"then":{"properties":{"payload":{"type":"object","properties":{"semantic_tool":{"allOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json"},{"type":"object","properties":{"phase":{"const":"result"}}}]}},"additionalProperties":true}}}},{"if":{"properties":{"eventType":{"const":"semantic_tool.reconciled"}}},"then":{"properties":{"payload":{"type":"object","properties":{"semantic_tool":{"allOf":[{"$ref":"https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json"},{"type":"object","properties":{"phase":{"const":"reconciled"}}}]}},"additionalProperties":true}}}},{"if":{"properties":{"eventType":{"const":"run.terminal"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/terminal.schema.json"}}}},{"if":{"properties":{"eventType":{"const":"run.result.proposed"}}},"then":{"properties":{"payload":{"$ref":"https://paperclip.dev/schemas/prp/v1/result.schema.json"}}}}],"additionalProperties":true};const schema171 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json","title":"Paperclip workspace file reference","type":"object","required":["schema","referenceId","source","path","displayName","mediaType","presentation","line","preview","previewTruncated","contentDigest"],"properties":{"schema":{"const":"paperclip.workspace.file_reference.v1"},"referenceId":{"type":"string","minLength":1,"maxLength":240},"source":{"enum":["harness_reported","runner_verified"]},"path":{"type":"string","minLength":1,"maxLength":1024,"pattern":"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},"displayName":{"type":"string","minLength":1,"maxLength":255},"mediaType":{"type":["string","null"],"maxLength":160},"presentation":{"enum":["document","code","image","generic"]},"line":{"type":["integer","null"],"minimum":1},"preview":{"type":["string","null"],"maxLength":262144},"previewTruncated":{"type":"boolean"},"contentDigest":{"oneOf":[{"type":"null"},{"type":"string","pattern":"^sha256:[a-f0-9]{64}$"}]}},"additionalProperties":false};const schema172 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json","title":"Paperclip workspace diff","type":"object","required":["schema","changeSetId","revision","source","complete","files","totals","patchArtifactRef"],"properties":{"schema":{"const":"paperclip.workspace.diff.v1"},"changeSetId":{"type":"string","minLength":1,"maxLength":200},"revision":{"type":"integer","minimum":1},"source":{"enum":["harness_reported","runner_verified"]},"complete":{"type":"boolean"},"files":{"type":"array","maxItems":2000,"items":{"type":"object","required":["path","operation","previousPath","additions","deletions","binary","diff"],"properties":{"path":{"type":"string","minLength":1,"maxLength":1024,"pattern":"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},"operation":{"enum":["create","modify","delete","rename","mode_change"]},"previousPath":{"oneOf":[{"type":"null"},{"type":"string","minLength":1,"maxLength":1024,"pattern":"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"}]},"additions":{"type":["integer","null"],"minimum":0},"deletions":{"type":["integer","null"],"minimum":0},"binary":{"type":"boolean"},"diff":{"type":["string","null"],"maxLength":262144}},"additionalProperties":false}},"totals":{"type":"object","required":["files","additions","deletions"],"properties":{"files":{"type":"integer","minimum":0},"additions":{"type":["integer","null"],"minimum":0},"deletions":{"type":["integer","null"],"minimum":0}},"additionalProperties":false},"patchArtifactRef":{"type":["string","null"],"maxLength":2048}},"additionalProperties":false};const schema49 = {"type":"object","description":"A complete provider-authored within-turn checklist snapshot. Consumers replace the prior snapshot with the same planId in PRP sourceSeq order; this is not Paperclip's durable Plan document.","required":["schema","planId","revision","steps","complete","syncStatus"],"properties":{"schema":{"const":"paperclip.plan.updated.v1"},"planId":{"$ref":"#/$defs/id"},"revision":{"type":"integer","minimum":1},"explanation":{"$ref":"#/$defs/nullableShortText"},"steps":{"type":"array","maxItems":256,"items":{"type":"object","required":["stepId","body","status"],"properties":{"stepId":{"$ref":"#/$defs/id"},"body":{"$ref":"#/$defs/shortText"},"status":{"enum":["pending","in_progress","completed","blocked"]}},"additionalProperties":false}},"complete":{"type":"boolean"},"syncStatus":{"description":"Legacy compatibility field. Within-turn checklist snapshots use not_applicable.","enum":["streaming","pending","synchronized","conflict","not_applicable"]},"documentRevision":{"description":"Legacy compatibility field. Within-turn checklist snapshots use null.","type":["integer","null"],"minimum":1}},"additionalProperties":false};const schema50 = {"type":"string","minLength":1,"maxLength":160,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"};const schema51 = {"type":["string","null"],"maxLength":4000};const schema53 = {"type":"string","maxLength":4000};function validate60(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate60.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.planId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "planId"},message:"must have required property '"+"planId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.revision === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "revision"},message:"must have required property '"+"revision"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.steps === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "steps"},message:"must have required property '"+"steps"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.complete === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "complete"},message:"must have required property '"+"complete"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.syncStatus === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "syncStatus"},message:"must have required property '"+"syncStatus"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema") || (key0 === "planId")) || (key0 === "revision")) || (key0 === "explanation")) || (key0 === "steps")) || (key0 === "complete")) || (key0 === "syncStatus")) || (key0 === "documentRevision"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.plan.updated.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.plan.updated.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.planId !== undefined){let data1 = data.planId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/planId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/planId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/planId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/planId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.revision !== undefined){let data2 = data.revision;if(!(((typeof data2 == "number") && (!(data2 % 1) && !isNaN(data2))) && (isFinite(data2)))){const err12 = {instancePath:instancePath+"/revision",schemaPath:"#/properties/revision/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if((typeof data2 == "number") && (isFinite(data2))){if(data2 < 1 || isNaN(data2)){const err13 = {instancePath:instancePath+"/revision",schemaPath:"#/properties/revision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}}if(data.explanation !== undefined){let data3 = data.explanation;if((typeof data3 !== "string") && (data3 !== null)){const err14 = {instancePath:instancePath+"/explanation",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(typeof data3 === "string"){if(func1(data3) > 4000){const err15 = {instancePath:instancePath+"/explanation",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}if(data.steps !== undefined){let data4 = data.steps;if(Array.isArray(data4)){if(data4.length > 256){const err16 = {instancePath:instancePath+"/steps",schemaPath:"#/properties/steps/maxItems",keyword:"maxItems",params:{limit: 256},message:"must NOT have more than 256 items"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}const len0 = data4.length;for(let i0=0; i0 160){const err21 = {instancePath:instancePath+"/steps/" + i0+"/stepId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data6) < 1){const err22 = {instancePath:instancePath+"/steps/" + i0+"/stepId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(!pattern4.test(data6)){const err23 = {instancePath:instancePath+"/steps/" + i0+"/stepId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/steps/" + i0+"/stepId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data5.body !== undefined){let data7 = data5.body;if(typeof data7 === "string"){if(func1(data7) > 4000){const err25 = {instancePath:instancePath+"/steps/" + i0+"/body",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/steps/" + i0+"/body",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data5.status !== undefined){let data8 = data5.status;if(!((((data8 === "pending") || (data8 === "in_progress")) || (data8 === "completed")) || (data8 === "blocked"))){const err27 = {instancePath:instancePath+"/steps/" + i0+"/status",schemaPath:"#/properties/steps/items/properties/status/enum",keyword:"enum",params:{allowedValues: schema49.properties.steps.items.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/steps/" + i0,schemaPath:"#/properties/steps/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}else {const err29 = {instancePath:instancePath+"/steps",schemaPath:"#/properties/steps/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data.complete !== undefined){if(typeof data.complete !== "boolean"){const err30 = {instancePath:instancePath+"/complete",schemaPath:"#/properties/complete/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data.syncStatus !== undefined){let data10 = data.syncStatus;if(!(((((data10 === "streaming") || (data10 === "pending")) || (data10 === "synchronized")) || (data10 === "conflict")) || (data10 === "not_applicable"))){const err31 = {instancePath:instancePath+"/syncStatus",schemaPath:"#/properties/syncStatus/enum",keyword:"enum",params:{allowedValues: schema49.properties.syncStatus.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}if(data.documentRevision !== undefined){let data11 = data.documentRevision;if((!(((typeof data11 == "number") && (!(data11 % 1) && !isNaN(data11))) && (isFinite(data11)))) && (data11 !== null)){const err32 = {instancePath:instancePath+"/documentRevision",schemaPath:"#/properties/documentRevision/type",keyword:"type",params:{type: schema49.properties.documentRevision.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if((typeof data11 == "number") && (isFinite(data11))){if(data11 < 1 || isNaN(data11)){const err33 = {instancePath:instancePath+"/documentRevision",schemaPath:"#/properties/documentRevision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}}else {const err34 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}validate60.errors = vErrors;return errors === 0;}validate60.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema54 = {"type":"object","required":["schema","executionId","transport","operation","status","output","outputBytes","outputTruncated","outputDigest"],"properties":{"schema":{"const":"paperclip.tool.execution.v1"},"executionId":{"$ref":"#/$defs/id"},"transport":{"enum":["process","mcp","dynamic","builtin"]},"operation":{"enum":["read","search","list","execute","edit","unknown"]},"name":{"$ref":"#/$defs/nullableShortText"},"target":{"$ref":"#/$defs/safePath"},"namespace":{"type":["string","null"],"maxLength":240},"readOnly":{"type":["boolean","null"]},"inputUpdated":{"type":"boolean"},"status":{"enum":["running","completed","failed","cancelled","interrupted"]},"durationMs":{"type":["integer","null"],"minimum":0},"exitCode":{"type":["integer","null"]},"progress":{"$ref":"#/$defs/nullableShortText"},"output":{"type":["string","null"],"maxLength":65536},"outputBytes":{"type":"integer","minimum":0},"outputTruncated":{"type":"boolean"},"outputDigest":{"type":["string","null"],"pattern":"^sha256:[a-f0-9]{64}$"}},"additionalProperties":false};const schema57 = {"type":["string","null"],"maxLength":4096,"pattern":"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"};const func58 = Object.prototype.hasOwnProperty;const pattern18 = new RegExp("^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$", "u");const pattern19 = new RegExp("^sha256:[a-f0-9]{64}$", "u");function validate62(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate62.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.executionId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "executionId"},message:"must have required property '"+"executionId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.transport === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "transport"},message:"must have required property '"+"transport"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.operation === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "operation"},message:"must have required property '"+"operation"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.status === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.output === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "output"},message:"must have required property '"+"output"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.outputBytes === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "outputBytes"},message:"must have required property '"+"outputBytes"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.outputTruncated === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "outputTruncated"},message:"must have required property '"+"outputTruncated"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.outputDigest === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "outputDigest"},message:"must have required property '"+"outputDigest"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}for(const key0 in data){if(!(func58.call(schema54.properties, key0))){const err9 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.schema !== undefined){if("paperclip.tool.execution.v1" !== data.schema){const err10 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.tool.execution.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.executionId !== undefined){let data1 = data.executionId;if(typeof data1 === "string"){if(func1(data1) > 160){const err11 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(func1(data1) < 1){const err12 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(!pattern4.test(data1)){const err13 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}else {const err14 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.transport !== undefined){let data2 = data.transport;if(!((((data2 === "process") || (data2 === "mcp")) || (data2 === "dynamic")) || (data2 === "builtin"))){const err15 = {instancePath:instancePath+"/transport",schemaPath:"#/properties/transport/enum",keyword:"enum",params:{allowedValues: schema54.properties.transport.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.operation !== undefined){let data3 = data.operation;if(!((((((data3 === "read") || (data3 === "search")) || (data3 === "list")) || (data3 === "execute")) || (data3 === "edit")) || (data3 === "unknown"))){const err16 = {instancePath:instancePath+"/operation",schemaPath:"#/properties/operation/enum",keyword:"enum",params:{allowedValues: schema54.properties.operation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.name !== undefined){let data4 = data.name;if((typeof data4 !== "string") && (data4 !== null)){const err17 = {instancePath:instancePath+"/name",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 4000){const err18 = {instancePath:instancePath+"/name",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}}if(data.target !== undefined){let data5 = data.target;if((typeof data5 !== "string") && (data5 !== null)){const err19 = {instancePath:instancePath+"/target",schemaPath:"#/$defs/safePath/type",keyword:"type",params:{type: schema57.type},message:"must be string,null"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data5 === "string"){if(func1(data5) > 4096){const err20 = {instancePath:instancePath+"/target",schemaPath:"#/$defs/safePath/maxLength",keyword:"maxLength",params:{limit: 4096},message:"must NOT have more than 4096 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(!pattern18.test(data5)){const err21 = {instancePath:instancePath+"/target",schemaPath:"#/$defs/safePath/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"+"\""};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}}if(data.namespace !== undefined){let data6 = data.namespace;if((typeof data6 !== "string") && (data6 !== null)){const err22 = {instancePath:instancePath+"/namespace",schemaPath:"#/properties/namespace/type",keyword:"type",params:{type: schema54.properties.namespace.type},message:"must be string,null"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(typeof data6 === "string"){if(func1(data6) > 240){const err23 = {instancePath:instancePath+"/namespace",schemaPath:"#/properties/namespace/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}if(data.readOnly !== undefined){let data7 = data.readOnly;if((typeof data7 !== "boolean") && (data7 !== null)){const err24 = {instancePath:instancePath+"/readOnly",schemaPath:"#/properties/readOnly/type",keyword:"type",params:{type: schema54.properties.readOnly.type},message:"must be boolean,null"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.inputUpdated !== undefined){if(typeof data.inputUpdated !== "boolean"){const err25 = {instancePath:instancePath+"/inputUpdated",schemaPath:"#/properties/inputUpdated/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data.status !== undefined){let data9 = data.status;if(!(((((data9 === "running") || (data9 === "completed")) || (data9 === "failed")) || (data9 === "cancelled")) || (data9 === "interrupted"))){const err26 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema54.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data.durationMs !== undefined){let data10 = data.durationMs;if((!(((typeof data10 == "number") && (!(data10 % 1) && !isNaN(data10))) && (isFinite(data10)))) && (data10 !== null)){const err27 = {instancePath:instancePath+"/durationMs",schemaPath:"#/properties/durationMs/type",keyword:"type",params:{type: schema54.properties.durationMs.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if((typeof data10 == "number") && (isFinite(data10))){if(data10 < 0 || isNaN(data10)){const err28 = {instancePath:instancePath+"/durationMs",schemaPath:"#/properties/durationMs/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}if(data.exitCode !== undefined){let data11 = data.exitCode;if((!(((typeof data11 == "number") && (!(data11 % 1) && !isNaN(data11))) && (isFinite(data11)))) && (data11 !== null)){const err29 = {instancePath:instancePath+"/exitCode",schemaPath:"#/properties/exitCode/type",keyword:"type",params:{type: schema54.properties.exitCode.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data.progress !== undefined){let data12 = data.progress;if((typeof data12 !== "string") && (data12 !== null)){const err30 = {instancePath:instancePath+"/progress",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(typeof data12 === "string"){if(func1(data12) > 4000){const err31 = {instancePath:instancePath+"/progress",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}}if(data.output !== undefined){let data13 = data.output;if((typeof data13 !== "string") && (data13 !== null)){const err32 = {instancePath:instancePath+"/output",schemaPath:"#/properties/output/type",keyword:"type",params:{type: schema54.properties.output.type},message:"must be string,null"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(typeof data13 === "string"){if(func1(data13) > 65536){const err33 = {instancePath:instancePath+"/output",schemaPath:"#/properties/output/maxLength",keyword:"maxLength",params:{limit: 65536},message:"must NOT have more than 65536 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}if(data.outputBytes !== undefined){let data14 = data.outputBytes;if(!(((typeof data14 == "number") && (!(data14 % 1) && !isNaN(data14))) && (isFinite(data14)))){const err34 = {instancePath:instancePath+"/outputBytes",schemaPath:"#/properties/outputBytes/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if((typeof data14 == "number") && (isFinite(data14))){if(data14 < 0 || isNaN(data14)){const err35 = {instancePath:instancePath+"/outputBytes",schemaPath:"#/properties/outputBytes/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}}if(data.outputTruncated !== undefined){if(typeof data.outputTruncated !== "boolean"){const err36 = {instancePath:instancePath+"/outputTruncated",schemaPath:"#/properties/outputTruncated/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data.outputDigest !== undefined){let data16 = data.outputDigest;if((typeof data16 !== "string") && (data16 !== null)){const err37 = {instancePath:instancePath+"/outputDigest",schemaPath:"#/properties/outputDigest/type",keyword:"type",params:{type: schema54.properties.outputDigest.type},message:"must be string,null"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(typeof data16 === "string"){if(!pattern19.test(data16)){const err38 = {instancePath:instancePath+"/outputDigest",schemaPath:"#/properties/outputDigest/pattern",keyword:"pattern",params:{pattern: "^sha256:[a-f0-9]{64}$"},message:"must match pattern \""+"^sha256:[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}}}else {const err39 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}validate62.errors = vErrors;return errors === 0;}validate62.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema59 = {"type":"object","required":["schema","researchId","action","status","sources"],"properties":{"schema":{"const":"paperclip.research.v1"},"researchId":{"$ref":"#/$defs/id"},"action":{"enum":["search","open_page","find_in_page","other"]},"status":{"enum":["running","completed","failed","cancelled"]},"query":{"$ref":"#/$defs/nullableShortText"},"url":{"$ref":"#/$defs/safeUrl"},"pattern":{"$ref":"#/$defs/nullableShortText"},"sources":{"type":"array","maxItems":64,"items":{"type":"object","required":["sourceId","title","url","snippet"],"properties":{"sourceId":{"$ref":"#/$defs/id"},"title":{"$ref":"#/$defs/shortText"},"url":{"$ref":"#/$defs/safeUrl"},"snippet":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false}}},"additionalProperties":false};const schema62 = {"type":["string","null"],"maxLength":8192,"pattern":"^https?://"};const pattern21 = new RegExp("^https?://", "u");function validate64(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate64.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.researchId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "researchId"},message:"must have required property '"+"researchId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.action === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "action"},message:"must have required property '"+"action"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.status === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.sources === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sources"},message:"must have required property '"+"sources"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema") || (key0 === "researchId")) || (key0 === "action")) || (key0 === "status")) || (key0 === "query")) || (key0 === "url")) || (key0 === "pattern")) || (key0 === "sources"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema !== undefined){if("paperclip.research.v1" !== data.schema){const err6 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.research.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.researchId !== undefined){let data1 = data.researchId;if(typeof data1 === "string"){if(func1(data1) > 160){const err7 = {instancePath:instancePath+"/researchId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func1(data1) < 1){const err8 = {instancePath:instancePath+"/researchId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!pattern4.test(data1)){const err9 = {instancePath:instancePath+"/researchId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/researchId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.action !== undefined){let data2 = data.action;if(!((((data2 === "search") || (data2 === "open_page")) || (data2 === "find_in_page")) || (data2 === "other"))){const err11 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema59.properties.action.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.status !== undefined){let data3 = data.status;if(!((((data3 === "running") || (data3 === "completed")) || (data3 === "failed")) || (data3 === "cancelled"))){const err12 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema59.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.query !== undefined){let data4 = data.query;if((typeof data4 !== "string") && (data4 !== null)){const err13 = {instancePath:instancePath+"/query",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 4000){const err14 = {instancePath:instancePath+"/query",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}}if(data.url !== undefined){let data5 = data.url;if((typeof data5 !== "string") && (data5 !== null)){const err15 = {instancePath:instancePath+"/url",schemaPath:"#/$defs/safeUrl/type",keyword:"type",params:{type: schema62.type},message:"must be string,null"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(typeof data5 === "string"){if(func1(data5) > 8192){const err16 = {instancePath:instancePath+"/url",schemaPath:"#/$defs/safeUrl/maxLength",keyword:"maxLength",params:{limit: 8192},message:"must NOT have more than 8192 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(!pattern21.test(data5)){const err17 = {instancePath:instancePath+"/url",schemaPath:"#/$defs/safeUrl/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}}if(data.pattern !== undefined){let data6 = data.pattern;if((typeof data6 !== "string") && (data6 !== null)){const err18 = {instancePath:instancePath+"/pattern",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(typeof data6 === "string"){if(func1(data6) > 4000){const err19 = {instancePath:instancePath+"/pattern",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}}if(data.sources !== undefined){let data7 = data.sources;if(Array.isArray(data7)){if(data7.length > 64){const err20 = {instancePath:instancePath+"/sources",schemaPath:"#/properties/sources/maxItems",keyword:"maxItems",params:{limit: 64},message:"must NOT have more than 64 items"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}const len0 = data7.length;for(let i0=0; i0 160){const err26 = {instancePath:instancePath+"/sources/" + i0+"/sourceId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(func1(data9) < 1){const err27 = {instancePath:instancePath+"/sources/" + i0+"/sourceId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(!pattern4.test(data9)){const err28 = {instancePath:instancePath+"/sources/" + i0+"/sourceId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}else {const err29 = {instancePath:instancePath+"/sources/" + i0+"/sourceId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data8.title !== undefined){let data10 = data8.title;if(typeof data10 === "string"){if(func1(data10) > 4000){const err30 = {instancePath:instancePath+"/sources/" + i0+"/title",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}else {const err31 = {instancePath:instancePath+"/sources/" + i0+"/title",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}if(data8.url !== undefined){let data11 = data8.url;if((typeof data11 !== "string") && (data11 !== null)){const err32 = {instancePath:instancePath+"/sources/" + i0+"/url",schemaPath:"#/$defs/safeUrl/type",keyword:"type",params:{type: schema62.type},message:"must be string,null"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(typeof data11 === "string"){if(func1(data11) > 8192){const err33 = {instancePath:instancePath+"/sources/" + i0+"/url",schemaPath:"#/$defs/safeUrl/maxLength",keyword:"maxLength",params:{limit: 8192},message:"must NOT have more than 8192 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(!pattern21.test(data11)){const err34 = {instancePath:instancePath+"/sources/" + i0+"/url",schemaPath:"#/$defs/safeUrl/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}if(data8.snippet !== undefined){let data12 = data8.snippet;if((typeof data12 !== "string") && (data12 !== null)){const err35 = {instancePath:instancePath+"/sources/" + i0+"/snippet",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(typeof data12 === "string"){if(func1(data12) > 4000){const err36 = {instancePath:instancePath+"/sources/" + i0+"/snippet",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}}}else {const err37 = {instancePath:instancePath+"/sources/" + i0,schemaPath:"#/properties/sources/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}}else {const err38 = {instancePath:instancePath+"/sources",schemaPath:"#/properties/sources/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}}else {const err39 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}validate64.errors = vErrors;return errors === 0;}validate64.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema68 = {"type":"object","required":["schema","delegationId","action","status","children"],"properties":{"schema":{"const":"paperclip.delegation.v1"},"delegationId":{"$ref":"#/$defs/id"},"action":{"enum":["spawn","message","resume","wait","close"]},"status":{"enum":["running","waiting","completed","failed","interrupted","closed"]},"children":{"type":"array","maxItems":64,"items":{"type":"object","required":["childId","role","model","status","summary","activitySummary"],"properties":{"childId":{"$ref":"#/$defs/id"},"role":{"type":["string","null"],"maxLength":160},"model":{"type":["string","null"],"maxLength":240},"status":{"enum":["running","waiting","completed","failed","interrupted","closed"]},"summary":{"$ref":"#/$defs/nullableShortText"},"activitySummary":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false}}},"additionalProperties":false};function validate66(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate66.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.delegationId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "delegationId"},message:"must have required property '"+"delegationId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.action === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "action"},message:"must have required property '"+"action"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.status === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.children === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "children"},message:"must have required property '"+"children"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!(((((key0 === "schema") || (key0 === "delegationId")) || (key0 === "action")) || (key0 === "status")) || (key0 === "children"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema !== undefined){if("paperclip.delegation.v1" !== data.schema){const err6 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.delegation.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.delegationId !== undefined){let data1 = data.delegationId;if(typeof data1 === "string"){if(func1(data1) > 160){const err7 = {instancePath:instancePath+"/delegationId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func1(data1) < 1){const err8 = {instancePath:instancePath+"/delegationId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!pattern4.test(data1)){const err9 = {instancePath:instancePath+"/delegationId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/delegationId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.action !== undefined){let data2 = data.action;if(!(((((data2 === "spawn") || (data2 === "message")) || (data2 === "resume")) || (data2 === "wait")) || (data2 === "close"))){const err11 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema68.properties.action.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.status !== undefined){let data3 = data.status;if(!((((((data3 === "running") || (data3 === "waiting")) || (data3 === "completed")) || (data3 === "failed")) || (data3 === "interrupted")) || (data3 === "closed"))){const err12 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema68.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.children !== undefined){let data4 = data.children;if(Array.isArray(data4)){if(data4.length > 64){const err13 = {instancePath:instancePath+"/children",schemaPath:"#/properties/children/maxItems",keyword:"maxItems",params:{limit: 64},message:"must NOT have more than 64 items"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}const len0 = data4.length;for(let i0=0; i0 160){const err21 = {instancePath:instancePath+"/children/" + i0+"/childId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data6) < 1){const err22 = {instancePath:instancePath+"/children/" + i0+"/childId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(!pattern4.test(data6)){const err23 = {instancePath:instancePath+"/children/" + i0+"/childId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/children/" + i0+"/childId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data5.role !== undefined){let data7 = data5.role;if((typeof data7 !== "string") && (data7 !== null)){const err25 = {instancePath:instancePath+"/children/" + i0+"/role",schemaPath:"#/properties/children/items/properties/role/type",keyword:"type",params:{type: schema68.properties.children.items.properties.role.type},message:"must be string,null"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(typeof data7 === "string"){if(func1(data7) > 160){const err26 = {instancePath:instancePath+"/children/" + i0+"/role",schemaPath:"#/properties/children/items/properties/role/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}}if(data5.model !== undefined){let data8 = data5.model;if((typeof data8 !== "string") && (data8 !== null)){const err27 = {instancePath:instancePath+"/children/" + i0+"/model",schemaPath:"#/properties/children/items/properties/model/type",keyword:"type",params:{type: schema68.properties.children.items.properties.model.type},message:"must be string,null"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(typeof data8 === "string"){if(func1(data8) > 240){const err28 = {instancePath:instancePath+"/children/" + i0+"/model",schemaPath:"#/properties/children/items/properties/model/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}if(data5.status !== undefined){let data9 = data5.status;if(!((((((data9 === "running") || (data9 === "waiting")) || (data9 === "completed")) || (data9 === "failed")) || (data9 === "interrupted")) || (data9 === "closed"))){const err29 = {instancePath:instancePath+"/children/" + i0+"/status",schemaPath:"#/properties/children/items/properties/status/enum",keyword:"enum",params:{allowedValues: schema68.properties.children.items.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data5.summary !== undefined){let data10 = data5.summary;if((typeof data10 !== "string") && (data10 !== null)){const err30 = {instancePath:instancePath+"/children/" + i0+"/summary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(typeof data10 === "string"){if(func1(data10) > 4000){const err31 = {instancePath:instancePath+"/children/" + i0+"/summary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}}if(data5.activitySummary !== undefined){let data11 = data5.activitySummary;if((typeof data11 !== "string") && (data11 !== null)){const err32 = {instancePath:instancePath+"/children/" + i0+"/activitySummary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(typeof data11 === "string"){if(func1(data11) > 4000){const err33 = {instancePath:instancePath+"/children/" + i0+"/activitySummary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}}else {const err34 = {instancePath:instancePath+"/children/" + i0,schemaPath:"#/properties/children/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/children",schemaPath:"#/properties/children/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}}else {const err36 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}validate66.errors = vErrors;return errors === 0;}validate66.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema73 = {"type":"object","required":["schema","routeId","provider","requestedModel","fromModel","effectiveModel","reason"],"properties":{"schema":{"const":"paperclip.model.route_changed.v1"},"routeId":{"$ref":"#/$defs/id"},"provider":{"type":"string","minLength":1,"maxLength":160},"requestedModel":{"type":"string","minLength":1,"maxLength":240},"fromModel":{"type":["string","null"],"maxLength":240},"effectiveModel":{"type":"string","minLength":1,"maxLength":240},"reason":{"$ref":"#/$defs/shortText"}},"additionalProperties":false};function validate68(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate68.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.routeId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "routeId"},message:"must have required property '"+"routeId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.provider === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "provider"},message:"must have required property '"+"provider"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.requestedModel === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "requestedModel"},message:"must have required property '"+"requestedModel"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.fromModel === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "fromModel"},message:"must have required property '"+"fromModel"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.effectiveModel === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "effectiveModel"},message:"must have required property '"+"effectiveModel"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.reason === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reason"},message:"must have required property '"+"reason"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}for(const key0 in data){if(!(((((((key0 === "schema") || (key0 === "routeId")) || (key0 === "provider")) || (key0 === "requestedModel")) || (key0 === "fromModel")) || (key0 === "effectiveModel")) || (key0 === "reason"))){const err7 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.schema !== undefined){if("paperclip.model.route_changed.v1" !== data.schema){const err8 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.model.route_changed.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.routeId !== undefined){let data1 = data.routeId;if(typeof data1 === "string"){if(func1(data1) > 160){const err9 = {instancePath:instancePath+"/routeId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(func1(data1) < 1){const err10 = {instancePath:instancePath+"/routeId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(!pattern4.test(data1)){const err11 = {instancePath:instancePath+"/routeId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/routeId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.provider !== undefined){let data2 = data.provider;if(typeof data2 === "string"){if(func1(data2) > 160){const err13 = {instancePath:instancePath+"/provider",schemaPath:"#/properties/provider/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data2) < 1){const err14 = {instancePath:instancePath+"/provider",schemaPath:"#/properties/provider/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/provider",schemaPath:"#/properties/provider/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.requestedModel !== undefined){let data3 = data.requestedModel;if(typeof data3 === "string"){if(func1(data3) > 240){const err16 = {instancePath:instancePath+"/requestedModel",schemaPath:"#/properties/requestedModel/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(func1(data3) < 1){const err17 = {instancePath:instancePath+"/requestedModel",schemaPath:"#/properties/requestedModel/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}else {const err18 = {instancePath:instancePath+"/requestedModel",schemaPath:"#/properties/requestedModel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.fromModel !== undefined){let data4 = data.fromModel;if((typeof data4 !== "string") && (data4 !== null)){const err19 = {instancePath:instancePath+"/fromModel",schemaPath:"#/properties/fromModel/type",keyword:"type",params:{type: schema73.properties.fromModel.type},message:"must be string,null"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 240){const err20 = {instancePath:instancePath+"/fromModel",schemaPath:"#/properties/fromModel/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}if(data.effectiveModel !== undefined){let data5 = data.effectiveModel;if(typeof data5 === "string"){if(func1(data5) > 240){const err21 = {instancePath:instancePath+"/effectiveModel",schemaPath:"#/properties/effectiveModel/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data5) < 1){const err22 = {instancePath:instancePath+"/effectiveModel",schemaPath:"#/properties/effectiveModel/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}else {const err23 = {instancePath:instancePath+"/effectiveModel",schemaPath:"#/properties/effectiveModel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data.reason !== undefined){let data6 = data.reason;if(typeof data6 === "string"){if(func1(data6) > 4000){const err24 = {instancePath:instancePath+"/reason",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}else {const err25 = {instancePath:instancePath+"/reason",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}}else {const err26 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}validate68.errors = vErrors;return errors === 0;}validate68.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema76 = {"type":"object","required":["schema","verificationId","status","classes","buffering","summary"],"properties":{"schema":{"const":"paperclip.model.verification.v1"},"verificationId":{"$ref":"#/$defs/id"},"status":{"enum":["running","completed","failed"]},"classes":{"type":"array","maxItems":32,"items":{"type":"string","maxLength":160}},"buffering":{"type":"boolean"},"summary":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate70(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate70.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.verificationId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "verificationId"},message:"must have required property '"+"verificationId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.status === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.classes === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "classes"},message:"must have required property '"+"classes"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.buffering === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "buffering"},message:"must have required property '"+"buffering"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.summary === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "verificationId")) || (key0 === "status")) || (key0 === "classes")) || (key0 === "buffering")) || (key0 === "summary"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.model.verification.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.model.verification.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.verificationId !== undefined){let data1 = data.verificationId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/verificationId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/verificationId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/verificationId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/verificationId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.status !== undefined){let data2 = data.status;if(!(((data2 === "running") || (data2 === "completed")) || (data2 === "failed"))){const err12 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema76.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.classes !== undefined){let data3 = data.classes;if(Array.isArray(data3)){if(data3.length > 32){const err13 = {instancePath:instancePath+"/classes",schemaPath:"#/properties/classes/maxItems",keyword:"maxItems",params:{limit: 32},message:"must NOT have more than 32 items"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}const len0 = data3.length;for(let i0=0; i0 160){const err14 = {instancePath:instancePath+"/classes/" + i0,schemaPath:"#/properties/classes/items/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/classes/" + i0,schemaPath:"#/properties/classes/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/classes",schemaPath:"#/properties/classes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.buffering !== undefined){if(typeof data.buffering !== "boolean"){const err17 = {instancePath:instancePath+"/buffering",schemaPath:"#/properties/buffering/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.summary !== undefined){let data6 = data.summary;if((typeof data6 !== "string") && (data6 !== null)){const err18 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(typeof data6 === "string"){if(func1(data6) > 4000){const err19 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}}}else {const err20 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}validate70.errors = vErrors;return errors === 0;}validate70.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema79 = {"type":"object","required":["schema","compactionId","reason","preTokens","postTokens","sameSession"],"properties":{"schema":{"const":"paperclip.context.compacted.v1"},"compactionId":{"$ref":"#/$defs/id"},"reason":{"enum":["context_window","manual","provider","unknown"]},"preTokens":{"type":["integer","null"],"minimum":0},"postTokens":{"type":["integer","null"],"minimum":0},"sameSession":{"type":"boolean"}},"additionalProperties":false};function validate72(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate72.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.compactionId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "compactionId"},message:"must have required property '"+"compactionId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.reason === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reason"},message:"must have required property '"+"reason"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.preTokens === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "preTokens"},message:"must have required property '"+"preTokens"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.postTokens === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "postTokens"},message:"must have required property '"+"postTokens"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.sameSession === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sameSession"},message:"must have required property '"+"sameSession"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "compactionId")) || (key0 === "reason")) || (key0 === "preTokens")) || (key0 === "postTokens")) || (key0 === "sameSession"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.context.compacted.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.context.compacted.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.compactionId !== undefined){let data1 = data.compactionId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/compactionId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/compactionId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/compactionId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/compactionId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.reason !== undefined){let data2 = data.reason;if(!((((data2 === "context_window") || (data2 === "manual")) || (data2 === "provider")) || (data2 === "unknown"))){const err12 = {instancePath:instancePath+"/reason",schemaPath:"#/properties/reason/enum",keyword:"enum",params:{allowedValues: schema79.properties.reason.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.preTokens !== undefined){let data3 = data.preTokens;if((!(((typeof data3 == "number") && (!(data3 % 1) && !isNaN(data3))) && (isFinite(data3)))) && (data3 !== null)){const err13 = {instancePath:instancePath+"/preTokens",schemaPath:"#/properties/preTokens/type",keyword:"type",params:{type: schema79.properties.preTokens.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if((typeof data3 == "number") && (isFinite(data3))){if(data3 < 0 || isNaN(data3)){const err14 = {instancePath:instancePath+"/preTokens",schemaPath:"#/properties/preTokens/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}}if(data.postTokens !== undefined){let data4 = data.postTokens;if((!(((typeof data4 == "number") && (!(data4 % 1) && !isNaN(data4))) && (isFinite(data4)))) && (data4 !== null)){const err15 = {instancePath:instancePath+"/postTokens",schemaPath:"#/properties/postTokens/type",keyword:"type",params:{type: schema79.properties.postTokens.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if((typeof data4 == "number") && (isFinite(data4))){if(data4 < 0 || isNaN(data4)){const err16 = {instancePath:instancePath+"/postTokens",schemaPath:"#/properties/postTokens/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}if(data.sameSession !== undefined){if(typeof data.sameSession !== "boolean"){const err17 = {instancePath:instancePath+"/sameSession",schemaPath:"#/properties/sameSession/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}}else {const err18 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}validate72.errors = vErrors;return errors === 0;}validate72.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema81 = {"type":"object","required":["schema","artifactId","reference","mediaType","title"],"properties":{"schema":{"const":"paperclip.artifact.viewed.v1"},"artifactId":{"$ref":"#/$defs/id"},"reference":{"$ref":"#/$defs/safePath"},"mediaType":{"type":["string","null"],"maxLength":160},"title":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate74(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate74.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.artifactId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "artifactId"},message:"must have required property '"+"artifactId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.reference === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reference"},message:"must have required property '"+"reference"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.mediaType === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "mediaType"},message:"must have required property '"+"mediaType"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.title === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!(((((key0 === "schema") || (key0 === "artifactId")) || (key0 === "reference")) || (key0 === "mediaType")) || (key0 === "title"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema !== undefined){if("paperclip.artifact.viewed.v1" !== data.schema){const err6 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.artifact.viewed.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.artifactId !== undefined){let data1 = data.artifactId;if(typeof data1 === "string"){if(func1(data1) > 160){const err7 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func1(data1) < 1){const err8 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!pattern4.test(data1)){const err9 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.reference !== undefined){let data2 = data.reference;if((typeof data2 !== "string") && (data2 !== null)){const err11 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/type",keyword:"type",params:{type: schema57.type},message:"must be string,null"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(typeof data2 === "string"){if(func1(data2) > 4096){const err12 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/maxLength",keyword:"maxLength",params:{limit: 4096},message:"must NOT have more than 4096 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(!pattern18.test(data2)){const err13 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"+"\""};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}}if(data.mediaType !== undefined){let data3 = data.mediaType;if((typeof data3 !== "string") && (data3 !== null)){const err14 = {instancePath:instancePath+"/mediaType",schemaPath:"#/properties/mediaType/type",keyword:"type",params:{type: schema81.properties.mediaType.type},message:"must be string,null"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(typeof data3 === "string"){if(func1(data3) > 160){const err15 = {instancePath:instancePath+"/mediaType",schemaPath:"#/properties/mediaType/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}if(data.title !== undefined){let data4 = data.title;if((typeof data4 !== "string") && (data4 !== null)){const err16 = {instancePath:instancePath+"/title",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 4000){const err17 = {instancePath:instancePath+"/title",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}}}else {const err18 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}validate74.errors = vErrors;return errors === 0;}validate74.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema85 = {"type":"object","required":["schema","artifactId","status","reference","mediaType","registered","failure"],"properties":{"schema":{"const":"paperclip.artifact.generated.v1"},"artifactId":{"$ref":"#/$defs/id"},"status":{"enum":["running","completed","failed"]},"reference":{"$ref":"#/$defs/safePath"},"mediaType":{"type":["string","null"],"maxLength":160},"registered":{"type":"boolean"},"transparentBackground":{"type":["boolean","null"]},"failure":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate76(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate76.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.artifactId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "artifactId"},message:"must have required property '"+"artifactId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.status === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.reference === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reference"},message:"must have required property '"+"reference"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.mediaType === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "mediaType"},message:"must have required property '"+"mediaType"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.registered === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "registered"},message:"must have required property '"+"registered"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.failure === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "failure"},message:"must have required property '"+"failure"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema") || (key0 === "artifactId")) || (key0 === "status")) || (key0 === "reference")) || (key0 === "mediaType")) || (key0 === "registered")) || (key0 === "transparentBackground")) || (key0 === "failure"))){const err7 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.schema !== undefined){if("paperclip.artifact.generated.v1" !== data.schema){const err8 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.artifact.generated.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.artifactId !== undefined){let data1 = data.artifactId;if(typeof data1 === "string"){if(func1(data1) > 160){const err9 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(func1(data1) < 1){const err10 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(!pattern4.test(data1)){const err11 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/artifactId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.status !== undefined){let data2 = data.status;if(!(((data2 === "running") || (data2 === "completed")) || (data2 === "failed"))){const err13 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema85.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.reference !== undefined){let data3 = data.reference;if((typeof data3 !== "string") && (data3 !== null)){const err14 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/type",keyword:"type",params:{type: schema57.type},message:"must be string,null"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(typeof data3 === "string"){if(func1(data3) > 4096){const err15 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/maxLength",keyword:"maxLength",params:{limit: 4096},message:"must NOT have more than 4096 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(!pattern18.test(data3)){const err16 = {instancePath:instancePath+"/reference",schemaPath:"#/$defs/safePath/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).*$"+"\""};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}if(data.mediaType !== undefined){let data4 = data.mediaType;if((typeof data4 !== "string") && (data4 !== null)){const err17 = {instancePath:instancePath+"/mediaType",schemaPath:"#/properties/mediaType/type",keyword:"type",params:{type: schema85.properties.mediaType.type},message:"must be string,null"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(typeof data4 === "string"){if(func1(data4) > 160){const err18 = {instancePath:instancePath+"/mediaType",schemaPath:"#/properties/mediaType/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}}if(data.registered !== undefined){if(typeof data.registered !== "boolean"){const err19 = {instancePath:instancePath+"/registered",schemaPath:"#/properties/registered/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.transparentBackground !== undefined){let data6 = data.transparentBackground;if((typeof data6 !== "boolean") && (data6 !== null)){const err20 = {instancePath:instancePath+"/transparentBackground",schemaPath:"#/properties/transparentBackground/type",keyword:"type",params:{type: schema85.properties.transparentBackground.type},message:"must be boolean,null"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.failure !== undefined){let data7 = data.failure;if((typeof data7 !== "string") && (data7 !== null)){const err21 = {instancePath:instancePath+"/failure",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(typeof data7 === "string"){if(func1(data7) > 4000){const err22 = {instancePath:instancePath+"/failure",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}}}else {const err23 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}validate76.errors = vErrors;return errors === 0;}validate76.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema89 = {"type":"object","required":["schema","reviewId","state","scope"],"properties":{"schema":{"const":"paperclip.review.mode_changed.v1"},"reviewId":{"$ref":"#/$defs/id"},"state":{"enum":["entered","exited"]},"scope":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate78(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate78.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.reviewId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reviewId"},message:"must have required property '"+"reviewId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.state === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "state"},message:"must have required property '"+"state"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.scope === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "scope"},message:"must have required property '"+"scope"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data){if(!((((key0 === "schema") || (key0 === "reviewId")) || (key0 === "state")) || (key0 === "scope"))){const err4 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.schema !== undefined){if("paperclip.review.mode_changed.v1" !== data.schema){const err5 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.review.mode_changed.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.reviewId !== undefined){let data1 = data.reviewId;if(typeof data1 === "string"){if(func1(data1) > 160){const err6 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(func1(data1) < 1){const err7 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(!pattern4.test(data1)){const err8 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.state !== undefined){let data2 = data.state;if(!((data2 === "entered") || (data2 === "exited"))){const err10 = {instancePath:instancePath+"/state",schemaPath:"#/properties/state/enum",keyword:"enum",params:{allowedValues: schema89.properties.state.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.scope !== undefined){let data3 = data.scope;if((typeof data3 !== "string") && (data3 !== null)){const err11 = {instancePath:instancePath+"/scope",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(typeof data3 === "string"){if(func1(data3) > 4000){const err12 = {instancePath:instancePath+"/scope",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}}else {const err13 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}validate78.errors = vErrors;return errors === 0;}validate78.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema92 = {"type":"object","required":["schema","hookId","event","scope","status","blocking","durationMs","summary"],"properties":{"schema":{"const":"paperclip.hook.v1"},"hookId":{"$ref":"#/$defs/id"},"event":{"type":"string","minLength":1,"maxLength":160},"scope":{"type":"string","minLength":1,"maxLength":160},"status":{"enum":["running","completed","failed","cancelled"]},"blocking":{"type":"boolean"},"durationMs":{"type":["integer","null"],"minimum":0},"summary":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate80(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate80.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.hookId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "hookId"},message:"must have required property '"+"hookId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.event === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "event"},message:"must have required property '"+"event"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.scope === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "scope"},message:"must have required property '"+"scope"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.status === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.blocking === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "blocking"},message:"must have required property '"+"blocking"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.durationMs === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "durationMs"},message:"must have required property '"+"durationMs"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.summary === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema") || (key0 === "hookId")) || (key0 === "event")) || (key0 === "scope")) || (key0 === "status")) || (key0 === "blocking")) || (key0 === "durationMs")) || (key0 === "summary"))){const err8 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.schema !== undefined){if("paperclip.hook.v1" !== data.schema){const err9 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.hook.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.hookId !== undefined){let data1 = data.hookId;if(typeof data1 === "string"){if(func1(data1) > 160){const err10 = {instancePath:instancePath+"/hookId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(func1(data1) < 1){const err11 = {instancePath:instancePath+"/hookId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(!pattern4.test(data1)){const err12 = {instancePath:instancePath+"/hookId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}else {const err13 = {instancePath:instancePath+"/hookId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.event !== undefined){let data2 = data.event;if(typeof data2 === "string"){if(func1(data2) > 160){const err14 = {instancePath:instancePath+"/event",schemaPath:"#/properties/event/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(func1(data2) < 1){const err15 = {instancePath:instancePath+"/event",schemaPath:"#/properties/event/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}else {const err16 = {instancePath:instancePath+"/event",schemaPath:"#/properties/event/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.scope !== undefined){let data3 = data.scope;if(typeof data3 === "string"){if(func1(data3) > 160){const err17 = {instancePath:instancePath+"/scope",schemaPath:"#/properties/scope/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(func1(data3) < 1){const err18 = {instancePath:instancePath+"/scope",schemaPath:"#/properties/scope/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/scope",schemaPath:"#/properties/scope/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.status !== undefined){let data4 = data.status;if(!((((data4 === "running") || (data4 === "completed")) || (data4 === "failed")) || (data4 === "cancelled"))){const err20 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema92.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.blocking !== undefined){if(typeof data.blocking !== "boolean"){const err21 = {instancePath:instancePath+"/blocking",schemaPath:"#/properties/blocking/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data.durationMs !== undefined){let data6 = data.durationMs;if((!(((typeof data6 == "number") && (!(data6 % 1) && !isNaN(data6))) && (isFinite(data6)))) && (data6 !== null)){const err22 = {instancePath:instancePath+"/durationMs",schemaPath:"#/properties/durationMs/type",keyword:"type",params:{type: schema92.properties.durationMs.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if((typeof data6 == "number") && (isFinite(data6))){if(data6 < 0 || isNaN(data6)){const err23 = {instancePath:instancePath+"/durationMs",schemaPath:"#/properties/durationMs/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}if(data.summary !== undefined){let data7 = data.summary;if((typeof data7 !== "string") && (data7 !== null)){const err24 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(typeof data7 === "string"){if(func1(data7) > 4000){const err25 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}}}else {const err26 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}validate80.errors = vErrors;return errors === 0;}validate80.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema95 = {"type":"object","required":["schema","citationId","messageItemId","label","available","reference"],"properties":{"schema":{"const":"paperclip.memory.citation.v1"},"citationId":{"$ref":"#/$defs/id"},"messageItemId":{"$ref":"#/$defs/id"},"label":{"$ref":"#/$defs/shortText"},"available":{"type":"boolean"},"reference":{"type":["string","null"],"maxLength":4096}},"additionalProperties":false};function validate82(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate82.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.citationId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "citationId"},message:"must have required property '"+"citationId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.messageItemId === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "messageItemId"},message:"must have required property '"+"messageItemId"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.label === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.available === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "available"},message:"must have required property '"+"available"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.reference === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reference"},message:"must have required property '"+"reference"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "citationId")) || (key0 === "messageItemId")) || (key0 === "label")) || (key0 === "available")) || (key0 === "reference"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.memory.citation.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.memory.citation.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.citationId !== undefined){let data1 = data.citationId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/citationId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/citationId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/citationId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/citationId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.messageItemId !== undefined){let data2 = data.messageItemId;if(typeof data2 === "string"){if(func1(data2) > 160){const err12 = {instancePath:instancePath+"/messageItemId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(func1(data2) < 1){const err13 = {instancePath:instancePath+"/messageItemId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(!pattern4.test(data2)){const err14 = {instancePath:instancePath+"/messageItemId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/messageItemId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.label !== undefined){let data3 = data.label;if(typeof data3 === "string"){if(func1(data3) > 4000){const err16 = {instancePath:instancePath+"/label",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}else {const err17 = {instancePath:instancePath+"/label",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.available !== undefined){if(typeof data.available !== "boolean"){const err18 = {instancePath:instancePath+"/available",schemaPath:"#/properties/available/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.reference !== undefined){let data5 = data.reference;if((typeof data5 !== "string") && (data5 !== null)){const err19 = {instancePath:instancePath+"/reference",schemaPath:"#/properties/reference/type",keyword:"type",params:{type: schema95.properties.reference.type},message:"must be string,null"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data5 === "string"){if(func1(data5) > 4096){const err20 = {instancePath:instancePath+"/reference",schemaPath:"#/properties/reference/maxLength",keyword:"maxLength",params:{limit: 4096},message:"must NOT have more than 4096 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}}else {const err21 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}validate82.errors = vErrors;return errors === 0;}validate82.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema99 = {"type":"object","required":["schema","reviewId","targetExecutionId","status","decision","summary"],"properties":{"schema":{"const":"paperclip.safety.review.v1"},"reviewId":{"$ref":"#/$defs/id"},"targetExecutionId":{"anyOf":[{"$ref":"#/$defs/id"},{"type":"null"}]},"status":{"enum":["running","completed","failed"]},"decision":{"enum":["pending","allowed","denied","user_required","unknown"]},"summary":{"$ref":"#/$defs/nullableShortText"}},"additionalProperties":false};function validate84(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate84.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.reviewId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reviewId"},message:"must have required property '"+"reviewId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.targetExecutionId === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "targetExecutionId"},message:"must have required property '"+"targetExecutionId"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.status === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.decision === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "decision"},message:"must have required property '"+"decision"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.summary === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "reviewId")) || (key0 === "targetExecutionId")) || (key0 === "status")) || (key0 === "decision")) || (key0 === "summary"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.safety.review.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.safety.review.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.reviewId !== undefined){let data1 = data.reviewId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/reviewId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.targetExecutionId !== undefined){let data2 = data.targetExecutionId;const _errs7 = errors;let valid2 = false;const _errs8 = errors;if(typeof data2 === "string"){if(func1(data2) > 160){const err12 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(func1(data2) < 1){const err13 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(!pattern4.test(data2)){const err14 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}var _valid0 = _errs8 === errors;valid2 = valid2 || _valid0;const _errs11 = errors;if(data2 !== null){const err16 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/properties/targetExecutionId/anyOf/1/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}var _valid0 = _errs11 === errors;valid2 = valid2 || _valid0;if(!valid2){const err17 = {instancePath:instancePath+"/targetExecutionId",schemaPath:"#/properties/targetExecutionId/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}else {errors = _errs7;if(vErrors !== null){if(_errs7){vErrors.length = _errs7;}else {vErrors = null;}}}}if(data.status !== undefined){let data3 = data.status;if(!(((data3 === "running") || (data3 === "completed")) || (data3 === "failed"))){const err18 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema99.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.decision !== undefined){let data4 = data.decision;if(!(((((data4 === "pending") || (data4 === "allowed")) || (data4 === "denied")) || (data4 === "user_required")) || (data4 === "unknown"))){const err19 = {instancePath:instancePath+"/decision",schemaPath:"#/properties/decision/enum",keyword:"enum",params:{allowedValues: schema99.properties.decision.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.summary !== undefined){let data5 = data.summary;if((typeof data5 !== "string") && (data5 !== null)){const err20 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/type",keyword:"type",params:{type: schema51.type},message:"must be string,null"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(typeof data5 === "string"){if(func1(data5) > 4000){const err21 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/nullableShortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}}}else {const err22 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}validate84.errors = vErrors;return errors === 0;}validate84.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema103 = {"type":"object","required":["schema","executionId","origin","inputClass","byteCount"],"properties":{"schema":{"const":"paperclip.terminal.input_sent.v1"},"executionId":{"$ref":"#/$defs/id"},"origin":{"enum":["agent","user","system"]},"inputClass":{"enum":["text","control","eof"]},"byteCount":{"type":"integer","minimum":0}},"additionalProperties":false};function validate86(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate86.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.executionId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "executionId"},message:"must have required property '"+"executionId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.origin === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "origin"},message:"must have required property '"+"origin"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.inputClass === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "inputClass"},message:"must have required property '"+"inputClass"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.byteCount === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "byteCount"},message:"must have required property '"+"byteCount"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!(((((key0 === "schema") || (key0 === "executionId")) || (key0 === "origin")) || (key0 === "inputClass")) || (key0 === "byteCount"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema !== undefined){if("paperclip.terminal.input_sent.v1" !== data.schema){const err6 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.terminal.input_sent.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.executionId !== undefined){let data1 = data.executionId;if(typeof data1 === "string"){if(func1(data1) > 160){const err7 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func1(data1) < 1){const err8 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!pattern4.test(data1)){const err9 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/executionId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.origin !== undefined){let data2 = data.origin;if(!(((data2 === "agent") || (data2 === "user")) || (data2 === "system"))){const err11 = {instancePath:instancePath+"/origin",schemaPath:"#/properties/origin/enum",keyword:"enum",params:{allowedValues: schema103.properties.origin.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.inputClass !== undefined){let data3 = data.inputClass;if(!(((data3 === "text") || (data3 === "control")) || (data3 === "eof"))){const err12 = {instancePath:instancePath+"/inputClass",schemaPath:"#/properties/inputClass/enum",keyword:"enum",params:{allowedValues: schema103.properties.inputClass.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.byteCount !== undefined){let data4 = data.byteCount;if(!(((typeof data4 == "number") && (!(data4 % 1) && !isNaN(data4))) && (isFinite(data4)))){const err13 = {instancePath:instancePath+"/byteCount",schemaPath:"#/properties/byteCount/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if((typeof data4 == "number") && (isFinite(data4))){if(data4 < 0 || isNaN(data4)){const err14 = {instancePath:instancePath+"/byteCount",schemaPath:"#/properties/byteCount/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}}}else {const err15 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}validate86.errors = vErrors;return errors === 0;}validate86.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema105 = {"type":"object","required":["schema","waitId","reason","status","plannedDurationMs","elapsedDurationMs"],"properties":{"schema":{"const":"paperclip.wait.v1"},"waitId":{"$ref":"#/$defs/id"},"reason":{"enum":["timer","provider","rate_limit","unknown"]},"status":{"enum":["running","completed","interrupted","cancelled"]},"plannedDurationMs":{"type":["integer","null"],"minimum":0},"elapsedDurationMs":{"type":["integer","null"],"minimum":0}},"additionalProperties":false};function validate88(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate88.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.waitId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "waitId"},message:"must have required property '"+"waitId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.reason === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reason"},message:"must have required property '"+"reason"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.status === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.plannedDurationMs === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "plannedDurationMs"},message:"must have required property '"+"plannedDurationMs"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.elapsedDurationMs === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "elapsedDurationMs"},message:"must have required property '"+"elapsedDurationMs"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!((((((key0 === "schema") || (key0 === "waitId")) || (key0 === "reason")) || (key0 === "status")) || (key0 === "plannedDurationMs")) || (key0 === "elapsedDurationMs"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema !== undefined){if("paperclip.wait.v1" !== data.schema){const err7 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.wait.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.waitId !== undefined){let data1 = data.waitId;if(typeof data1 === "string"){if(func1(data1) > 160){const err8 = {instancePath:instancePath+"/waitId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(func1(data1) < 1){const err9 = {instancePath:instancePath+"/waitId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(!pattern4.test(data1)){const err10 = {instancePath:instancePath+"/waitId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/waitId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.reason !== undefined){let data2 = data.reason;if(!((((data2 === "timer") || (data2 === "provider")) || (data2 === "rate_limit")) || (data2 === "unknown"))){const err12 = {instancePath:instancePath+"/reason",schemaPath:"#/properties/reason/enum",keyword:"enum",params:{allowedValues: schema105.properties.reason.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.status !== undefined){let data3 = data.status;if(!((((data3 === "running") || (data3 === "completed")) || (data3 === "interrupted")) || (data3 === "cancelled"))){const err13 = {instancePath:instancePath+"/status",schemaPath:"#/properties/status/enum",keyword:"enum",params:{allowedValues: schema105.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.plannedDurationMs !== undefined){let data4 = data.plannedDurationMs;if((!(((typeof data4 == "number") && (!(data4 % 1) && !isNaN(data4))) && (isFinite(data4)))) && (data4 !== null)){const err14 = {instancePath:instancePath+"/plannedDurationMs",schemaPath:"#/properties/plannedDurationMs/type",keyword:"type",params:{type: schema105.properties.plannedDurationMs.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if((typeof data4 == "number") && (isFinite(data4))){if(data4 < 0 || isNaN(data4)){const err15 = {instancePath:instancePath+"/plannedDurationMs",schemaPath:"#/properties/plannedDurationMs/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}if(data.elapsedDurationMs !== undefined){let data5 = data.elapsedDurationMs;if((!(((typeof data5 == "number") && (!(data5 % 1) && !isNaN(data5))) && (isFinite(data5)))) && (data5 !== null)){const err16 = {instancePath:instancePath+"/elapsedDurationMs",schemaPath:"#/properties/elapsedDurationMs/type",keyword:"type",params:{type: schema105.properties.elapsedDurationMs.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if((typeof data5 == "number") && (isFinite(data5))){if(data5 < 0 || isNaN(data5)){const err17 = {instancePath:instancePath+"/elapsedDurationMs",schemaPath:"#/properties/elapsedDurationMs/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}}}else {const err18 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}validate88.errors = vErrors;return errors === 0;}validate88.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema107 = {"type":"object","required":["schema","noticeId","severity","category","scope","recoverable","userActionable","summary"],"properties":{"schema":{"const":"paperclip.provider.notice.v1"},"noticeId":{"$ref":"#/$defs/id"},"severity":{"enum":["info","warning","error"]},"category":{"type":"string","minLength":1,"maxLength":160},"scope":{"enum":["turn","session","environment","account"]},"recoverable":{"type":"boolean"},"userActionable":{"type":"boolean"},"summary":{"$ref":"#/$defs/shortText"},"provenance":{"type":"object","required":["method","eventType","sessionId","turnId"],"properties":{"method":{"type":"string","maxLength":160},"eventType":{"type":"string","maxLength":160},"sessionId":{"type":"string","maxLength":240},"turnId":{"type":"string","maxLength":240},"agentId":{"type":"string","maxLength":240},"timestamp":{"type":"string","maxLength":80}},"additionalProperties":false},"details":{"type":"array","maxItems":64,"items":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","minLength":1,"maxLength":160},"value":{"type":"string","maxLength":4000}},"additionalProperties":false}}},"additionalProperties":false};function validate90(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate90.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.noticeId === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "noticeId"},message:"must have required property '"+"noticeId"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.severity === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "severity"},message:"must have required property '"+"severity"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.category === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "category"},message:"must have required property '"+"category"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.scope === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "scope"},message:"must have required property '"+"scope"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.recoverable === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "recoverable"},message:"must have required property '"+"recoverable"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.userActionable === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "userActionable"},message:"must have required property '"+"userActionable"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.summary === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}for(const key0 in data){if(!(func58.call(schema107.properties, key0))){const err8 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.schema !== undefined){if("paperclip.provider.notice.v1" !== data.schema){const err9 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.provider.notice.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.noticeId !== undefined){let data1 = data.noticeId;if(typeof data1 === "string"){if(func1(data1) > 160){const err10 = {instancePath:instancePath+"/noticeId",schemaPath:"#/$defs/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(func1(data1) < 1){const err11 = {instancePath:instancePath+"/noticeId",schemaPath:"#/$defs/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(!pattern4.test(data1)){const err12 = {instancePath:instancePath+"/noticeId",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}else {const err13 = {instancePath:instancePath+"/noticeId",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.severity !== undefined){let data2 = data.severity;if(!(((data2 === "info") || (data2 === "warning")) || (data2 === "error"))){const err14 = {instancePath:instancePath+"/severity",schemaPath:"#/properties/severity/enum",keyword:"enum",params:{allowedValues: schema107.properties.severity.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.category !== undefined){let data3 = data.category;if(typeof data3 === "string"){if(func1(data3) > 160){const err15 = {instancePath:instancePath+"/category",schemaPath:"#/properties/category/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(func1(data3) < 1){const err16 = {instancePath:instancePath+"/category",schemaPath:"#/properties/category/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}else {const err17 = {instancePath:instancePath+"/category",schemaPath:"#/properties/category/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.scope !== undefined){let data4 = data.scope;if(!((((data4 === "turn") || (data4 === "session")) || (data4 === "environment")) || (data4 === "account"))){const err18 = {instancePath:instancePath+"/scope",schemaPath:"#/properties/scope/enum",keyword:"enum",params:{allowedValues: schema107.properties.scope.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.recoverable !== undefined){if(typeof data.recoverable !== "boolean"){const err19 = {instancePath:instancePath+"/recoverable",schemaPath:"#/properties/recoverable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.userActionable !== undefined){if(typeof data.userActionable !== "boolean"){const err20 = {instancePath:instancePath+"/userActionable",schemaPath:"#/properties/userActionable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.summary !== undefined){let data7 = data.summary;if(typeof data7 === "string"){if(func1(data7) > 4000){const err21 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/shortText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/summary",schemaPath:"#/$defs/shortText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data.provenance !== undefined){let data8 = data.provenance;if(data8 && typeof data8 == "object" && !Array.isArray(data8)){if(data8.method === undefined){const err23 = {instancePath:instancePath+"/provenance",schemaPath:"#/properties/provenance/required",keyword:"required",params:{missingProperty: "method"},message:"must have required property '"+"method"+"'"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(data8.eventType === undefined){const err24 = {instancePath:instancePath+"/provenance",schemaPath:"#/properties/provenance/required",keyword:"required",params:{missingProperty: "eventType"},message:"must have required property '"+"eventType"+"'"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(data8.sessionId === undefined){const err25 = {instancePath:instancePath+"/provenance",schemaPath:"#/properties/provenance/required",keyword:"required",params:{missingProperty: "sessionId"},message:"must have required property '"+"sessionId"+"'"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(data8.turnId === undefined){const err26 = {instancePath:instancePath+"/provenance",schemaPath:"#/properties/provenance/required",keyword:"required",params:{missingProperty: "turnId"},message:"must have required property '"+"turnId"+"'"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}for(const key1 in data8){if(!((((((key1 === "method") || (key1 === "eventType")) || (key1 === "sessionId")) || (key1 === "turnId")) || (key1 === "agentId")) || (key1 === "timestamp"))){const err27 = {instancePath:instancePath+"/provenance",schemaPath:"#/properties/provenance/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data8.method !== undefined){let data9 = data8.method;if(typeof data9 === "string"){if(func1(data9) > 160){const err28 = {instancePath:instancePath+"/provenance/method",schemaPath:"#/properties/provenance/properties/method/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}else {const err29 = {instancePath:instancePath+"/provenance/method",schemaPath:"#/properties/provenance/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data8.eventType !== undefined){let data10 = data8.eventType;if(typeof data10 === "string"){if(func1(data10) > 160){const err30 = {instancePath:instancePath+"/provenance/eventType",schemaPath:"#/properties/provenance/properties/eventType/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}else {const err31 = {instancePath:instancePath+"/provenance/eventType",schemaPath:"#/properties/provenance/properties/eventType/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}if(data8.sessionId !== undefined){let data11 = data8.sessionId;if(typeof data11 === "string"){if(func1(data11) > 240){const err32 = {instancePath:instancePath+"/provenance/sessionId",schemaPath:"#/properties/provenance/properties/sessionId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/provenance/sessionId",schemaPath:"#/properties/provenance/properties/sessionId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data8.turnId !== undefined){let data12 = data8.turnId;if(typeof data12 === "string"){if(func1(data12) > 240){const err34 = {instancePath:instancePath+"/provenance/turnId",schemaPath:"#/properties/provenance/properties/turnId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}else {const err35 = {instancePath:instancePath+"/provenance/turnId",schemaPath:"#/properties/provenance/properties/turnId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data8.agentId !== undefined){let data13 = data8.agentId;if(typeof data13 === "string"){if(func1(data13) > 240){const err36 = {instancePath:instancePath+"/provenance/agentId",schemaPath:"#/properties/provenance/properties/agentId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}else {const err37 = {instancePath:instancePath+"/provenance/agentId",schemaPath:"#/properties/provenance/properties/agentId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data8.timestamp !== undefined){let data14 = data8.timestamp;if(typeof data14 === "string"){if(func1(data14) > 80){const err38 = {instancePath:instancePath+"/provenance/timestamp",schemaPath:"#/properties/provenance/properties/timestamp/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}else {const err39 = {instancePath:instancePath+"/provenance/timestamp",schemaPath:"#/properties/provenance/properties/timestamp/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}}else {const err40 = {instancePath:instancePath+"/provenance",schemaPath:"#/properties/provenance/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data.details !== undefined){let data15 = data.details;if(Array.isArray(data15)){if(data15.length > 64){const err41 = {instancePath:instancePath+"/details",schemaPath:"#/properties/details/maxItems",keyword:"maxItems",params:{limit: 64},message:"must NOT have more than 64 items"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}const len0 = data15.length;for(let i0=0; i0 160){const err45 = {instancePath:instancePath+"/details/" + i0+"/name",schemaPath:"#/properties/details/items/properties/name/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func1(data17) < 1){const err46 = {instancePath:instancePath+"/details/" + i0+"/name",schemaPath:"#/properties/details/items/properties/name/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/details/" + i0+"/name",schemaPath:"#/properties/details/items/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data16.value !== undefined){let data18 = data16.value;if(typeof data18 === "string"){if(func1(data18) > 4000){const err48 = {instancePath:instancePath+"/details/" + i0+"/value",schemaPath:"#/properties/details/items/properties/value/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}else {const err49 = {instancePath:instancePath+"/details/" + i0+"/value",schemaPath:"#/properties/details/items/properties/value/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}}else {const err50 = {instancePath:instancePath+"/details/" + i0,schemaPath:"#/properties/details/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}else {const err51 = {instancePath:instancePath+"/details",schemaPath:"#/properties/details/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}}else {const err52 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}validate90.errors = vErrors;return errors === 0;}validate90.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema174 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json","title":"PRP provider-neutral semantic tool envelope","type":"object","required":["schema","schemaVersion","phase","operationId","callId","correlation","idempotencyKey","content"],"properties":{"schema":{"const":"paperclip.prp.semantic_tool.v1"},"schemaVersion":{"const":1},"phase":{"enum":["input","result","reconciled"]},"operationId":{"$ref":"#/$defs/stableId"},"callId":{"$ref":"#/$defs/stableId"},"correlation":{"type":"object","required":["runId","normalizedSessionId","turnId","itemId"],"properties":{"runId":{"$ref":"#/$defs/stableId"},"normalizedSessionId":{"$ref":"#/$defs/stableId"},"turnId":{"$ref":"#/$defs/stableId"},"itemId":{"$ref":"#/$defs/stableId"},"requestId":{"$ref":"#/$defs/stableId"}},"additionalProperties":true},"idempotencyKey":{"type":["string","null"],"minLength":1,"maxLength":240},"content":{"$ref":"#/$defs/safeContent"},"outcome":{"enum":["succeeded","denied","conflict","duplicate","unavailable","failed"]},"code":{"$ref":"#/$defs/stableCode"},"retryable":{"type":"boolean"},"authorizationBoundary":{"enum":["company","actor","active_task","grant","governed_action","lock","revision"]},"operationReceiptId":{"$ref":"#/$defs/stableId"},"auditReceiptId":{"$ref":"#/$defs/stableId"},"currentRevision":{"oneOf":[{"type":"integer","minimum":0},{"$ref":"#/$defs/stableId"}]},"duplicateOfReceiptId":{"$ref":"#/$defs/stableId"},"artifactRefs":{"type":"array","items":{"allOf":[{"$ref":"#/$defs/safeReference"},{"type":"object","properties":{"kind":{"enum":["artifact","work_product"]}}}]},"uniqueItems":true},"targets":{"type":"array","items":{"$ref":"#/$defs/immutableTarget"},"uniqueItems":true},"causalRefs":{"type":"array","items":{"$ref":"#/$defs/safeReference"},"uniqueItems":true}},"allOf":[{"if":{"properties":{"phase":{"enum":["result","reconciled"]}},"required":["phase"]},"then":{"required":["outcome","code","retryable","authorizationBoundary","operationReceiptId"]}}],"additionalProperties":true,"$defs":{"stableId":{"type":"string","minLength":1,"maxLength":240,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"},"stableCode":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z][a-z0-9_.:-]*$"},"safeContent":{"type":"object","required":["digest","redactionDisposition","references"],"properties":{"digest":{"type":"string","pattern":"^sha256:[a-f0-9]{64}$"},"redactionDisposition":{"enum":["digest_only","allowlisted_references","redacted"]},"references":{"type":"array","items":{"$ref":"#/$defs/safeReference"},"uniqueItems":true}},"additionalProperties":true},"safeReference":{"type":"object","required":["kind","id"],"properties":{"kind":{"enum":["task","document_revision","interaction","approval","decision","artifact","work_product","wake","monitor","audit","operation"]},"id":{"$ref":"#/$defs/stableId"}},"additionalProperties":true},"immutableTarget":{"type":"object","required":["kind","id","immutable"],"properties":{"kind":{"enum":["document_revision","interaction","approval","decision"]},"id":{"$ref":"#/$defs/stableId"},"immutable":{"const":true},"revisionId":{"$ref":"#/$defs/stableId"},"decisionId":{"$ref":"#/$defs/stableId"}},"additionalProperties":true}}};const schema175 = {"type":"string","minLength":1,"maxLength":240,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"};const schema185 = {"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z][a-z0-9_.:-]*$"};const pattern14 = new RegExp("^[a-z][a-z0-9_.:-]*$", "u");const schema182 = {"type":"object","required":["digest","redactionDisposition","references"],"properties":{"digest":{"type":"string","pattern":"^sha256:[a-f0-9]{64}$"},"redactionDisposition":{"enum":["digest_only","allowlisted_references","redacted"]},"references":{"type":"array","items":{"$ref":"#/$defs/safeReference"},"uniqueItems":true}},"additionalProperties":true};const schema183 = {"type":"object","required":["kind","id"],"properties":{"kind":{"enum":["task","document_revision","interaction","approval","decision","artifact","work_product","wake","monitor","audit","operation"]},"id":{"$ref":"#/$defs/stableId"}},"additionalProperties":true};function validate94(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate94.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.kind === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.id === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.kind !== undefined){let data0 = data.kind;if(!(((((((((((data0 === "task") || (data0 === "document_revision")) || (data0 === "interaction")) || (data0 === "approval")) || (data0 === "decision")) || (data0 === "artifact")) || (data0 === "work_product")) || (data0 === "wake")) || (data0 === "monitor")) || (data0 === "audit")) || (data0 === "operation"))){const err2 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema183.properties.kind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.id !== undefined){let data1 = data.id;if(typeof data1 === "string"){if(func1(data1) > 240){const err3 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(func1(data1) < 1){const err4 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(!pattern4.test(data1)){const err5 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}else {const err6 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}else {const err7 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}validate94.errors = vErrors;return errors === 0;}validate94.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate93(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate93.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.digest === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "digest"},message:"must have required property '"+"digest"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.redactionDisposition === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "redactionDisposition"},message:"must have required property '"+"redactionDisposition"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.references === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "references"},message:"must have required property '"+"references"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.digest !== undefined){let data0 = data.digest;if(typeof data0 === "string"){if(!pattern19.test(data0)){const err3 = {instancePath:instancePath+"/digest",schemaPath:"#/properties/digest/pattern",keyword:"pattern",params:{pattern: "^sha256:[a-f0-9]{64}$"},message:"must match pattern \""+"^sha256:[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}else {const err4 = {instancePath:instancePath+"/digest",schemaPath:"#/properties/digest/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.redactionDisposition !== undefined){let data1 = data.redactionDisposition;if(!(((data1 === "digest_only") || (data1 === "allowlisted_references")) || (data1 === "redacted"))){const err5 = {instancePath:instancePath+"/redactionDisposition",schemaPath:"#/properties/redactionDisposition/enum",keyword:"enum",params:{allowedValues: schema182.properties.redactionDisposition.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.references !== undefined){let data2 = data.references;if(Array.isArray(data2)){const len0 = data2.length;for(let i0=0; i0 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data2[i1], data2[j0])){const err6 = {instancePath:instancePath+"/references",schemaPath:"#/properties/references/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;break outer0;}}}}}else {const err7 = {instancePath:instancePath+"/references",schemaPath:"#/properties/references/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}}else {const err8 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}validate93.errors = vErrors;return errors === 0;}validate93.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema190 = {"type":"object","required":["kind","id","immutable"],"properties":{"kind":{"enum":["document_revision","interaction","approval","decision"]},"id":{"$ref":"#/$defs/stableId"},"immutable":{"const":true},"revisionId":{"$ref":"#/$defs/stableId"},"decisionId":{"$ref":"#/$defs/stableId"}},"additionalProperties":true};function validate98(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate98.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.kind === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.id === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.immutable === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "immutable"},message:"must have required property '"+"immutable"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.kind !== undefined){let data0 = data.kind;if(!((((data0 === "document_revision") || (data0 === "interaction")) || (data0 === "approval")) || (data0 === "decision"))){const err3 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema190.properties.kind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.id !== undefined){let data1 = data.id;if(typeof data1 === "string"){if(func1(data1) > 240){const err4 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(func1(data1) < 1){const err5 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(!pattern4.test(data1)){const err6 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}else {const err7 = {instancePath:instancePath+"/id",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.immutable !== undefined){if(true !== data.immutable){const err8 = {instancePath:instancePath+"/immutable",schemaPath:"#/properties/immutable/const",keyword:"const",params:{allowedValue: true},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.revisionId !== undefined){let data3 = data.revisionId;if(typeof data3 === "string"){if(func1(data3) > 240){const err9 = {instancePath:instancePath+"/revisionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(func1(data3) < 1){const err10 = {instancePath:instancePath+"/revisionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(!pattern4.test(data3)){const err11 = {instancePath:instancePath+"/revisionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/revisionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.decisionId !== undefined){let data4 = data.decisionId;if(typeof data4 === "string"){if(func1(data4) > 240){const err13 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data4) < 1){const err14 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(!pattern4.test(data4)){const err15 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}else {const err16 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate98.errors = vErrors;return errors === 0;}validate98.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate92(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate92.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){let missing0;if((data.phase === undefined) && (missing0 = "phase")){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}else {if(data.phase !== undefined){let data0 = data.phase;if(!((data0 === "result") || (data0 === "reconciled"))){const err1 = {};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.outcome === undefined){const err2 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "outcome"},message:"must have required property '"+"outcome"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.code === undefined){const err3 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "code"},message:"must have required property '"+"code"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.retryable === undefined){const err4 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "retryable"},message:"must have required property '"+"retryable"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.authorizationBoundary === undefined){const err5 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "authorizationBoundary"},message:"must have required property '"+"authorizationBoundary"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.operationReceiptId === undefined){const err6 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "operationReceiptId"},message:"must have required property '"+"operationReceiptId"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}var _valid0 = _errs5 === errors;valid1 = _valid0;}if(!valid1){const err7 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data.schemaVersion === undefined){const err9 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.phase === undefined){const err10 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "phase"},message:"must have required property '"+"phase"+"'"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(data.operationId === undefined){const err11 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "operationId"},message:"must have required property '"+"operationId"+"'"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(data.callId === undefined){const err12 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "callId"},message:"must have required property '"+"callId"+"'"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(data.correlation === undefined){const err13 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "correlation"},message:"must have required property '"+"correlation"+"'"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(data.idempotencyKey === undefined){const err14 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "idempotencyKey"},message:"must have required property '"+"idempotencyKey"+"'"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(data.content === undefined){const err15 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "content"},message:"must have required property '"+"content"+"'"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(data.schema !== undefined){if("paperclip.prp.semantic_tool.v1" !== data.schema){const err16 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.semantic_tool.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.schemaVersion !== undefined){if(1 !== data.schemaVersion){const err17 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.phase !== undefined){let data3 = data.phase;if(!(((data3 === "input") || (data3 === "result")) || (data3 === "reconciled"))){const err18 = {instancePath:instancePath+"/phase",schemaPath:"#/properties/phase/enum",keyword:"enum",params:{allowedValues: schema174.properties.phase.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.operationId !== undefined){let data4 = data.operationId;if(typeof data4 === "string"){if(func1(data4) > 240){const err19 = {instancePath:instancePath+"/operationId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(func1(data4) < 1){const err20 = {instancePath:instancePath+"/operationId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(!pattern4.test(data4)){const err21 = {instancePath:instancePath+"/operationId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/operationId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data.callId !== undefined){let data5 = data.callId;if(typeof data5 === "string"){if(func1(data5) > 240){const err23 = {instancePath:instancePath+"/callId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(func1(data5) < 1){const err24 = {instancePath:instancePath+"/callId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(!pattern4.test(data5)){const err25 = {instancePath:instancePath+"/callId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/callId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data.correlation !== undefined){let data6 = data.correlation;if(data6 && typeof data6 == "object" && !Array.isArray(data6)){if(data6.runId === undefined){const err27 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/required",keyword:"required",params:{missingProperty: "runId"},message:"must have required property '"+"runId"+"'"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data6.normalizedSessionId === undefined){const err28 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/required",keyword:"required",params:{missingProperty: "normalizedSessionId"},message:"must have required property '"+"normalizedSessionId"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data6.turnId === undefined){const err29 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/required",keyword:"required",params:{missingProperty: "turnId"},message:"must have required property '"+"turnId"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(data6.itemId === undefined){const err30 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/required",keyword:"required",params:{missingProperty: "itemId"},message:"must have required property '"+"itemId"+"'"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(data6.runId !== undefined){let data7 = data6.runId;if(typeof data7 === "string"){if(func1(data7) > 240){const err31 = {instancePath:instancePath+"/correlation/runId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func1(data7) < 1){const err32 = {instancePath:instancePath+"/correlation/runId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(!pattern4.test(data7)){const err33 = {instancePath:instancePath+"/correlation/runId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}else {const err34 = {instancePath:instancePath+"/correlation/runId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}if(data6.normalizedSessionId !== undefined){let data8 = data6.normalizedSessionId;if(typeof data8 === "string"){if(func1(data8) > 240){const err35 = {instancePath:instancePath+"/correlation/normalizedSessionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(func1(data8) < 1){const err36 = {instancePath:instancePath+"/correlation/normalizedSessionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}if(!pattern4.test(data8)){const err37 = {instancePath:instancePath+"/correlation/normalizedSessionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}else {const err38 = {instancePath:instancePath+"/correlation/normalizedSessionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data6.turnId !== undefined){let data9 = data6.turnId;if(typeof data9 === "string"){if(func1(data9) > 240){const err39 = {instancePath:instancePath+"/correlation/turnId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}if(func1(data9) < 1){const err40 = {instancePath:instancePath+"/correlation/turnId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if(!pattern4.test(data9)){const err41 = {instancePath:instancePath+"/correlation/turnId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}else {const err42 = {instancePath:instancePath+"/correlation/turnId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data6.itemId !== undefined){let data10 = data6.itemId;if(typeof data10 === "string"){if(func1(data10) > 240){const err43 = {instancePath:instancePath+"/correlation/itemId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}if(func1(data10) < 1){const err44 = {instancePath:instancePath+"/correlation/itemId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}if(!pattern4.test(data10)){const err45 = {instancePath:instancePath+"/correlation/itemId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}else {const err46 = {instancePath:instancePath+"/correlation/itemId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data6.requestId !== undefined){let data11 = data6.requestId;if(typeof data11 === "string"){if(func1(data11) > 240){const err47 = {instancePath:instancePath+"/correlation/requestId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}if(func1(data11) < 1){const err48 = {instancePath:instancePath+"/correlation/requestId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}if(!pattern4.test(data11)){const err49 = {instancePath:instancePath+"/correlation/requestId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}else {const err50 = {instancePath:instancePath+"/correlation/requestId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}else {const err51 = {instancePath:instancePath+"/correlation",schemaPath:"#/properties/correlation/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data.idempotencyKey !== undefined){let data12 = data.idempotencyKey;if((typeof data12 !== "string") && (data12 !== null)){const err52 = {instancePath:instancePath+"/idempotencyKey",schemaPath:"#/properties/idempotencyKey/type",keyword:"type",params:{type: schema174.properties.idempotencyKey.type},message:"must be string,null"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(typeof data12 === "string"){if(func1(data12) > 240){const err53 = {instancePath:instancePath+"/idempotencyKey",schemaPath:"#/properties/idempotencyKey/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}if(func1(data12) < 1){const err54 = {instancePath:instancePath+"/idempotencyKey",schemaPath:"#/properties/idempotencyKey/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}}if(data.content !== undefined){if(!(validate93(data.content, {instancePath:instancePath+"/content",parentData:data,parentDataProperty:"content",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate93.errors : vErrors.concat(validate93.errors);errors = vErrors.length;}}if(data.outcome !== undefined){let data14 = data.outcome;if(!((((((data14 === "succeeded") || (data14 === "denied")) || (data14 === "conflict")) || (data14 === "duplicate")) || (data14 === "unavailable")) || (data14 === "failed"))){const err55 = {instancePath:instancePath+"/outcome",schemaPath:"#/properties/outcome/enum",keyword:"enum",params:{allowedValues: schema174.properties.outcome.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}if(data.code !== undefined){let data15 = data.code;if(typeof data15 === "string"){if(func1(data15) > 160){const err56 = {instancePath:instancePath+"/code",schemaPath:"#/$defs/stableCode/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}if(func1(data15) < 1){const err57 = {instancePath:instancePath+"/code",schemaPath:"#/$defs/stableCode/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}if(!pattern14.test(data15)){const err58 = {instancePath:instancePath+"/code",schemaPath:"#/$defs/stableCode/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_.:-]*$"},message:"must match pattern \""+"^[a-z][a-z0-9_.:-]*$"+"\""};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}else {const err59 = {instancePath:instancePath+"/code",schemaPath:"#/$defs/stableCode/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}if(data.retryable !== undefined){if(typeof data.retryable !== "boolean"){const err60 = {instancePath:instancePath+"/retryable",schemaPath:"#/properties/retryable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}if(data.authorizationBoundary !== undefined){let data17 = data.authorizationBoundary;if(!(((((((data17 === "company") || (data17 === "actor")) || (data17 === "active_task")) || (data17 === "grant")) || (data17 === "governed_action")) || (data17 === "lock")) || (data17 === "revision"))){const err61 = {instancePath:instancePath+"/authorizationBoundary",schemaPath:"#/properties/authorizationBoundary/enum",keyword:"enum",params:{allowedValues: schema174.properties.authorizationBoundary.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data.operationReceiptId !== undefined){let data18 = data.operationReceiptId;if(typeof data18 === "string"){if(func1(data18) > 240){const err62 = {instancePath:instancePath+"/operationReceiptId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(func1(data18) < 1){const err63 = {instancePath:instancePath+"/operationReceiptId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}if(!pattern4.test(data18)){const err64 = {instancePath:instancePath+"/operationReceiptId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}else {const err65 = {instancePath:instancePath+"/operationReceiptId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data.auditReceiptId !== undefined){let data19 = data.auditReceiptId;if(typeof data19 === "string"){if(func1(data19) > 240){const err66 = {instancePath:instancePath+"/auditReceiptId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(func1(data19) < 1){const err67 = {instancePath:instancePath+"/auditReceiptId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}if(!pattern4.test(data19)){const err68 = {instancePath:instancePath+"/auditReceiptId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}else {const err69 = {instancePath:instancePath+"/auditReceiptId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}if(data.currentRevision !== undefined){let data20 = data.currentRevision;const _errs51 = errors;let valid15 = false;let passing0 = null;const _errs52 = errors;if(!(((typeof data20 == "number") && (!(data20 % 1) && !isNaN(data20))) && (isFinite(data20)))){const err70 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/properties/currentRevision/oneOf/0/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if((typeof data20 == "number") && (isFinite(data20))){if(data20 < 0 || isNaN(data20)){const err71 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/properties/currentRevision/oneOf/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}var _valid1 = _errs52 === errors;if(_valid1){valid15 = true;passing0 = 0;}const _errs54 = errors;if(typeof data20 === "string"){if(func1(data20) > 240){const err72 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}if(func1(data20) < 1){const err73 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(!pattern4.test(data20)){const err74 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}else {const err75 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}var _valid1 = _errs54 === errors;if(_valid1 && valid15){valid15 = false;passing0 = [passing0, 1];}else {if(_valid1){valid15 = true;passing0 = 1;}}if(!valid15){const err76 = {instancePath:instancePath+"/currentRevision",schemaPath:"#/properties/currentRevision/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}else {errors = _errs51;if(vErrors !== null){if(_errs51){vErrors.length = _errs51;}else {vErrors = null;}}}}if(data.duplicateOfReceiptId !== undefined){let data21 = data.duplicateOfReceiptId;if(typeof data21 === "string"){if(func1(data21) > 240){const err77 = {instancePath:instancePath+"/duplicateOfReceiptId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(func1(data21) < 1){const err78 = {instancePath:instancePath+"/duplicateOfReceiptId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}if(!pattern4.test(data21)){const err79 = {instancePath:instancePath+"/duplicateOfReceiptId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}else {const err80 = {instancePath:instancePath+"/duplicateOfReceiptId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data.artifactRefs !== undefined){let data22 = data.artifactRefs;if(Array.isArray(data22)){const len0 = data22.length;for(let i0=0; i0 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data22[i1], data22[j0])){const err83 = {instancePath:instancePath+"/artifactRefs",schemaPath:"#/properties/artifactRefs/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;break outer0;}}}}}else {const err84 = {instancePath:instancePath+"/artifactRefs",schemaPath:"#/properties/artifactRefs/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}if(data.targets !== undefined){let data25 = data.targets;if(Array.isArray(data25)){const len1 = data25.length;for(let i2=0; i2 1){outer1:for(;i3--;){for(j1 = i3; j1--;){if(func0(data25[i3], data25[j1])){const err85 = {instancePath:instancePath+"/targets",schemaPath:"#/properties/targets/uniqueItems",keyword:"uniqueItems",params:{i: i3, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i3+" are identical)"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;break outer1;}}}}}else {const err86 = {instancePath:instancePath+"/targets",schemaPath:"#/properties/targets/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}if(data.causalRefs !== undefined){let data27 = data.causalRefs;if(Array.isArray(data27)){const len2 = data27.length;for(let i4=0; i4 1){outer2:for(;i5--;){for(j2 = i5; j2--;){if(func0(data27[i5], data27[j2])){const err87 = {instancePath:instancePath+"/causalRefs",schemaPath:"#/properties/causalRefs/uniqueItems",keyword:"uniqueItems",params:{i: i5, j: j2},message:"must NOT have duplicate items (items ## "+j2+" and "+i5+" are identical)"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;break outer2;}}}}}else {const err88 = {instancePath:instancePath+"/causalRefs",schemaPath:"#/properties/causalRefs/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}}else {const err89 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}validate92.errors = vErrors;return errors === 0;}validate92.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema194 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/terminal.schema.json","title":"PRP run terminal state","type":"object","required":["schema","turnTerminalState","runTerminalState","reportedWorkDisposition"],"properties":{"schema":{"const":"paperclip.prp.terminal.v1"},"turnTerminalState":{"enum":["completed","failed","interrupted","cancelled"]},"runTerminalState":{"enum":["succeeded","failed","cancelled"]},"reportedWorkDisposition":{"enum":["done","blocked","needs_review","yielded"]},"workAssessmentId":{"type":"string","minLength":1},"statusDecisionId":{"type":"string","minLength":1},"stopReason":{"$ref":"https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json"}},"additionalProperties":true};const schema195 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json","title":"PRP terminal stop reason receipt","type":"object","required":["schema","schemaVersion","receiptId","kind","code","retryable","decisionId","limitClass","aggregate"],"properties":{"schema":{"const":"paperclip.prp.stop_reason.v1"},"schemaVersion":{"const":1},"receiptId":{"$ref":"#/$defs/stableId"},"kind":{"enum":["budget","cost"]},"code":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z][a-z0-9_.:-]*$"},"retryable":{"type":"boolean"},"decisionId":{"$ref":"#/$defs/stableId"},"limitClass":{"enum":["company_monthly","actor_monthly","project_monthly","run_cost","provider_quota"]},"aggregate":{"type":"object","required":["unit","observed","limit","window"],"properties":{"unit":{"enum":["cents","usd_micros","tokens"]},"observed":{"type":"integer","minimum":0},"limit":{"type":"integer","minimum":0},"window":{"enum":["run","monthly_utc","provider_window"]}},"additionalProperties":true},"relatedReceiptIds":{"type":"array","items":{"$ref":"#/$defs/stableId"},"uniqueItems":true}},"additionalProperties":true,"$defs":{"stableId":{"type":"string","minLength":1,"maxLength":240,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"}}};const schema196 = {"type":"string","minLength":1,"maxLength":240,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"};function validate105(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate105.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.schemaVersion === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.receiptId === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "receiptId"},message:"must have required property '"+"receiptId"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.kind === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.code === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "code"},message:"must have required property '"+"code"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.retryable === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "retryable"},message:"must have required property '"+"retryable"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.decisionId === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "decisionId"},message:"must have required property '"+"decisionId"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.limitClass === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "limitClass"},message:"must have required property '"+"limitClass"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.aggregate === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "aggregate"},message:"must have required property '"+"aggregate"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data.schema !== undefined){if("paperclip.prp.stop_reason.v1" !== data.schema){const err9 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.stop_reason.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.schemaVersion !== undefined){if(1 !== data.schemaVersion){const err10 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.receiptId !== undefined){let data2 = data.receiptId;if(typeof data2 === "string"){if(func1(data2) > 240){const err11 = {instancePath:instancePath+"/receiptId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(func1(data2) < 1){const err12 = {instancePath:instancePath+"/receiptId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(!pattern4.test(data2)){const err13 = {instancePath:instancePath+"/receiptId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}else {const err14 = {instancePath:instancePath+"/receiptId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.kind !== undefined){let data3 = data.kind;if(!((data3 === "budget") || (data3 === "cost"))){const err15 = {instancePath:instancePath+"/kind",schemaPath:"#/properties/kind/enum",keyword:"enum",params:{allowedValues: schema195.properties.kind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.code !== undefined){let data4 = data.code;if(typeof data4 === "string"){if(func1(data4) > 160){const err16 = {instancePath:instancePath+"/code",schemaPath:"#/properties/code/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(func1(data4) < 1){const err17 = {instancePath:instancePath+"/code",schemaPath:"#/properties/code/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(!pattern14.test(data4)){const err18 = {instancePath:instancePath+"/code",schemaPath:"#/properties/code/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_.:-]*$"},message:"must match pattern \""+"^[a-z][a-z0-9_.:-]*$"+"\""};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/code",schemaPath:"#/properties/code/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.retryable !== undefined){if(typeof data.retryable !== "boolean"){const err20 = {instancePath:instancePath+"/retryable",schemaPath:"#/properties/retryable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.decisionId !== undefined){let data6 = data.decisionId;if(typeof data6 === "string"){if(func1(data6) > 240){const err21 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(func1(data6) < 1){const err22 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(!pattern4.test(data6)){const err23 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/decisionId",schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.limitClass !== undefined){let data7 = data.limitClass;if(!(((((data7 === "company_monthly") || (data7 === "actor_monthly")) || (data7 === "project_monthly")) || (data7 === "run_cost")) || (data7 === "provider_quota"))){const err25 = {instancePath:instancePath+"/limitClass",schemaPath:"#/properties/limitClass/enum",keyword:"enum",params:{allowedValues: schema195.properties.limitClass.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data.aggregate !== undefined){let data8 = data.aggregate;if(data8 && typeof data8 == "object" && !Array.isArray(data8)){if(data8.unit === undefined){const err26 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/required",keyword:"required",params:{missingProperty: "unit"},message:"must have required property '"+"unit"+"'"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(data8.observed === undefined){const err27 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/required",keyword:"required",params:{missingProperty: "observed"},message:"must have required property '"+"observed"+"'"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data8.limit === undefined){const err28 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/required",keyword:"required",params:{missingProperty: "limit"},message:"must have required property '"+"limit"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data8.window === undefined){const err29 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/required",keyword:"required",params:{missingProperty: "window"},message:"must have required property '"+"window"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(data8.unit !== undefined){let data9 = data8.unit;if(!(((data9 === "cents") || (data9 === "usd_micros")) || (data9 === "tokens"))){const err30 = {instancePath:instancePath+"/aggregate/unit",schemaPath:"#/properties/aggregate/properties/unit/enum",keyword:"enum",params:{allowedValues: schema195.properties.aggregate.properties.unit.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data8.observed !== undefined){let data10 = data8.observed;if(!(((typeof data10 == "number") && (!(data10 % 1) && !isNaN(data10))) && (isFinite(data10)))){const err31 = {instancePath:instancePath+"/aggregate/observed",schemaPath:"#/properties/aggregate/properties/observed/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if((typeof data10 == "number") && (isFinite(data10))){if(data10 < 0 || isNaN(data10)){const err32 = {instancePath:instancePath+"/aggregate/observed",schemaPath:"#/properties/aggregate/properties/observed/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}}if(data8.limit !== undefined){let data11 = data8.limit;if(!(((typeof data11 == "number") && (!(data11 % 1) && !isNaN(data11))) && (isFinite(data11)))){const err33 = {instancePath:instancePath+"/aggregate/limit",schemaPath:"#/properties/aggregate/properties/limit/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if((typeof data11 == "number") && (isFinite(data11))){if(data11 < 0 || isNaN(data11)){const err34 = {instancePath:instancePath+"/aggregate/limit",schemaPath:"#/properties/aggregate/properties/limit/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}if(data8.window !== undefined){let data12 = data8.window;if(!(((data12 === "run") || (data12 === "monthly_utc")) || (data12 === "provider_window"))){const err35 = {instancePath:instancePath+"/aggregate/window",schemaPath:"#/properties/aggregate/properties/window/enum",keyword:"enum",params:{allowedValues: schema195.properties.aggregate.properties.window.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}}else {const err36 = {instancePath:instancePath+"/aggregate",schemaPath:"#/properties/aggregate/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data.relatedReceiptIds !== undefined){let data13 = data.relatedReceiptIds;if(Array.isArray(data13)){const len0 = data13.length;for(let i0=0; i0 240){const err37 = {instancePath:instancePath+"/relatedReceiptIds/" + i0,schemaPath:"#/$defs/stableId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(func1(data14) < 1){const err38 = {instancePath:instancePath+"/relatedReceiptIds/" + i0,schemaPath:"#/$defs/stableId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(!pattern4.test(data14)){const err39 = {instancePath:instancePath+"/relatedReceiptIds/" + i0,schemaPath:"#/$defs/stableId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/relatedReceiptIds/" + i0,schemaPath:"#/$defs/stableId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}let i1 = data13.length;let j0;if(i1 > 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data13[i1], data13[j0])){const err41 = {instancePath:instancePath+"/relatedReceiptIds",schemaPath:"#/properties/relatedReceiptIds/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;break outer0;}}}}}else {const err42 = {instancePath:instancePath+"/relatedReceiptIds",schemaPath:"#/properties/relatedReceiptIds/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}}else {const err43 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}validate105.errors = vErrors;return errors === 0;}validate105.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate104(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/terminal.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate104.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.turnTerminalState === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "turnTerminalState"},message:"must have required property '"+"turnTerminalState"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.runTerminalState === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "runTerminalState"},message:"must have required property '"+"runTerminalState"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.reportedWorkDisposition === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "reportedWorkDisposition"},message:"must have required property '"+"reportedWorkDisposition"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.schema !== undefined){if("paperclip.prp.terminal.v1" !== data.schema){const err4 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.terminal.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.turnTerminalState !== undefined){let data1 = data.turnTerminalState;if(!((((data1 === "completed") || (data1 === "failed")) || (data1 === "interrupted")) || (data1 === "cancelled"))){const err5 = {instancePath:instancePath+"/turnTerminalState",schemaPath:"#/properties/turnTerminalState/enum",keyword:"enum",params:{allowedValues: schema194.properties.turnTerminalState.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.runTerminalState !== undefined){let data2 = data.runTerminalState;if(!(((data2 === "succeeded") || (data2 === "failed")) || (data2 === "cancelled"))){const err6 = {instancePath:instancePath+"/runTerminalState",schemaPath:"#/properties/runTerminalState/enum",keyword:"enum",params:{allowedValues: schema194.properties.runTerminalState.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.reportedWorkDisposition !== undefined){let data3 = data.reportedWorkDisposition;if(!((((data3 === "done") || (data3 === "blocked")) || (data3 === "needs_review")) || (data3 === "yielded"))){const err7 = {instancePath:instancePath+"/reportedWorkDisposition",schemaPath:"#/properties/reportedWorkDisposition/enum",keyword:"enum",params:{allowedValues: schema194.properties.reportedWorkDisposition.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.workAssessmentId !== undefined){let data4 = data.workAssessmentId;if(typeof data4 === "string"){if(func1(data4) < 1){const err8 = {instancePath:instancePath+"/workAssessmentId",schemaPath:"#/properties/workAssessmentId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/workAssessmentId",schemaPath:"#/properties/workAssessmentId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.statusDecisionId !== undefined){let data5 = data.statusDecisionId;if(typeof data5 === "string"){if(func1(data5) < 1){const err10 = {instancePath:instancePath+"/statusDecisionId",schemaPath:"#/properties/statusDecisionId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/statusDecisionId",schemaPath:"#/properties/statusDecisionId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.stopReason !== undefined){if(!(validate105(data.stopReason, {instancePath:instancePath+"/stopReason",parentData:data,parentDataProperty:"stopReason",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate105.errors : vErrors.concat(validate105.errors);errors = vErrors.length;}}}else {const err12 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}validate104.errors = vErrors;return errors === 0;}validate104.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const pattern69 = new RegExp("^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$", "u");const formats0 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function validate26(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/event.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate26.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("plan.updated" !== data.eventType){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate60(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate60.errors : vErrors.concat(validate60.errors);errors = vErrors.length;}}}var _valid0 = _errs5 === errors;valid1 = _valid0;if(valid1){var props0 = {};props0.payload = true;props0.eventType = true;}}if(!valid1){const err1 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}const _errs8 = errors;let valid4 = true;const _errs9 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data2 = data.eventType;if(!(((data2 === "tool.execution.started") || (data2 === "tool.execution.progressed")) || (data2 === "tool.execution.completed"))){const err2 = {};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}}var _valid1 = _errs9 === errors;errors = _errs8;if(vErrors !== null){if(_errs8){vErrors.length = _errs8;}else {vErrors = null;}}if(_valid1){const _errs11 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate62(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate62.errors : vErrors.concat(validate62.errors);errors = vErrors.length;}}}var _valid1 = _errs11 === errors;valid4 = _valid1;if(valid4){var props1 = {};props1.payload = true;props1.eventType = true;}}if(!valid4){const err3 = {instancePath,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(props0 !== true && props1 !== undefined){if(props1 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props1);}}const _errs14 = errors;let valid7 = true;const _errs15 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data4 = data.eventType;if(!(((data4 === "research.started") || (data4 === "research.progressed")) || (data4 === "research.completed"))){const err4 = {};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}}var _valid2 = _errs15 === errors;errors = _errs14;if(vErrors !== null){if(_errs14){vErrors.length = _errs14;}else {vErrors = null;}}if(_valid2){const _errs17 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate64(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate64.errors : vErrors.concat(validate64.errors);errors = vErrors.length;}}}var _valid2 = _errs17 === errors;valid7 = _valid2;if(valid7){var props2 = {};props2.payload = true;props2.eventType = true;}}if(!valid7){const err5 = {instancePath,schemaPath:"#/allOf/2/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(props0 !== true && props2 !== undefined){if(props2 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props2);}}const _errs20 = errors;let valid10 = true;const _errs21 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data6 = data.eventType;if(!(((data6 === "delegation.started") || (data6 === "delegation.updated")) || (data6 === "delegation.completed"))){const err6 = {};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}var _valid3 = _errs21 === errors;errors = _errs20;if(vErrors !== null){if(_errs20){vErrors.length = _errs20;}else {vErrors = null;}}if(_valid3){const _errs23 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate66(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate66.errors : vErrors.concat(validate66.errors);errors = vErrors.length;}}}var _valid3 = _errs23 === errors;valid10 = _valid3;if(valid10){var props3 = {};props3.payload = true;props3.eventType = true;}}if(!valid10){const err7 = {instancePath,schemaPath:"#/allOf/3/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(props0 !== true && props3 !== undefined){if(props3 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props3);}}const _errs26 = errors;let valid13 = true;const _errs27 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("model.route.changed" !== data.eventType){const err8 = {};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}}var _valid4 = _errs27 === errors;errors = _errs26;if(vErrors !== null){if(_errs26){vErrors.length = _errs26;}else {vErrors = null;}}if(_valid4){const _errs29 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate68(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate68.errors : vErrors.concat(validate68.errors);errors = vErrors.length;}}}var _valid4 = _errs29 === errors;valid13 = _valid4;if(valid13){var props4 = {};props4.payload = true;props4.eventType = true;}}if(!valid13){const err9 = {instancePath,schemaPath:"#/allOf/4/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(props0 !== true && props4 !== undefined){if(props4 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props4);}}const _errs32 = errors;let valid16 = true;const _errs33 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("model.verification.updated" !== data.eventType){const err10 = {};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}}var _valid5 = _errs33 === errors;errors = _errs32;if(vErrors !== null){if(_errs32){vErrors.length = _errs32;}else {vErrors = null;}}if(_valid5){const _errs35 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate70(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate70.errors : vErrors.concat(validate70.errors);errors = vErrors.length;}}}var _valid5 = _errs35 === errors;valid16 = _valid5;if(valid16){var props5 = {};props5.payload = true;props5.eventType = true;}}if(!valid16){const err11 = {instancePath,schemaPath:"#/allOf/5/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(props0 !== true && props5 !== undefined){if(props5 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props5);}}const _errs38 = errors;let valid19 = true;const _errs39 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("context.compacted" !== data.eventType){const err12 = {};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}var _valid6 = _errs39 === errors;errors = _errs38;if(vErrors !== null){if(_errs38){vErrors.length = _errs38;}else {vErrors = null;}}if(_valid6){const _errs41 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate72(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate72.errors : vErrors.concat(validate72.errors);errors = vErrors.length;}}}var _valid6 = _errs41 === errors;valid19 = _valid6;if(valid19){var props6 = {};props6.payload = true;props6.eventType = true;}}if(!valid19){const err13 = {instancePath,schemaPath:"#/allOf/6/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(props0 !== true && props6 !== undefined){if(props6 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props6);}}const _errs44 = errors;let valid22 = true;const _errs45 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("artifact.viewed" !== data.eventType){const err14 = {};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}}var _valid7 = _errs45 === errors;errors = _errs44;if(vErrors !== null){if(_errs44){vErrors.length = _errs44;}else {vErrors = null;}}if(_valid7){const _errs47 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate74(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate74.errors : vErrors.concat(validate74.errors);errors = vErrors.length;}}}var _valid7 = _errs47 === errors;valid22 = _valid7;if(valid22){var props7 = {};props7.payload = true;props7.eventType = true;}}if(!valid22){const err15 = {instancePath,schemaPath:"#/allOf/7/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(props0 !== true && props7 !== undefined){if(props7 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props7);}}const _errs50 = errors;let valid25 = true;const _errs51 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("artifact.generated" !== data.eventType){const err16 = {};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}var _valid8 = _errs51 === errors;errors = _errs50;if(vErrors !== null){if(_errs50){vErrors.length = _errs50;}else {vErrors = null;}}if(_valid8){const _errs53 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate76(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate76.errors : vErrors.concat(validate76.errors);errors = vErrors.length;}}}var _valid8 = _errs53 === errors;valid25 = _valid8;if(valid25){var props8 = {};props8.payload = true;props8.eventType = true;}}if(!valid25){const err17 = {instancePath,schemaPath:"#/allOf/8/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(props0 !== true && props8 !== undefined){if(props8 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props8);}}const _errs56 = errors;let valid28 = true;const _errs57 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("review.mode.changed" !== data.eventType){const err18 = {};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}}var _valid9 = _errs57 === errors;errors = _errs56;if(vErrors !== null){if(_errs56){vErrors.length = _errs56;}else {vErrors = null;}}if(_valid9){const _errs59 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate78(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate78.errors : vErrors.concat(validate78.errors);errors = vErrors.length;}}}var _valid9 = _errs59 === errors;valid28 = _valid9;if(valid28){var props9 = {};props9.payload = true;props9.eventType = true;}}if(!valid28){const err19 = {instancePath,schemaPath:"#/allOf/9/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(props0 !== true && props9 !== undefined){if(props9 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props9);}}const _errs62 = errors;let valid31 = true;const _errs63 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data20 = data.eventType;if(!((data20 === "hook.started") || (data20 === "hook.completed"))){const err20 = {};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}var _valid10 = _errs63 === errors;errors = _errs62;if(vErrors !== null){if(_errs62){vErrors.length = _errs62;}else {vErrors = null;}}if(_valid10){const _errs65 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate80(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate80.errors : vErrors.concat(validate80.errors);errors = vErrors.length;}}}var _valid10 = _errs65 === errors;valid31 = _valid10;if(valid31){var props10 = {};props10.payload = true;props10.eventType = true;}}if(!valid31){const err21 = {instancePath,schemaPath:"#/allOf/10/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(props0 !== true && props10 !== undefined){if(props10 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props10);}}const _errs68 = errors;let valid34 = true;const _errs69 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("memory.citation.referenced" !== data.eventType){const err22 = {};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}}var _valid11 = _errs69 === errors;errors = _errs68;if(vErrors !== null){if(_errs68){vErrors.length = _errs68;}else {vErrors = null;}}if(_valid11){const _errs71 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate82(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate82.errors : vErrors.concat(validate82.errors);errors = vErrors.length;}}}var _valid11 = _errs71 === errors;valid34 = _valid11;if(valid34){var props11 = {};props11.payload = true;props11.eventType = true;}}if(!valid34){const err23 = {instancePath,schemaPath:"#/allOf/11/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(props0 !== true && props11 !== undefined){if(props11 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props11);}}const _errs74 = errors;let valid37 = true;const _errs75 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data24 = data.eventType;if(!((data24 === "safety.review.started") || (data24 === "safety.review.completed"))){const err24 = {};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}}var _valid12 = _errs75 === errors;errors = _errs74;if(vErrors !== null){if(_errs74){vErrors.length = _errs74;}else {vErrors = null;}}if(_valid12){const _errs77 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate84(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate84.errors : vErrors.concat(validate84.errors);errors = vErrors.length;}}}var _valid12 = _errs77 === errors;valid37 = _valid12;if(valid37){var props12 = {};props12.payload = true;props12.eventType = true;}}if(!valid37){const err25 = {instancePath,schemaPath:"#/allOf/12/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(props0 !== true && props12 !== undefined){if(props12 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props12);}}const _errs80 = errors;let valid40 = true;const _errs81 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("terminal.input.sent" !== data.eventType){const err26 = {};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}}var _valid13 = _errs81 === errors;errors = _errs80;if(vErrors !== null){if(_errs80){vErrors.length = _errs80;}else {vErrors = null;}}if(_valid13){const _errs83 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate86(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate86.errors : vErrors.concat(validate86.errors);errors = vErrors.length;}}}var _valid13 = _errs83 === errors;valid40 = _valid13;if(valid40){var props13 = {};props13.payload = true;props13.eventType = true;}}if(!valid40){const err27 = {instancePath,schemaPath:"#/allOf/13/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(props0 !== true && props13 !== undefined){if(props13 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props13);}}const _errs86 = errors;let valid43 = true;const _errs87 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data28 = data.eventType;if(!((data28 === "wait.started") || (data28 === "wait.completed"))){const err28 = {};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}var _valid14 = _errs87 === errors;errors = _errs86;if(vErrors !== null){if(_errs86){vErrors.length = _errs86;}else {vErrors = null;}}if(_valid14){const _errs89 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate88(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate88.errors : vErrors.concat(validate88.errors);errors = vErrors.length;}}}var _valid14 = _errs89 === errors;valid43 = _valid14;if(valid43){var props14 = {};props14.payload = true;props14.eventType = true;}}if(!valid43){const err29 = {instancePath,schemaPath:"#/allOf/14/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(props0 !== true && props14 !== undefined){if(props14 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props14);}}const _errs92 = errors;let valid46 = true;const _errs93 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("provider.notice.recorded" !== data.eventType){const err30 = {};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}}var _valid15 = _errs93 === errors;errors = _errs92;if(vErrors !== null){if(_errs92){vErrors.length = _errs92;}else {vErrors = null;}}if(_valid15){const _errs95 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate90(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate90.errors : vErrors.concat(validate90.errors);errors = vErrors.length;}}}var _valid15 = _errs95 === errors;valid46 = _valid15;if(valid46){var props15 = {};props15.payload = true;props15.eventType = true;}}if(!valid46){const err31 = {instancePath,schemaPath:"#/allOf/15/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(props0 !== true && props15 !== undefined){if(props15 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props15);}}const _errs98 = errors;let valid49 = true;const _errs99 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("workspace.file.referenced" !== data.eventType){const err32 = {};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}}var _valid16 = _errs99 === errors;errors = _errs98;if(vErrors !== null){if(_errs98){vErrors.length = _errs98;}else {vErrors = null;}}if(_valid16){const _errs101 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data33 = data.payload;if(data33 && typeof data33 == "object" && !Array.isArray(data33)){if(data33.schema === undefined){const err33 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(data33.referenceId === undefined){const err34 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "referenceId"},message:"must have required property '"+"referenceId"+"'"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(data33.source === undefined){const err35 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "source"},message:"must have required property '"+"source"+"'"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(data33.path === undefined){const err36 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "path"},message:"must have required property '"+"path"+"'"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}if(data33.displayName === undefined){const err37 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "displayName"},message:"must have required property '"+"displayName"+"'"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(data33.mediaType === undefined){const err38 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "mediaType"},message:"must have required property '"+"mediaType"+"'"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(data33.presentation === undefined){const err39 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "presentation"},message:"must have required property '"+"presentation"+"'"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}if(data33.line === undefined){const err40 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "line"},message:"must have required property '"+"line"+"'"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if(data33.preview === undefined){const err41 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "preview"},message:"must have required property '"+"preview"+"'"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}if(data33.previewTruncated === undefined){const err42 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "previewTruncated"},message:"must have required property '"+"previewTruncated"+"'"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}if(data33.contentDigest === undefined){const err43 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/required",keyword:"required",params:{missingProperty: "contentDigest"},message:"must have required property '"+"contentDigest"+"'"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key0 in data33){if(!(func58.call(schema171.properties, key0))){const err44 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data33.schema !== undefined){if("paperclip.workspace.file_reference.v1" !== data33.schema){const err45 = {instancePath:instancePath+"/payload/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.workspace.file_reference.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}if(data33.referenceId !== undefined){let data35 = data33.referenceId;if(typeof data35 === "string"){if(func1(data35) > 240){const err46 = {instancePath:instancePath+"/payload/referenceId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/referenceId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}if(func1(data35) < 1){const err47 = {instancePath:instancePath+"/payload/referenceId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/referenceId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}else {const err48 = {instancePath:instancePath+"/payload/referenceId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/referenceId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}if(data33.source !== undefined){let data36 = data33.source;if(!((data36 === "harness_reported") || (data36 === "runner_verified"))){const err49 = {instancePath:instancePath+"/payload/source",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/source/enum",keyword:"enum",params:{allowedValues: schema171.properties.source.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data33.path !== undefined){let data37 = data33.path;if(typeof data37 === "string"){if(func1(data37) > 1024){const err50 = {instancePath:instancePath+"/payload/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/path/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}if(func1(data37) < 1){const err51 = {instancePath:instancePath+"/payload/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}if(!pattern69.test(data37)){const err52 = {instancePath:instancePath+"/payload/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/path/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}else {const err53 = {instancePath:instancePath+"/payload/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data33.displayName !== undefined){let data38 = data33.displayName;if(typeof data38 === "string"){if(func1(data38) > 255){const err54 = {instancePath:instancePath+"/payload/displayName",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/displayName/maxLength",keyword:"maxLength",params:{limit: 255},message:"must NOT have more than 255 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}if(func1(data38) < 1){const err55 = {instancePath:instancePath+"/payload/displayName",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/displayName/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}else {const err56 = {instancePath:instancePath+"/payload/displayName",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/displayName/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data33.mediaType !== undefined){let data39 = data33.mediaType;if((typeof data39 !== "string") && (data39 !== null)){const err57 = {instancePath:instancePath+"/payload/mediaType",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/mediaType/type",keyword:"type",params:{type: schema171.properties.mediaType.type},message:"must be string,null"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}if(typeof data39 === "string"){if(func1(data39) > 160){const err58 = {instancePath:instancePath+"/payload/mediaType",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/mediaType/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}}if(data33.presentation !== undefined){let data40 = data33.presentation;if(!((((data40 === "document") || (data40 === "code")) || (data40 === "image")) || (data40 === "generic"))){const err59 = {instancePath:instancePath+"/payload/presentation",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/presentation/enum",keyword:"enum",params:{allowedValues: schema171.properties.presentation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}if(data33.line !== undefined){let data41 = data33.line;if((!(((typeof data41 == "number") && (!(data41 % 1) && !isNaN(data41))) && (isFinite(data41)))) && (data41 !== null)){const err60 = {instancePath:instancePath+"/payload/line",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/line/type",keyword:"type",params:{type: schema171.properties.line.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}if((typeof data41 == "number") && (isFinite(data41))){if(data41 < 1 || isNaN(data41)){const err61 = {instancePath:instancePath+"/payload/line",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/line/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}}if(data33.preview !== undefined){let data42 = data33.preview;if((typeof data42 !== "string") && (data42 !== null)){const err62 = {instancePath:instancePath+"/payload/preview",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/preview/type",keyword:"type",params:{type: schema171.properties.preview.type},message:"must be string,null"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(typeof data42 === "string"){if(func1(data42) > 262144){const err63 = {instancePath:instancePath+"/payload/preview",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/preview/maxLength",keyword:"maxLength",params:{limit: 262144},message:"must NOT have more than 262144 characters"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}}if(data33.previewTruncated !== undefined){if(typeof data33.previewTruncated !== "boolean"){const err64 = {instancePath:instancePath+"/payload/previewTruncated",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/previewTruncated/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}if(data33.contentDigest !== undefined){let data44 = data33.contentDigest;const _errs124 = errors;let valid54 = false;let passing0 = null;const _errs125 = errors;if(data44 !== null){const err65 = {instancePath:instancePath+"/payload/contentDigest",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/contentDigest/oneOf/0/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}var _valid17 = _errs125 === errors;if(_valid17){valid54 = true;passing0 = 0;}const _errs127 = errors;if(typeof data44 === "string"){if(!pattern19.test(data44)){const err66 = {instancePath:instancePath+"/payload/contentDigest",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/contentDigest/oneOf/1/pattern",keyword:"pattern",params:{pattern: "^sha256:[a-f0-9]{64}$"},message:"must match pattern \""+"^sha256:[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}else {const err67 = {instancePath:instancePath+"/payload/contentDigest",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/contentDigest/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}var _valid17 = _errs127 === errors;if(_valid17 && valid54){valid54 = false;passing0 = [passing0, 1];}else {if(_valid17){valid54 = true;passing0 = 1;}}if(!valid54){const err68 = {instancePath:instancePath+"/payload/contentDigest",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/properties/contentDigest/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}else {errors = _errs124;if(vErrors !== null){if(_errs124){vErrors.length = _errs124;}else {vErrors = null;}}}}}else {const err69 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}}var _valid16 = _errs101 === errors;valid49 = _valid16;if(valid49){var props16 = {};props16.payload = true;props16.eventType = true;}}if(!valid49){const err70 = {instancePath,schemaPath:"#/allOf/16/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if(props0 !== true && props16 !== undefined){if(props16 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props16);}}const _errs130 = errors;let valid55 = true;const _errs131 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data45 = data.eventType;if(!((data45 === "workspace.change.updated") || (data45 === "workspace.diff.recorded"))){const err71 = {};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}}var _valid18 = _errs131 === errors;errors = _errs130;if(vErrors !== null){if(_errs130){vErrors.length = _errs130;}else {vErrors = null;}}if(_valid18){const _errs133 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data46 = data.payload;if(data46 && typeof data46 == "object" && !Array.isArray(data46)){if(data46.schema === undefined){const err72 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}if(data46.changeSetId === undefined){const err73 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "changeSetId"},message:"must have required property '"+"changeSetId"+"'"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(data46.revision === undefined){const err74 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "revision"},message:"must have required property '"+"revision"+"'"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}if(data46.source === undefined){const err75 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "source"},message:"must have required property '"+"source"+"'"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}if(data46.complete === undefined){const err76 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "complete"},message:"must have required property '"+"complete"+"'"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}if(data46.files === undefined){const err77 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "files"},message:"must have required property '"+"files"+"'"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(data46.totals === undefined){const err78 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "totals"},message:"must have required property '"+"totals"+"'"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}if(data46.patchArtifactRef === undefined){const err79 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "patchArtifactRef"},message:"must have required property '"+"patchArtifactRef"+"'"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}for(const key1 in data46){if(!((((((((key1 === "schema") || (key1 === "changeSetId")) || (key1 === "revision")) || (key1 === "source")) || (key1 === "complete")) || (key1 === "files")) || (key1 === "totals")) || (key1 === "patchArtifactRef"))){const err80 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data46.schema !== undefined){if("paperclip.workspace.diff.v1" !== data46.schema){const err81 = {instancePath:instancePath+"/payload/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.workspace.diff.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}if(data46.changeSetId !== undefined){let data48 = data46.changeSetId;if(typeof data48 === "string"){if(func1(data48) > 200){const err82 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/maxLength",keyword:"maxLength",params:{limit: 200},message:"must NOT have more than 200 characters"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}if(func1(data48) < 1){const err83 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}else {const err84 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}if(data46.revision !== undefined){let data49 = data46.revision;if(!(((typeof data49 == "number") && (!(data49 % 1) && !isNaN(data49))) && (isFinite(data49)))){const err85 = {instancePath:instancePath+"/payload/revision",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/revision/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}if((typeof data49 == "number") && (isFinite(data49))){if(data49 < 1 || isNaN(data49)){const err86 = {instancePath:instancePath+"/payload/revision",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/revision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}}if(data46.source !== undefined){let data50 = data46.source;if(!((data50 === "harness_reported") || (data50 === "runner_verified"))){const err87 = {instancePath:instancePath+"/payload/source",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/source/enum",keyword:"enum",params:{allowedValues: schema172.properties.source.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}if(data46.complete !== undefined){if(typeof data46.complete !== "boolean"){const err88 = {instancePath:instancePath+"/payload/complete",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/complete/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data46.files !== undefined){let data52 = data46.files;if(Array.isArray(data52)){if(data52.length > 2000){const err89 = {instancePath:instancePath+"/payload/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/maxItems",keyword:"maxItems",params:{limit: 2000},message:"must NOT have more than 2000 items"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}const len0 = data52.length;for(let i0=0; i0 1024){const err98 = {instancePath:instancePath+"/payload/files/" + i0+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}if(func1(data54) < 1){const err99 = {instancePath:instancePath+"/payload/files/" + i0+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}if(!pattern69.test(data54)){const err100 = {instancePath:instancePath+"/payload/files/" + i0+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}else {const err101 = {instancePath:instancePath+"/payload/files/" + i0+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}}if(data53.operation !== undefined){let data55 = data53.operation;if(!(((((data55 === "create") || (data55 === "modify")) || (data55 === "delete")) || (data55 === "rename")) || (data55 === "mode_change"))){const err102 = {instancePath:instancePath+"/payload/files/" + i0+"/operation",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/operation/enum",keyword:"enum",params:{allowedValues: schema172.properties.files.items.properties.operation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}if(data53.previousPath !== undefined){let data56 = data53.previousPath;const _errs155 = errors;let valid63 = false;let passing1 = null;const _errs156 = errors;if(data56 !== null){const err103 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/0/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}var _valid19 = _errs156 === errors;if(_valid19){valid63 = true;passing1 = 0;}const _errs158 = errors;if(typeof data56 === "string"){if(func1(data56) > 1024){const err104 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}if(func1(data56) < 1){const err105 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}if(!pattern69.test(data56)){const err106 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}else {const err107 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}var _valid19 = _errs158 === errors;if(_valid19 && valid63){valid63 = false;passing1 = [passing1, 1];}else {if(_valid19){valid63 = true;passing1 = 1;}}if(!valid63){const err108 = {instancePath:instancePath+"/payload/files/" + i0+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf",keyword:"oneOf",params:{passingSchemas: passing1},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}else {errors = _errs155;if(vErrors !== null){if(_errs155){vErrors.length = _errs155;}else {vErrors = null;}}}}if(data53.additions !== undefined){let data57 = data53.additions;if((!(((typeof data57 == "number") && (!(data57 % 1) && !isNaN(data57))) && (isFinite(data57)))) && (data57 !== null)){const err109 = {instancePath:instancePath+"/payload/files/" + i0+"/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/additions/type",keyword:"type",params:{type: schema172.properties.files.items.properties.additions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}if((typeof data57 == "number") && (isFinite(data57))){if(data57 < 0 || isNaN(data57)){const err110 = {instancePath:instancePath+"/payload/files/" + i0+"/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/additions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}}if(data53.deletions !== undefined){let data58 = data53.deletions;if((!(((typeof data58 == "number") && (!(data58 % 1) && !isNaN(data58))) && (isFinite(data58)))) && (data58 !== null)){const err111 = {instancePath:instancePath+"/payload/files/" + i0+"/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/deletions/type",keyword:"type",params:{type: schema172.properties.files.items.properties.deletions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}if((typeof data58 == "number") && (isFinite(data58))){if(data58 < 0 || isNaN(data58)){const err112 = {instancePath:instancePath+"/payload/files/" + i0+"/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/deletions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}}if(data53.binary !== undefined){if(typeof data53.binary !== "boolean"){const err113 = {instancePath:instancePath+"/payload/files/" + i0+"/binary",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/binary/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}if(data53.diff !== undefined){let data60 = data53.diff;if((typeof data60 !== "string") && (data60 !== null)){const err114 = {instancePath:instancePath+"/payload/files/" + i0+"/diff",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/diff/type",keyword:"type",params:{type: schema172.properties.files.items.properties.diff.type},message:"must be string,null"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}if(typeof data60 === "string"){if(func1(data60) > 262144){const err115 = {instancePath:instancePath+"/payload/files/" + i0+"/diff",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/diff/maxLength",keyword:"maxLength",params:{limit: 262144},message:"must NOT have more than 262144 characters"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}}}else {const err116 = {instancePath:instancePath+"/payload/files/" + i0,schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}}else {const err117 = {instancePath:instancePath+"/payload/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}}if(data46.totals !== undefined){let data61 = data46.totals;if(data61 && typeof data61 == "object" && !Array.isArray(data61)){if(data61.files === undefined){const err118 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "files"},message:"must have required property '"+"files"+"'"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}if(data61.additions === undefined){const err119 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "additions"},message:"must have required property '"+"additions"+"'"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}if(data61.deletions === undefined){const err120 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "deletions"},message:"must have required property '"+"deletions"+"'"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}for(const key3 in data61){if(!(((key3 === "files") || (key3 === "additions")) || (key3 === "deletions"))){const err121 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}if(data61.files !== undefined){let data62 = data61.files;if(!(((typeof data62 == "number") && (!(data62 % 1) && !isNaN(data62))) && (isFinite(data62)))){const err122 = {instancePath:instancePath+"/payload/totals/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/files/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}if((typeof data62 == "number") && (isFinite(data62))){if(data62 < 0 || isNaN(data62)){const err123 = {instancePath:instancePath+"/payload/totals/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/files/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}}if(data61.additions !== undefined){let data63 = data61.additions;if((!(((typeof data63 == "number") && (!(data63 % 1) && !isNaN(data63))) && (isFinite(data63)))) && (data63 !== null)){const err124 = {instancePath:instancePath+"/payload/totals/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/additions/type",keyword:"type",params:{type: schema172.properties.totals.properties.additions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}if((typeof data63 == "number") && (isFinite(data63))){if(data63 < 0 || isNaN(data63)){const err125 = {instancePath:instancePath+"/payload/totals/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/additions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}}if(data61.deletions !== undefined){let data64 = data61.deletions;if((!(((typeof data64 == "number") && (!(data64 % 1) && !isNaN(data64))) && (isFinite(data64)))) && (data64 !== null)){const err126 = {instancePath:instancePath+"/payload/totals/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/deletions/type",keyword:"type",params:{type: schema172.properties.totals.properties.deletions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}if((typeof data64 == "number") && (isFinite(data64))){if(data64 < 0 || isNaN(data64)){const err127 = {instancePath:instancePath+"/payload/totals/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/deletions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}}}else {const err128 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}if(data46.patchArtifactRef !== undefined){let data65 = data46.patchArtifactRef;if((typeof data65 !== "string") && (data65 !== null)){const err129 = {instancePath:instancePath+"/payload/patchArtifactRef",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/patchArtifactRef/type",keyword:"type",params:{type: schema172.properties.patchArtifactRef.type},message:"must be string,null"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}if(typeof data65 === "string"){if(func1(data65) > 2048){const err130 = {instancePath:instancePath+"/payload/patchArtifactRef",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/patchArtifactRef/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}}}}else {const err131 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}}var _valid18 = _errs133 === errors;valid55 = _valid18;if(valid55){var props17 = {};props17.payload = true;props17.eventType = true;}}if(!valid55){const err132 = {instancePath,schemaPath:"#/allOf/17/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}if(props0 !== true && props17 !== undefined){if(props17 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props17);}}const _errs180 = errors;let valid65 = true;const _errs181 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("workspace.diff.recorded" !== data.eventType){const err133 = {};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}}var _valid20 = _errs181 === errors;errors = _errs180;if(vErrors !== null){if(_errs180){vErrors.length = _errs180;}else {vErrors = null;}}if(_valid20){const _errs183 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data67 = data.payload;if(data67 && typeof data67 == "object" && !Array.isArray(data67)){if(data67.schema === undefined){const err134 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}if(data67.changeSetId === undefined){const err135 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "changeSetId"},message:"must have required property '"+"changeSetId"+"'"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}if(data67.revision === undefined){const err136 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "revision"},message:"must have required property '"+"revision"+"'"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}if(data67.source === undefined){const err137 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "source"},message:"must have required property '"+"source"+"'"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}if(data67.complete === undefined){const err138 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "complete"},message:"must have required property '"+"complete"+"'"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}if(data67.files === undefined){const err139 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "files"},message:"must have required property '"+"files"+"'"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}if(data67.totals === undefined){const err140 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "totals"},message:"must have required property '"+"totals"+"'"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}if(data67.patchArtifactRef === undefined){const err141 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/required",keyword:"required",params:{missingProperty: "patchArtifactRef"},message:"must have required property '"+"patchArtifactRef"+"'"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}for(const key4 in data67){if(!((((((((key4 === "schema") || (key4 === "changeSetId")) || (key4 === "revision")) || (key4 === "source")) || (key4 === "complete")) || (key4 === "files")) || (key4 === "totals")) || (key4 === "patchArtifactRef"))){const err142 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}if(data67.schema !== undefined){if("paperclip.workspace.diff.v1" !== data67.schema){const err143 = {instancePath:instancePath+"/payload/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.workspace.diff.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}if(data67.changeSetId !== undefined){let data69 = data67.changeSetId;if(typeof data69 === "string"){if(func1(data69) > 200){const err144 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/maxLength",keyword:"maxLength",params:{limit: 200},message:"must NOT have more than 200 characters"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}if(func1(data69) < 1){const err145 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}else {const err146 = {instancePath:instancePath+"/payload/changeSetId",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/changeSetId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}}if(data67.revision !== undefined){let data70 = data67.revision;if(!(((typeof data70 == "number") && (!(data70 % 1) && !isNaN(data70))) && (isFinite(data70)))){const err147 = {instancePath:instancePath+"/payload/revision",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/revision/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}if((typeof data70 == "number") && (isFinite(data70))){if(data70 < 1 || isNaN(data70)){const err148 = {instancePath:instancePath+"/payload/revision",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/revision/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}}}if(data67.source !== undefined){let data71 = data67.source;if(!((data71 === "harness_reported") || (data71 === "runner_verified"))){const err149 = {instancePath:instancePath+"/payload/source",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/source/enum",keyword:"enum",params:{allowedValues: schema172.properties.source.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}}if(data67.complete !== undefined){if(typeof data67.complete !== "boolean"){const err150 = {instancePath:instancePath+"/payload/complete",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/complete/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err150];}else {vErrors.push(err150);}errors++;}}if(data67.files !== undefined){let data73 = data67.files;if(Array.isArray(data73)){if(data73.length > 2000){const err151 = {instancePath:instancePath+"/payload/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/maxItems",keyword:"maxItems",params:{limit: 2000},message:"must NOT have more than 2000 items"};if(vErrors === null){vErrors = [err151];}else {vErrors.push(err151);}errors++;}const len1 = data73.length;for(let i1=0; i1 1024){const err160 = {instancePath:instancePath+"/payload/files/" + i1+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err160];}else {vErrors.push(err160);}errors++;}if(func1(data75) < 1){const err161 = {instancePath:instancePath+"/payload/files/" + i1+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err161];}else {vErrors.push(err161);}errors++;}if(!pattern69.test(data75)){const err162 = {instancePath:instancePath+"/payload/files/" + i1+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err162];}else {vErrors.push(err162);}errors++;}}else {const err163 = {instancePath:instancePath+"/payload/files/" + i1+"/path",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err163];}else {vErrors.push(err163);}errors++;}}if(data74.operation !== undefined){let data76 = data74.operation;if(!(((((data76 === "create") || (data76 === "modify")) || (data76 === "delete")) || (data76 === "rename")) || (data76 === "mode_change"))){const err164 = {instancePath:instancePath+"/payload/files/" + i1+"/operation",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/operation/enum",keyword:"enum",params:{allowedValues: schema172.properties.files.items.properties.operation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err164];}else {vErrors.push(err164);}errors++;}}if(data74.previousPath !== undefined){let data77 = data74.previousPath;const _errs206 = errors;let valid74 = false;let passing2 = null;const _errs207 = errors;if(data77 !== null){const err165 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/0/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err165];}else {vErrors.push(err165);}errors++;}var _valid21 = _errs207 === errors;if(_valid21){valid74 = true;passing2 = 0;}const _errs209 = errors;if(typeof data77 === "string"){if(func1(data77) > 1024){const err166 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"};if(vErrors === null){vErrors = [err166];}else {vErrors.push(err166);}errors++;}if(func1(data77) < 1){const err167 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err167];}else {vErrors.push(err167);}errors++;}if(!pattern69.test(data77)){const err168 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/pattern",keyword:"pattern",params:{pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"},message:"must match pattern \""+"^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$"+"\""};if(vErrors === null){vErrors = [err168];}else {vErrors.push(err168);}errors++;}}else {const err169 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err169];}else {vErrors.push(err169);}errors++;}var _valid21 = _errs209 === errors;if(_valid21 && valid74){valid74 = false;passing2 = [passing2, 1];}else {if(_valid21){valid74 = true;passing2 = 1;}}if(!valid74){const err170 = {instancePath:instancePath+"/payload/files/" + i1+"/previousPath",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/previousPath/oneOf",keyword:"oneOf",params:{passingSchemas: passing2},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err170];}else {vErrors.push(err170);}errors++;}else {errors = _errs206;if(vErrors !== null){if(_errs206){vErrors.length = _errs206;}else {vErrors = null;}}}}if(data74.additions !== undefined){let data78 = data74.additions;if((!(((typeof data78 == "number") && (!(data78 % 1) && !isNaN(data78))) && (isFinite(data78)))) && (data78 !== null)){const err171 = {instancePath:instancePath+"/payload/files/" + i1+"/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/additions/type",keyword:"type",params:{type: schema172.properties.files.items.properties.additions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err171];}else {vErrors.push(err171);}errors++;}if((typeof data78 == "number") && (isFinite(data78))){if(data78 < 0 || isNaN(data78)){const err172 = {instancePath:instancePath+"/payload/files/" + i1+"/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/additions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err172];}else {vErrors.push(err172);}errors++;}}}if(data74.deletions !== undefined){let data79 = data74.deletions;if((!(((typeof data79 == "number") && (!(data79 % 1) && !isNaN(data79))) && (isFinite(data79)))) && (data79 !== null)){const err173 = {instancePath:instancePath+"/payload/files/" + i1+"/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/deletions/type",keyword:"type",params:{type: schema172.properties.files.items.properties.deletions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err173];}else {vErrors.push(err173);}errors++;}if((typeof data79 == "number") && (isFinite(data79))){if(data79 < 0 || isNaN(data79)){const err174 = {instancePath:instancePath+"/payload/files/" + i1+"/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/deletions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err174];}else {vErrors.push(err174);}errors++;}}}if(data74.binary !== undefined){if(typeof data74.binary !== "boolean"){const err175 = {instancePath:instancePath+"/payload/files/" + i1+"/binary",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/binary/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err175];}else {vErrors.push(err175);}errors++;}}if(data74.diff !== undefined){let data81 = data74.diff;if((typeof data81 !== "string") && (data81 !== null)){const err176 = {instancePath:instancePath+"/payload/files/" + i1+"/diff",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/diff/type",keyword:"type",params:{type: schema172.properties.files.items.properties.diff.type},message:"must be string,null"};if(vErrors === null){vErrors = [err176];}else {vErrors.push(err176);}errors++;}if(typeof data81 === "string"){if(func1(data81) > 262144){const err177 = {instancePath:instancePath+"/payload/files/" + i1+"/diff",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/properties/diff/maxLength",keyword:"maxLength",params:{limit: 262144},message:"must NOT have more than 262144 characters"};if(vErrors === null){vErrors = [err177];}else {vErrors.push(err177);}errors++;}}}}else {const err178 = {instancePath:instancePath+"/payload/files/" + i1,schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err178];}else {vErrors.push(err178);}errors++;}}}else {const err179 = {instancePath:instancePath+"/payload/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/files/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err179];}else {vErrors.push(err179);}errors++;}}if(data67.totals !== undefined){let data82 = data67.totals;if(data82 && typeof data82 == "object" && !Array.isArray(data82)){if(data82.files === undefined){const err180 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "files"},message:"must have required property '"+"files"+"'"};if(vErrors === null){vErrors = [err180];}else {vErrors.push(err180);}errors++;}if(data82.additions === undefined){const err181 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "additions"},message:"must have required property '"+"additions"+"'"};if(vErrors === null){vErrors = [err181];}else {vErrors.push(err181);}errors++;}if(data82.deletions === undefined){const err182 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/required",keyword:"required",params:{missingProperty: "deletions"},message:"must have required property '"+"deletions"+"'"};if(vErrors === null){vErrors = [err182];}else {vErrors.push(err182);}errors++;}for(const key6 in data82){if(!(((key6 === "files") || (key6 === "additions")) || (key6 === "deletions"))){const err183 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err183];}else {vErrors.push(err183);}errors++;}}if(data82.files !== undefined){let data83 = data82.files;if(!(((typeof data83 == "number") && (!(data83 % 1) && !isNaN(data83))) && (isFinite(data83)))){const err184 = {instancePath:instancePath+"/payload/totals/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/files/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err184];}else {vErrors.push(err184);}errors++;}if((typeof data83 == "number") && (isFinite(data83))){if(data83 < 0 || isNaN(data83)){const err185 = {instancePath:instancePath+"/payload/totals/files",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/files/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err185];}else {vErrors.push(err185);}errors++;}}}if(data82.additions !== undefined){let data84 = data82.additions;if((!(((typeof data84 == "number") && (!(data84 % 1) && !isNaN(data84))) && (isFinite(data84)))) && (data84 !== null)){const err186 = {instancePath:instancePath+"/payload/totals/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/additions/type",keyword:"type",params:{type: schema172.properties.totals.properties.additions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err186];}else {vErrors.push(err186);}errors++;}if((typeof data84 == "number") && (isFinite(data84))){if(data84 < 0 || isNaN(data84)){const err187 = {instancePath:instancePath+"/payload/totals/additions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/additions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err187];}else {vErrors.push(err187);}errors++;}}}if(data82.deletions !== undefined){let data85 = data82.deletions;if((!(((typeof data85 == "number") && (!(data85 % 1) && !isNaN(data85))) && (isFinite(data85)))) && (data85 !== null)){const err188 = {instancePath:instancePath+"/payload/totals/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/deletions/type",keyword:"type",params:{type: schema172.properties.totals.properties.deletions.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err188];}else {vErrors.push(err188);}errors++;}if((typeof data85 == "number") && (isFinite(data85))){if(data85 < 0 || isNaN(data85)){const err189 = {instancePath:instancePath+"/payload/totals/deletions",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/properties/deletions/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err189];}else {vErrors.push(err189);}errors++;}}}}else {const err190 = {instancePath:instancePath+"/payload/totals",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/totals/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err190];}else {vErrors.push(err190);}errors++;}}if(data67.patchArtifactRef !== undefined){let data86 = data67.patchArtifactRef;if((typeof data86 !== "string") && (data86 !== null)){const err191 = {instancePath:instancePath+"/payload/patchArtifactRef",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/patchArtifactRef/type",keyword:"type",params:{type: schema172.properties.patchArtifactRef.type},message:"must be string,null"};if(vErrors === null){vErrors = [err191];}else {vErrors.push(err191);}errors++;}if(typeof data86 === "string"){if(func1(data86) > 2048){const err192 = {instancePath:instancePath+"/payload/patchArtifactRef",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/properties/patchArtifactRef/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err192];}else {vErrors.push(err192);}errors++;}}}}else {const err193 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err193];}else {vErrors.push(err193);}errors++;}if(data67 && typeof data67 == "object" && !Array.isArray(data67)){if(data67.complete !== undefined){if(true !== data67.complete){const err194 = {instancePath:instancePath+"/payload/complete",schemaPath:"#/allOf/18/then/properties/payload/allOf/1/properties/complete/const",keyword:"const",params:{allowedValue: true},message:"must be equal to constant"};if(vErrors === null){vErrors = [err194];}else {vErrors.push(err194);}errors++;}}}else {const err195 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/18/then/properties/payload/allOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err195];}else {vErrors.push(err195);}errors++;}}}var _valid20 = _errs183 === errors;valid65 = _valid20;if(valid65){var props18 = {};props18.payload = true;props18.eventType = true;}}if(!valid65){const err196 = {instancePath,schemaPath:"#/allOf/18/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err196];}else {vErrors.push(err196);}errors++;}if(props0 !== true && props18 !== undefined){if(props18 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props18);}}const _errs234 = errors;let valid77 = true;const _errs235 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data88 = data.eventType;if(!((data88 === "semantic_tool.input") || (data88 === "mcp_app.tool_input"))){const err197 = {};if(vErrors === null){vErrors = [err197];}else {vErrors.push(err197);}errors++;}}}var _valid22 = _errs235 === errors;errors = _errs234;if(vErrors !== null){if(_errs234){vErrors.length = _errs234;}else {vErrors = null;}}if(_valid22){const _errs237 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data89 = data.payload;if(data89 && typeof data89 == "object" && !Array.isArray(data89)){if(data89.semantic_tool !== undefined){let data90 = data89.semantic_tool;if(!(validate92(data90, {instancePath:instancePath+"/payload/semantic_tool",parentData:data89,parentDataProperty:"semantic_tool",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate92.errors : vErrors.concat(validate92.errors);errors = vErrors.length;}if(data90 && typeof data90 == "object" && !Array.isArray(data90)){if(data90.phase !== undefined){if("input" !== data90.phase){const err198 = {instancePath:instancePath+"/payload/semantic_tool/phase",schemaPath:"#/allOf/19/then/properties/payload/properties/semantic_tool/allOf/1/properties/phase/const",keyword:"const",params:{allowedValue: "input"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err198];}else {vErrors.push(err198);}errors++;}}}else {const err199 = {instancePath:instancePath+"/payload/semantic_tool",schemaPath:"#/allOf/19/then/properties/payload/properties/semantic_tool/allOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err199];}else {vErrors.push(err199);}errors++;}}}else {const err200 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/19/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err200];}else {vErrors.push(err200);}errors++;}}}var _valid22 = _errs237 === errors;valid77 = _valid22;if(valid77){var props19 = {};props19.payload = true;props19.eventType = true;}}if(!valid77){const err201 = {instancePath,schemaPath:"#/allOf/19/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err201];}else {vErrors.push(err201);}errors++;}if(props0 !== true && props19 !== undefined){if(props19 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props19);}}const _errs247 = errors;let valid83 = true;const _errs248 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data92 = data.eventType;if(!((data92 === "semantic_tool.result") || (data92 === "mcp_app.tool_result"))){const err202 = {};if(vErrors === null){vErrors = [err202];}else {vErrors.push(err202);}errors++;}}}var _valid23 = _errs248 === errors;errors = _errs247;if(vErrors !== null){if(_errs247){vErrors.length = _errs247;}else {vErrors = null;}}if(_valid23){const _errs250 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data93 = data.payload;if(data93 && typeof data93 == "object" && !Array.isArray(data93)){if(data93.semantic_tool !== undefined){let data94 = data93.semantic_tool;if(!(validate92(data94, {instancePath:instancePath+"/payload/semantic_tool",parentData:data93,parentDataProperty:"semantic_tool",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate92.errors : vErrors.concat(validate92.errors);errors = vErrors.length;}if(data94 && typeof data94 == "object" && !Array.isArray(data94)){if(data94.phase !== undefined){if("result" !== data94.phase){const err203 = {instancePath:instancePath+"/payload/semantic_tool/phase",schemaPath:"#/allOf/20/then/properties/payload/properties/semantic_tool/allOf/1/properties/phase/const",keyword:"const",params:{allowedValue: "result"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err203];}else {vErrors.push(err203);}errors++;}}}else {const err204 = {instancePath:instancePath+"/payload/semantic_tool",schemaPath:"#/allOf/20/then/properties/payload/properties/semantic_tool/allOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err204];}else {vErrors.push(err204);}errors++;}}}else {const err205 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/20/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err205];}else {vErrors.push(err205);}errors++;}}}var _valid23 = _errs250 === errors;valid83 = _valid23;if(valid83){var props20 = {};props20.payload = true;props20.eventType = true;}}if(!valid83){const err206 = {instancePath,schemaPath:"#/allOf/20/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err206];}else {vErrors.push(err206);}errors++;}if(props0 !== true && props20 !== undefined){if(props20 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props20);}}const _errs260 = errors;let valid89 = true;const _errs261 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("semantic_tool.reconciled" !== data.eventType){const err207 = {};if(vErrors === null){vErrors = [err207];}else {vErrors.push(err207);}errors++;}}}var _valid24 = _errs261 === errors;errors = _errs260;if(vErrors !== null){if(_errs260){vErrors.length = _errs260;}else {vErrors = null;}}if(_valid24){const _errs263 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data97 = data.payload;if(data97 && typeof data97 == "object" && !Array.isArray(data97)){if(data97.semantic_tool !== undefined){let data98 = data97.semantic_tool;if(!(validate92(data98, {instancePath:instancePath+"/payload/semantic_tool",parentData:data97,parentDataProperty:"semantic_tool",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate92.errors : vErrors.concat(validate92.errors);errors = vErrors.length;}if(data98 && typeof data98 == "object" && !Array.isArray(data98)){if(data98.phase !== undefined){if("reconciled" !== data98.phase){const err208 = {instancePath:instancePath+"/payload/semantic_tool/phase",schemaPath:"#/allOf/21/then/properties/payload/properties/semantic_tool/allOf/1/properties/phase/const",keyword:"const",params:{allowedValue: "reconciled"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err208];}else {vErrors.push(err208);}errors++;}}}else {const err209 = {instancePath:instancePath+"/payload/semantic_tool",schemaPath:"#/allOf/21/then/properties/payload/properties/semantic_tool/allOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err209];}else {vErrors.push(err209);}errors++;}}}else {const err210 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/21/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err210];}else {vErrors.push(err210);}errors++;}}}var _valid24 = _errs263 === errors;valid89 = _valid24;if(valid89){var props21 = {};props21.payload = true;props21.eventType = true;}}if(!valid89){const err211 = {instancePath,schemaPath:"#/allOf/21/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err211];}else {vErrors.push(err211);}errors++;}if(props0 !== true && props21 !== undefined){if(props21 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props21);}}const _errs273 = errors;let valid95 = true;const _errs274 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("run.terminal" !== data.eventType){const err212 = {};if(vErrors === null){vErrors = [err212];}else {vErrors.push(err212);}errors++;}}}var _valid25 = _errs274 === errors;errors = _errs273;if(vErrors !== null){if(_errs273){vErrors.length = _errs273;}else {vErrors = null;}}if(_valid25){const _errs276 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){if(!(validate104(data.payload, {instancePath:instancePath+"/payload",parentData:data,parentDataProperty:"payload",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate104.errors : vErrors.concat(validate104.errors);errors = vErrors.length;}}}var _valid25 = _errs276 === errors;valid95 = _valid25;if(valid95){var props22 = {};props22.payload = true;props22.eventType = true;}}if(!valid95){const err213 = {instancePath,schemaPath:"#/allOf/22/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err213];}else {vErrors.push(err213);}errors++;}if(props0 !== true && props22 !== undefined){if(props22 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props22);}}const _errs279 = errors;let valid98 = true;const _errs280 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("run.result.proposed" !== data.eventType){const err214 = {};if(vErrors === null){vErrors = [err214];}else {vErrors.push(err214);}errors++;}}}var _valid26 = _errs280 === errors;errors = _errs279;if(vErrors !== null){if(_errs279){vErrors.length = _errs279;}else {vErrors = null;}}if(_valid26){const _errs282 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data103 = data.payload;const _errs287 = errors;let valid103 = true;const _errs288 = errors;if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.reportedWorkDisposition !== undefined){if("blocked" !== data103.reportedWorkDisposition){const err215 = {};if(vErrors === null){vErrors = [err215];}else {vErrors.push(err215);}errors++;}}}var _valid27 = _errs288 === errors;errors = _errs287;if(vErrors !== null){if(_errs287){vErrors.length = _errs287;}else {vErrors = null;}}if(_valid27){const _errs290 = errors;if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.blocker === undefined){const err216 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/allOf/0/then/required",keyword:"required",params:{missingProperty: "blocker"},message:"must have required property '"+"blocker"+"'"};if(vErrors === null){vErrors = [err216];}else {vErrors.push(err216);}errors++;}}var _valid27 = _errs290 === errors;valid103 = _valid27;}if(!valid103){const err217 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err217];}else {vErrors.push(err217);}errors++;}const _errs292 = errors;let valid105 = true;const _errs293 = errors;if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.reportedWorkDisposition !== undefined){if("yielded" !== data103.reportedWorkDisposition){const err218 = {};if(vErrors === null){vErrors = [err218];}else {vErrors.push(err218);}errors++;}}}var _valid28 = _errs293 === errors;errors = _errs292;if(vErrors !== null){if(_errs292){vErrors.length = _errs292;}else {vErrors = null;}}if(_valid28){const _errs295 = errors;if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.continuation === undefined){const err219 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/allOf/1/then/required",keyword:"required",params:{missingProperty: "continuation"},message:"must have required property '"+"continuation"+"'"};if(vErrors === null){vErrors = [err219];}else {vErrors.push(err219);}errors++;}}var _valid28 = _errs295 === errors;valid105 = _valid28;}if(!valid105){const err220 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err220];}else {vErrors.push(err220);}errors++;}if(data103 && typeof data103 == "object" && !Array.isArray(data103)){if(data103.schema === undefined){const err221 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err221];}else {vErrors.push(err221);}errors++;}if(data103.reportedWorkDisposition === undefined){const err222 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "reportedWorkDisposition"},message:"must have required property '"+"reportedWorkDisposition"+"'"};if(vErrors === null){vErrors = [err222];}else {vErrors.push(err222);}errors++;}if(data103.summary === undefined){const err223 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "summary"},message:"must have required property '"+"summary"+"'"};if(vErrors === null){vErrors = [err223];}else {vErrors.push(err223);}errors++;}if(data103.completionClaim === undefined){const err224 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "completionClaim"},message:"must have required property '"+"completionClaim"+"'"};if(vErrors === null){vErrors = [err224];}else {vErrors.push(err224);}errors++;}if(data103.evidence === undefined){const err225 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "evidence"},message:"must have required property '"+"evidence"+"'"};if(vErrors === null){vErrors = [err225];}else {vErrors.push(err225);}errors++;}if(data103.verification === undefined){const err226 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "verification"},message:"must have required property '"+"verification"+"'"};if(vErrors === null){vErrors = [err226];}else {vErrors.push(err226);}errors++;}if(data103.attentionRequests === undefined){const err227 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "attentionRequests"},message:"must have required property '"+"attentionRequests"+"'"};if(vErrors === null){vErrors = [err227];}else {vErrors.push(err227);}errors++;}if(data103.artifacts === undefined){const err228 = {instancePath:instancePath+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/required",keyword:"required",params:{missingProperty: "artifacts"},message:"must have required property '"+"artifacts"+"'"};if(vErrors === null){vErrors = [err228];}else {vErrors.push(err228);}errors++;}if(data103.schema !== undefined){if("paperclip.run_result.v1" !== data103.schema){const err229 = {instancePath:instancePath+"/payload/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.run_result.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err229];}else {vErrors.push(err229);}errors++;}}if(data103.reportedWorkDisposition !== undefined){let data107 = data103.reportedWorkDisposition;if(!((((data107 === "done") || (data107 === "blocked")) || (data107 === "needs_review")) || (data107 === "yielded"))){const err230 = {instancePath:instancePath+"/payload/reportedWorkDisposition",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/reportedWorkDisposition/enum",keyword:"enum",params:{allowedValues: schema199.properties.reportedWorkDisposition.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err230];}else {vErrors.push(err230);}errors++;}}if(data103.summary !== undefined){let data108 = data103.summary;if(typeof data108 === "string"){if(func1(data108) > 12000){const err231 = {instancePath:instancePath+"/payload/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/maxLength",keyword:"maxLength",params:{limit: 12000},message:"must NOT have more than 12000 characters"};if(vErrors === null){vErrors = [err231];}else {vErrors.push(err231);}errors++;}if(func1(data108) < 1){const err232 = {instancePath:instancePath+"/payload/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err232];}else {vErrors.push(err232);}errors++;}}else {const err233 = {instancePath:instancePath+"/payload/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err233];}else {vErrors.push(err233);}errors++;}}if(data103.completionClaim !== undefined){let data109 = data103.completionClaim;if(data109 && typeof data109 == "object" && !Array.isArray(data109)){if(data109.contractRevision === undefined){const err234 = {instancePath:instancePath+"/payload/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "contractRevision"},message:"must have required property '"+"contractRevision"+"'"};if(vErrors === null){vErrors = [err234];}else {vErrors.push(err234);}errors++;}if(data109.objectiveSatisfied === undefined){const err235 = {instancePath:instancePath+"/payload/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "objectiveSatisfied"},message:"must have required property '"+"objectiveSatisfied"+"'"};if(vErrors === null){vErrors = [err235];}else {vErrors.push(err235);}errors++;}if(data109.criteria === undefined){const err236 = {instancePath:instancePath+"/payload/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "criteria"},message:"must have required property '"+"criteria"+"'"};if(vErrors === null){vErrors = [err236];}else {vErrors.push(err236);}errors++;}if(data109.remainingWork === undefined){const err237 = {instancePath:instancePath+"/payload/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "remainingWork"},message:"must have required property '"+"remainingWork"+"'"};if(vErrors === null){vErrors = [err237];}else {vErrors.push(err237);}errors++;}if(data109.contractRevision !== undefined){let data110 = data109.contractRevision;if(typeof data110 === "string"){if(func1(data110) < 1){const err238 = {instancePath:instancePath+"/payload/completionClaim/contractRevision",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/contractRevision/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err238];}else {vErrors.push(err238);}errors++;}}else {const err239 = {instancePath:instancePath+"/payload/completionClaim/contractRevision",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/contractRevision/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err239];}else {vErrors.push(err239);}errors++;}}if(data109.objectiveSatisfied !== undefined){if(typeof data109.objectiveSatisfied !== "boolean"){const err240 = {instancePath:instancePath+"/payload/completionClaim/objectiveSatisfied",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/objectiveSatisfied/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err240];}else {vErrors.push(err240);}errors++;}}if(data109.criteria !== undefined){let data112 = data109.criteria;if(Array.isArray(data112)){const len2 = data112.length;for(let i2=0; i2 160){const err329 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err329];}else {vErrors.push(err329);}errors++;}if(func1(data155) < 1){const err330 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err330];}else {vErrors.push(err330);}errors++;}}else {const err331 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err331];}else {vErrors.push(err331);}errors++;}}if(data.sourceSeq !== undefined){let data156 = data.sourceSeq;if(!(((typeof data156 == "number") && (!(data156 % 1) && !isNaN(data156))) && (isFinite(data156)))){const err332 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err332];}else {vErrors.push(err332);}errors++;}if((typeof data156 == "number") && (isFinite(data156))){if(data156 > 9007199254740991 || isNaN(data156)){const err333 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/maximum",keyword:"maximum",params:{comparison: "<=", limit: 9007199254740991},message:"must be <= 9007199254740991"};if(vErrors === null){vErrors = [err333];}else {vErrors.push(err333);}errors++;}if(data156 < 1 || isNaN(data156)){const err334 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err334];}else {vErrors.push(err334);}errors++;}}}if(data.sourceInstanceId !== undefined){let data157 = data.sourceInstanceId;if(typeof data157 === "string"){if(func1(data157) > 160){const err335 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err335];}else {vErrors.push(err335);}errors++;}if(func1(data157) < 1){const err336 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err336];}else {vErrors.push(err336);}errors++;}}else {const err337 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err337];}else {vErrors.push(err337);}errors++;}}if(data.sourceKind !== undefined){let data158 = data.sourceKind;if(!((data158 === "runner") || (data158 === "control_plane"))){const err338 = {instancePath:instancePath+"/sourceKind",schemaPath:"#/properties/sourceKind/enum",keyword:"enum",params:{allowedValues: schema47.properties.sourceKind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err338];}else {vErrors.push(err338);}errors++;}}if(data.runId !== undefined){let data159 = data.runId;if(typeof data159 === "string"){if(func1(data159) > 160){const err339 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err339];}else {vErrors.push(err339);}errors++;}if(func1(data159) < 1){const err340 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err340];}else {vErrors.push(err340);}errors++;}}else {const err341 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err341];}else {vErrors.push(err341);}errors++;}}if(data.normalizedSessionId !== undefined){let data160 = data.normalizedSessionId;if(typeof data160 === "string"){if(func1(data160) > 160){const err342 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err342];}else {vErrors.push(err342);}errors++;}if(func1(data160) < 1){const err343 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err343];}else {vErrors.push(err343);}errors++;}}else {const err344 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err344];}else {vErrors.push(err344);}errors++;}}if(data.turnId !== undefined){let data161 = data.turnId;if(typeof data161 === "string"){if(func1(data161) > 240){const err345 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err345];}else {vErrors.push(err345);}errors++;}if(func1(data161) < 1){const err346 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err346];}else {vErrors.push(err346);}errors++;}}else {const err347 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err347];}else {vErrors.push(err347);}errors++;}}if(data.itemId !== undefined){let data162 = data.itemId;if(typeof data162 === "string"){if(func1(data162) > 240){const err348 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err348];}else {vErrors.push(err348);}errors++;}if(func1(data162) < 1){const err349 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err349];}else {vErrors.push(err349);}errors++;}}else {const err350 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err350];}else {vErrors.push(err350);}errors++;}}if(data.eventType !== undefined){let data163 = data.eventType;if(!(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((data163 === "runner.connected") || (data163 === "runner.reconnected")) || (data163 === "runner.reconciled")) || (data163 === "runner.disconnected")) || (data163 === "runner.draining")) || (data163 === "runner.backpressure")) || (data163 === "runner.suspending")) || (data163 === "runner.suspended")) || (data163 === "runner.stopped")) || (data163 === "runner.diagnostic")) || (data163 === "runtime.phase.changed")) || (data163 === "sandbox.metric")) || (data163 === "workspace.ready")) || (data163 === "workspace.change.updated")) || (data163 === "workspace.diff.recorded")) || (data163 === "workspace.file.referenced")) || (data163 === "harness.starting")) || (data163 === "harness.ready")) || (data163 === "harness.exited")) || (data163 === "harness.diagnostic")) || (data163 === "plan.updated")) || (data163 === "tool.execution.started")) || (data163 === "tool.execution.progressed")) || (data163 === "tool.execution.completed")) || (data163 === "research.started")) || (data163 === "research.progressed")) || (data163 === "research.completed")) || (data163 === "delegation.started")) || (data163 === "delegation.updated")) || (data163 === "delegation.completed")) || (data163 === "model.route.changed")) || (data163 === "model.verification.updated")) || (data163 === "context.compacted")) || (data163 === "artifact.viewed")) || (data163 === "artifact.generated")) || (data163 === "review.mode.changed")) || (data163 === "hook.started")) || (data163 === "hook.completed")) || (data163 === "memory.citation.referenced")) || (data163 === "safety.review.started")) || (data163 === "safety.review.completed")) || (data163 === "terminal.input.sent")) || (data163 === "wait.started")) || (data163 === "wait.completed")) || (data163 === "provider.notice.recorded")) || (data163 === "session.starting")) || (data163 === "session.started")) || (data163 === "session.resuming")) || (data163 === "session.resumed")) || (data163 === "session.reconciled")) || (data163 === "session.updated")) || (data163 === "session.closed")) || (data163 === "session.failed")) || (data163 === "turn.submitted")) || (data163 === "turn.accepted")) || (data163 === "turn.started")) || (data163 === "turn.completed")) || (data163 === "turn.failed")) || (data163 === "turn.interrupted")) || (data163 === "turn.cancelled")) || (data163 === "item.started")) || (data163 === "item.delta")) || (data163 === "item.completed")) || (data163 === "item.failed")) || (data163 === "usage.reported")) || (data163 === "semantic_tool.input")) || (data163 === "semantic_tool.result")) || (data163 === "semantic_tool.reconciled")) || (data163 === "mcp_app.discovered")) || (data163 === "mcp_app.resource.resolved")) || (data163 === "mcp_app.initializing")) || (data163 === "mcp_app.ready")) || (data163 === "mcp_app.tool_input")) || (data163 === "mcp_app.tool_result")) || (data163 === "mcp_app.action.requested")) || (data163 === "mcp_app.action.resolved")) || (data163 === "mcp_app.host_context.changed")) || (data163 === "mcp_app.failed")) || (data163 === "mcp_app.teardown")) || (data163 === "runtime_request.created")) || (data163 === "runtime_request.resolved")) || (data163 === "runtime_request.expired")) || (data163 === "runtime_request.cancelled")) || (data163 === "interaction.request.proposed")) || (data163 === "interaction.request.materialized")) || (data163 === "interaction.request.rejected")) || (data163 === "interaction.response.progressed")) || (data163 === "interaction.response.resolved")) || (data163 === "interaction.response.delivered")) || (data163 === "run.attached")) || (data163 === "run.detached")) || (data163 === "run.result.proposed")) || (data163 === "run.result.accepted")) || (data163 === "run.result.rejected")) || (data163 === "attention.request.proposed")) || (data163 === "attention.request.routed")) || (data163 === "attention.request.resolved")) || (data163 === "attention.request.expired")) || (data163 === "attention.request.superseded")) || (data163 === "work.assessment.recorded")) || (data163 === "issue.status.decision.recorded")) || (data163 === "issue.status.decision.applied")) || (data163 === "issue.status.decision.rejected")) || (data163 === "issue.status.decision.superseded")) || (data163 === "run.terminal"))){const err351 = {instancePath:instancePath+"/eventType",schemaPath:"#/properties/eventType/enum",keyword:"enum",params:{allowedValues: schema47.properties.eventType.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err351];}else {vErrors.push(err351);}errors++;}}if(data.schemaVersion !== undefined){if(1 !== data.schemaVersion){const err352 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err352];}else {vErrors.push(err352);}errors++;}}if(data.priority !== undefined){let data165 = data.priority;if(!(((data165 === 0) || (data165 === 1)) || (data165 === 2))){const err353 = {instancePath:instancePath+"/priority",schemaPath:"#/properties/priority/enum",keyword:"enum",params:{allowedValues: schema47.properties.priority.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err353];}else {vErrors.push(err353);}errors++;}}if(data.emittedAt !== undefined){let data166 = data.emittedAt;if(typeof data166 === "string"){if(!(formats0.test(data166))){const err354 = {instancePath:instancePath+"/emittedAt",schemaPath:"#/properties/emittedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err354];}else {vErrors.push(err354);}errors++;}}else {const err355 = {instancePath:instancePath+"/emittedAt",schemaPath:"#/properties/emittedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err355];}else {vErrors.push(err355);}errors++;}}if(data.observedAt !== undefined){let data167 = data.observedAt;if(typeof data167 === "string"){if(!(formats0.test(data167))){const err356 = {instancePath:instancePath+"/observedAt",schemaPath:"#/properties/observedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err356];}else {vErrors.push(err356);}errors++;}}else {const err357 = {instancePath:instancePath+"/observedAt",schemaPath:"#/properties/observedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err357];}else {vErrors.push(err357);}errors++;}}if(data.payload !== undefined){let data168 = data.payload;if(data168 && typeof data168 == "object" && !Array.isArray(data168)){if(data168.channel !== undefined){let data169 = data168.channel;if(!(((((data169 === "progress") || (data169 === "final")) || (data169 === "summary")) || (data169 === "detail")) || (data169 === "unknown"))){const err358 = {instancePath:instancePath+"/payload/channel",schemaPath:"#/properties/payload/properties/channel/enum",keyword:"enum",params:{allowedValues: schema47.properties.payload.properties.channel.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err358];}else {vErrors.push(err358);}errors++;}}if(data168.providerPhase !== undefined){if(typeof data168.providerPhase !== "string"){const err359 = {instancePath:instancePath+"/payload/providerPhase",schemaPath:"#/properties/payload/properties/providerPhase/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err359];}else {vErrors.push(err359);}errors++;}}if(data168.providerMethod !== undefined){if(typeof data168.providerMethod !== "string"){const err360 = {instancePath:instancePath+"/payload/providerMethod",schemaPath:"#/properties/payload/properties/providerMethod/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err360];}else {vErrors.push(err360);}errors++;}}}else {const err361 = {instancePath:instancePath+"/payload",schemaPath:"#/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err361];}else {vErrors.push(err361);}errors++;}}if(data.debug !== undefined){let data172 = data.debug;if(data172 && typeof data172 == "object" && !Array.isArray(data172)){}else {const err362 = {instancePath:instancePath+"/debug",schemaPath:"#/properties/debug/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err362];}else {vErrors.push(err362);}errors++;}}}else {const err363 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err363];}else {vErrors.push(err363);}errors++;}validate26.errors = vErrors;return errors === 0;}validate26.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema200 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v2/event.schema.json","title":"PRP v2 native event","type":"object","required":["schema","sourceEventId","sourceSeq","sourceInstanceId","sourceKind","runId","eventType","schemaVersion","priority","emittedAt","payload"],"properties":{"schema":{"const":"paperclip.prp.event.v2"},"sourceEventId":{"type":"string","minLength":1,"maxLength":160},"sourceSeq":{"type":"integer","minimum":1,"maximum":9007199254740991},"sourceInstanceId":{"type":"string","minLength":1,"maxLength":160},"sourceKind":{"enum":["runner","control_plane"]},"runId":{"type":"string","minLength":1,"maxLength":160},"normalizedSessionId":{"type":"string","minLength":1,"maxLength":160},"turnId":{"type":"string","minLength":1,"maxLength":160},"itemId":{"type":"string","minLength":1,"maxLength":160},"eventType":{"enum":["runner.connected","runner.reconnected","runner.reconciled","runner.disconnected","runner.draining","runner.suspending","runner.suspended","runner.stopped","runner.diagnostic","runtime.phase.changed","sandbox.metric","workspace.ready","workspace.change.updated","workspace.diff.recorded","workspace.file.referenced","harness.starting","harness.ready","harness.exited","harness.diagnostic","plan.updated","tool.execution.started","tool.execution.progressed","tool.execution.completed","research.started","research.progressed","research.completed","delegation.started","delegation.updated","delegation.completed","model.route.changed","model.verification.updated","context.compacted","artifact.viewed","artifact.generated","review.mode.changed","hook.started","hook.completed","memory.citation.referenced","safety.review.started","safety.review.completed","terminal.input.sent","wait.started","wait.completed","provider.notice.recorded","session.starting","session.started","session.resuming","session.resumed","session.reconciled","session.updated","session.closed","session.failed","session.capabilities.updated","session.goal.snapshot","session.goal.updated","session.goal.cleared","turn.submitted","turn.accepted","turn.started","turn.completed","turn.failed","turn.interrupted","turn.cancelled","item.started","item.delta","item.completed","item.failed","usage.reported","semantic_tool.input","semantic_tool.result","mcp_app.discovered","mcp_app.resource.resolved","mcp_app.initializing","mcp_app.ready","mcp_app.tool_input","mcp_app.tool_result","mcp_app.action.requested","mcp_app.action.resolved","mcp_app.host_context.changed","mcp_app.failed","mcp_app.teardown","runtime_request.created","runtime_request.resolved","runtime_request.expired","runtime_request.cancelled","interaction.request.proposed","interaction.request.materialized","interaction.request.rejected","interaction.response.progressed","interaction.response.resolved","interaction.response.delivered","run.attached","run.detached","run.result.proposed","run.result.accepted","run.result.rejected","attention.request.proposed","attention.request.routed","attention.request.resolved","attention.request.expired","attention.request.superseded","work.assessment.recorded","issue.status.decision.recorded","issue.status.decision.applied","issue.status.decision.rejected","issue.status.decision.superseded","run.terminal"]},"schemaVersion":{"const":2},"priority":{"enum":[0,1,2]},"emittedAt":{"type":"string","format":"date-time"},"observedAt":{"type":"string","format":"date-time"},"payload":{"type":"object","additionalProperties":true},"debug":{"type":"object","additionalProperties":true}},"allOf":[{"if":{"properties":{"eventType":{"enum":["session.goal.snapshot","session.goal.updated"]}}},"then":{"properties":{"payload":{"type":"object","required":["goal"],"properties":{"goal":{"oneOf":[{"$ref":"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot"},{"type":"null"}]}},"additionalProperties":true}}}},{"if":{"properties":{"eventType":{"const":"session.capabilities.updated"}}},"then":{"properties":{"payload":{"type":"object","required":["sessionGoals"],"properties":{"sessionGoals":{"$ref":"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability"},"turnControls":{"$ref":"https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json#/$defs/turnControls"}},"additionalProperties":true}}}}],"additionalProperties":true};const schema201 = {"type":"object","required":["objective","status","tokenBudget","tokensUsed","elapsedSeconds","iterations","lastReason","createdAt","updatedAt","completedAt","workingNow"],"properties":{"objective":{"type":"string","minLength":1,"maxLength":4000},"status":{"enum":["active","paused","blocked","limited","usage_limited","budget_limited","complete"]},"tokenBudget":{"type":["integer","null"],"minimum":1},"tokensUsed":{"type":["integer","null"],"minimum":0},"elapsedSeconds":{"type":["number","null"],"minimum":0},"iterations":{"type":["integer","null"],"minimum":0},"lastReason":{"type":["string","null"],"maxLength":4000},"createdAt":{"type":["string","null"],"format":"date-time"},"updatedAt":{"type":["string","null"],"format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"workingNow":{"type":"boolean"}},"additionalProperties":true};const schema204 = {"type":"object","required":["steering","queuedFollowUp"],"properties":{"steering":{"type":"boolean"},"queuedFollowUp":{"type":"boolean"}},"additionalProperties":false};function validate109(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v2/event.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate109.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){let data0 = data.eventType;if(!((data0 === "session.goal.snapshot") || (data0 === "session.goal.updated"))){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data1 = data.payload;if(data1 && typeof data1 == "object" && !Array.isArray(data1)){if(data1.goal === undefined){const err1 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/0/then/properties/payload/required",keyword:"required",params:{missingProperty: "goal"},message:"must have required property '"+"goal"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data1.goal !== undefined){let data2 = data1.goal;const _errs10 = errors;let valid5 = false;let passing0 = null;const _errs11 = errors;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.objective === undefined){const err2 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "objective"},message:"must have required property '"+"objective"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data2.status === undefined){const err3 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data2.tokenBudget === undefined){const err4 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "tokenBudget"},message:"must have required property '"+"tokenBudget"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data2.tokensUsed === undefined){const err5 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "tokensUsed"},message:"must have required property '"+"tokensUsed"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data2.elapsedSeconds === undefined){const err6 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "elapsedSeconds"},message:"must have required property '"+"elapsedSeconds"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data2.iterations === undefined){const err7 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "iterations"},message:"must have required property '"+"iterations"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data2.lastReason === undefined){const err8 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "lastReason"},message:"must have required property '"+"lastReason"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data2.createdAt === undefined){const err9 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "createdAt"},message:"must have required property '"+"createdAt"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data2.updatedAt === undefined){const err10 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "updatedAt"},message:"must have required property '"+"updatedAt"+"'"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(data2.completedAt === undefined){const err11 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "completedAt"},message:"must have required property '"+"completedAt"+"'"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(data2.workingNow === undefined){const err12 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/required",keyword:"required",params:{missingProperty: "workingNow"},message:"must have required property '"+"workingNow"+"'"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(data2.objective !== undefined){let data3 = data2.objective;if(typeof data3 === "string"){if(func1(data3) > 4000){const err13 = {instancePath:instancePath+"/payload/goal/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/objective/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data3) < 1){const err14 = {instancePath:instancePath+"/payload/goal/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/objective/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/payload/goal/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/objective/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.status !== undefined){let data4 = data2.status;if(!(((((((data4 === "active") || (data4 === "paused")) || (data4 === "blocked")) || (data4 === "limited")) || (data4 === "usage_limited")) || (data4 === "budget_limited")) || (data4 === "complete"))){const err16 = {instancePath:instancePath+"/payload/goal/status",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/status/enum",keyword:"enum",params:{allowedValues: schema201.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.tokenBudget !== undefined){let data5 = data2.tokenBudget;if((!(((typeof data5 == "number") && (!(data5 % 1) && !isNaN(data5))) && (isFinite(data5)))) && (data5 !== null)){const err17 = {instancePath:instancePath+"/payload/goal/tokenBudget",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/tokenBudget/type",keyword:"type",params:{type: schema201.properties.tokenBudget.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if((typeof data5 == "number") && (isFinite(data5))){if(data5 < 1 || isNaN(data5)){const err18 = {instancePath:instancePath+"/payload/goal/tokenBudget",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/tokenBudget/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}}if(data2.tokensUsed !== undefined){let data6 = data2.tokensUsed;if((!(((typeof data6 == "number") && (!(data6 % 1) && !isNaN(data6))) && (isFinite(data6)))) && (data6 !== null)){const err19 = {instancePath:instancePath+"/payload/goal/tokensUsed",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/tokensUsed/type",keyword:"type",params:{type: schema201.properties.tokensUsed.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if((typeof data6 == "number") && (isFinite(data6))){if(data6 < 0 || isNaN(data6)){const err20 = {instancePath:instancePath+"/payload/goal/tokensUsed",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/tokensUsed/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}if(data2.elapsedSeconds !== undefined){let data7 = data2.elapsedSeconds;if((!((typeof data7 == "number") && (isFinite(data7)))) && (data7 !== null)){const err21 = {instancePath:instancePath+"/payload/goal/elapsedSeconds",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/elapsedSeconds/type",keyword:"type",params:{type: schema201.properties.elapsedSeconds.type},message:"must be number,null"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if((typeof data7 == "number") && (isFinite(data7))){if(data7 < 0 || isNaN(data7)){const err22 = {instancePath:instancePath+"/payload/goal/elapsedSeconds",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/elapsedSeconds/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}}if(data2.iterations !== undefined){let data8 = data2.iterations;if((!(((typeof data8 == "number") && (!(data8 % 1) && !isNaN(data8))) && (isFinite(data8)))) && (data8 !== null)){const err23 = {instancePath:instancePath+"/payload/goal/iterations",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/iterations/type",keyword:"type",params:{type: schema201.properties.iterations.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if((typeof data8 == "number") && (isFinite(data8))){if(data8 < 0 || isNaN(data8)){const err24 = {instancePath:instancePath+"/payload/goal/iterations",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/iterations/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}}if(data2.lastReason !== undefined){let data9 = data2.lastReason;if((typeof data9 !== "string") && (data9 !== null)){const err25 = {instancePath:instancePath+"/payload/goal/lastReason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/lastReason/type",keyword:"type",params:{type: schema201.properties.lastReason.type},message:"must be string,null"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(typeof data9 === "string"){if(func1(data9) > 4000){const err26 = {instancePath:instancePath+"/payload/goal/lastReason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/lastReason/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}}if(data2.createdAt !== undefined){let data10 = data2.createdAt;if((typeof data10 !== "string") && (data10 !== null)){const err27 = {instancePath:instancePath+"/payload/goal/createdAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/createdAt/type",keyword:"type",params:{type: schema201.properties.createdAt.type},message:"must be string,null"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(typeof data10 === "string"){if(!(formats0.test(data10))){const err28 = {instancePath:instancePath+"/payload/goal/createdAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/createdAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}}if(data2.updatedAt !== undefined){let data11 = data2.updatedAt;if((typeof data11 !== "string") && (data11 !== null)){const err29 = {instancePath:instancePath+"/payload/goal/updatedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/updatedAt/type",keyword:"type",params:{type: schema201.properties.updatedAt.type},message:"must be string,null"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(typeof data11 === "string"){if(!(formats0.test(data11))){const err30 = {instancePath:instancePath+"/payload/goal/updatedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/updatedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}}if(data2.completedAt !== undefined){let data12 = data2.completedAt;if((typeof data12 !== "string") && (data12 !== null)){const err31 = {instancePath:instancePath+"/payload/goal/completedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/completedAt/type",keyword:"type",params:{type: schema201.properties.completedAt.type},message:"must be string,null"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(typeof data12 === "string"){if(!(formats0.test(data12))){const err32 = {instancePath:instancePath+"/payload/goal/completedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/completedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}}if(data2.workingNow !== undefined){if(typeof data2.workingNow !== "boolean"){const err33 = {instancePath:instancePath+"/payload/goal/workingNow",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/properties/workingNow/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}else {const err34 = {instancePath:instancePath+"/payload/goal",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/snapshot/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}var _valid1 = _errs11 === errors;if(_valid1){valid5 = true;passing0 = 0;}const _errs36 = errors;if(data2 !== null){const err35 = {instancePath:instancePath+"/payload/goal",schemaPath:"#/allOf/0/then/properties/payload/properties/goal/oneOf/1/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}var _valid1 = _errs36 === errors;if(_valid1 && valid5){valid5 = false;passing0 = [passing0, 1];}else {if(_valid1){valid5 = true;passing0 = 1;}}if(!valid5){const err36 = {instancePath:instancePath+"/payload/goal",schemaPath:"#/allOf/0/then/properties/payload/properties/goal/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}else {errors = _errs10;if(vErrors !== null){if(_errs10){vErrors.length = _errs10;}else {vErrors = null;}}}}}else {const err37 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/0/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}}var _valid0 = _errs5 === errors;valid1 = _valid0;if(valid1){var props1 = {};props1.payload = true;props1.eventType = true;}}if(!valid1){const err38 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}const _errs39 = errors;let valid8 = true;const _errs40 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.eventType !== undefined){if("session.capabilities.updated" !== data.eventType){const err39 = {};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}}var _valid2 = _errs40 === errors;errors = _errs39;if(vErrors !== null){if(_errs39){vErrors.length = _errs39;}else {vErrors = null;}}if(_valid2){const _errs42 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.payload !== undefined){let data15 = data.payload;if(data15 && typeof data15 == "object" && !Array.isArray(data15)){if(data15.sessionGoals === undefined){const err40 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/1/then/properties/payload/required",keyword:"required",params:{missingProperty: "sessionGoals"},message:"must have required property '"+"sessionGoals"+"'"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if(data15.sessionGoals !== undefined){let data16 = data15.sessionGoals;if(data16 && typeof data16 == "object" && !Array.isArray(data16)){if(data16.availability === undefined){const err41 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "availability"},message:"must have required property '"+"availability"+"'"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}if(data16.actions === undefined){const err42 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "actions"},message:"must have required property '"+"actions"+"'"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}if(data16.autonomousUpdates === undefined){const err43 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "autonomousUpdates"},message:"must have required property '"+"autonomousUpdates"+"'"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}if(data16.persistentAcrossResume === undefined){const err44 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "persistentAcrossResume"},message:"must have required property '"+"persistentAcrossResume"+"'"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}if(data16.maxObjectiveChars === undefined){const err45 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "maxObjectiveChars"},message:"must have required property '"+"maxObjectiveChars"+"'"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(data16.tokenBudgetControl === undefined){const err46 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "tokenBudgetControl"},message:"must have required property '"+"tokenBudgetControl"+"'"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}if(data16.usageReporting === undefined){const err47 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/required",keyword:"required",params:{missingProperty: "usageReporting"},message:"must have required property '"+"usageReporting"+"'"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}if(data16.availability !== undefined){let data17 = data16.availability;if(!(((data17 === "available") || (data17 === "unsupported")) || (data17 === "policy_disabled"))){const err48 = {instancePath:instancePath+"/payload/sessionGoals/availability",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/availability/enum",keyword:"enum",params:{allowedValues: schema44.properties.availability.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}if(data16.actions !== undefined){let data18 = data16.actions;if(Array.isArray(data18)){const len0 = data18.length;for(let i0=0; i0 1){outer0:for(;i1--;){for(j0 = i1; j0--;){if(func0(data18[i1], data18[j0])){const err50 = {instancePath:instancePath+"/payload/sessionGoals/actions",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/actions/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;break outer0;}}}}}else {const err51 = {instancePath:instancePath+"/payload/sessionGoals/actions",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/actions/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data16.autonomousUpdates !== undefined){if(typeof data16.autonomousUpdates !== "boolean"){const err52 = {instancePath:instancePath+"/payload/sessionGoals/autonomousUpdates",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/autonomousUpdates/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}if(data16.persistentAcrossResume !== undefined){if(typeof data16.persistentAcrossResume !== "boolean"){const err53 = {instancePath:instancePath+"/payload/sessionGoals/persistentAcrossResume",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/persistentAcrossResume/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data16.maxObjectiveChars !== undefined){let data22 = data16.maxObjectiveChars;if(!(((typeof data22 == "number") && (!(data22 % 1) && !isNaN(data22))) && (isFinite(data22)))){const err54 = {instancePath:instancePath+"/payload/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}if((typeof data22 == "number") && (isFinite(data22))){if(data22 > 4000 || isNaN(data22)){const err55 = {instancePath:instancePath+"/payload/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/maximum",keyword:"maximum",params:{comparison: "<=", limit: 4000},message:"must be <= 4000"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}if(data22 < 1 || isNaN(data22)){const err56 = {instancePath:instancePath+"/payload/sessionGoals/maxObjectiveChars",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/maxObjectiveChars/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}}if(data16.tokenBudgetControl !== undefined){if(typeof data16.tokenBudgetControl !== "boolean"){const err57 = {instancePath:instancePath+"/payload/sessionGoals/tokenBudgetControl",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/tokenBudgetControl/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}if(data16.usageReporting !== undefined){if(typeof data16.usageReporting !== "boolean"){const err58 = {instancePath:instancePath+"/payload/sessionGoals/usageReporting",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/usageReporting/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data16.reasonCode !== undefined){let data25 = data16.reasonCode;if(typeof data25 === "string"){if(func1(data25) > 160){const err59 = {instancePath:instancePath+"/payload/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(func1(data25) < 1){const err60 = {instancePath:instancePath+"/payload/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}else {const err61 = {instancePath:instancePath+"/payload/sessionGoals/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reasonCode/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data16.reason !== undefined){let data26 = data16.reason;if(typeof data26 === "string"){if(func1(data26) > 1000){const err62 = {instancePath:instancePath+"/payload/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(func1(data26) < 1){const err63 = {instancePath:instancePath+"/payload/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}else {const err64 = {instancePath:instancePath+"/payload/sessionGoals/reason",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/properties/reason/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}}else {const err65 = {instancePath:instancePath+"/payload/sessionGoals",schemaPath:"https://paperclip.dev/schemas/prp/v2/session-goal.schema.json#/$defs/capability/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data15.turnControls !== undefined){let data27 = data15.turnControls;if(data27 && typeof data27 == "object" && !Array.isArray(data27)){if(data27.steering === undefined){const err66 = {instancePath:instancePath+"/payload/turnControls",schemaPath:"https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json#/$defs/turnControls/required",keyword:"required",params:{missingProperty: "steering"},message:"must have required property '"+"steering"+"'"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(data27.queuedFollowUp === undefined){const err67 = {instancePath:instancePath+"/payload/turnControls",schemaPath:"https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json#/$defs/turnControls/required",keyword:"required",params:{missingProperty: "queuedFollowUp"},message:"must have required property '"+"queuedFollowUp"+"'"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}for(const key0 in data27){if(!((key0 === "steering") || (key0 === "queuedFollowUp"))){const err68 = {instancePath:instancePath+"/payload/turnControls",schemaPath:"https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json#/$defs/turnControls/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}if(data27.steering !== undefined){if(typeof data27.steering !== "boolean"){const err69 = {instancePath:instancePath+"/payload/turnControls/steering",schemaPath:"https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json#/$defs/turnControls/properties/steering/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}if(data27.queuedFollowUp !== undefined){if(typeof data27.queuedFollowUp !== "boolean"){const err70 = {instancePath:instancePath+"/payload/turnControls/queuedFollowUp",schemaPath:"https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json#/$defs/turnControls/properties/queuedFollowUp/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}}else {const err71 = {instancePath:instancePath+"/payload/turnControls",schemaPath:"https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json#/$defs/turnControls/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}}else {const err72 = {instancePath:instancePath+"/payload",schemaPath:"#/allOf/1/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}}var _valid2 = _errs42 === errors;valid8 = _valid2;if(valid8){var props2 = {};props2.payload = true;props2.eventType = true;}}if(!valid8){const err73 = {instancePath,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(props1 !== true && props2 !== undefined){if(props2 === true){props1 = true;}else {props1 = props1 || {};Object.assign(props1, props2);}}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err74 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}if(data.sourceEventId === undefined){const err75 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sourceEventId"},message:"must have required property '"+"sourceEventId"+"'"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}if(data.sourceSeq === undefined){const err76 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sourceSeq"},message:"must have required property '"+"sourceSeq"+"'"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}if(data.sourceInstanceId === undefined){const err77 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sourceInstanceId"},message:"must have required property '"+"sourceInstanceId"+"'"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(data.sourceKind === undefined){const err78 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sourceKind"},message:"must have required property '"+"sourceKind"+"'"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}if(data.runId === undefined){const err79 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "runId"},message:"must have required property '"+"runId"+"'"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}if(data.eventType === undefined){const err80 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "eventType"},message:"must have required property '"+"eventType"+"'"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}if(data.schemaVersion === undefined){const err81 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}if(data.priority === undefined){const err82 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "priority"},message:"must have required property '"+"priority"+"'"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}if(data.emittedAt === undefined){const err83 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "emittedAt"},message:"must have required property '"+"emittedAt"+"'"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}if(data.payload === undefined){const err84 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "payload"},message:"must have required property '"+"payload"+"'"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}if(data.schema !== undefined){if("paperclip.prp.event.v2" !== data.schema){const err85 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.event.v2"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}if(data.sourceEventId !== undefined){let data31 = data.sourceEventId;if(typeof data31 === "string"){if(func1(data31) > 160){const err86 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}if(func1(data31) < 1){const err87 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}else {const err88 = {instancePath:instancePath+"/sourceEventId",schemaPath:"#/properties/sourceEventId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data.sourceSeq !== undefined){let data32 = data.sourceSeq;if(!(((typeof data32 == "number") && (!(data32 % 1) && !isNaN(data32))) && (isFinite(data32)))){const err89 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}if((typeof data32 == "number") && (isFinite(data32))){if(data32 > 9007199254740991 || isNaN(data32)){const err90 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/maximum",keyword:"maximum",params:{comparison: "<=", limit: 9007199254740991},message:"must be <= 9007199254740991"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}if(data32 < 1 || isNaN(data32)){const err91 = {instancePath:instancePath+"/sourceSeq",schemaPath:"#/properties/sourceSeq/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}}if(data.sourceInstanceId !== undefined){let data33 = data.sourceInstanceId;if(typeof data33 === "string"){if(func1(data33) > 160){const err92 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}if(func1(data33) < 1){const err93 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}else {const err94 = {instancePath:instancePath+"/sourceInstanceId",schemaPath:"#/properties/sourceInstanceId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}if(data.sourceKind !== undefined){let data34 = data.sourceKind;if(!((data34 === "runner") || (data34 === "control_plane"))){const err95 = {instancePath:instancePath+"/sourceKind",schemaPath:"#/properties/sourceKind/enum",keyword:"enum",params:{allowedValues: schema200.properties.sourceKind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}if(data.runId !== undefined){let data35 = data.runId;if(typeof data35 === "string"){if(func1(data35) > 160){const err96 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}if(func1(data35) < 1){const err97 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}else {const err98 = {instancePath:instancePath+"/runId",schemaPath:"#/properties/runId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}if(data.normalizedSessionId !== undefined){let data36 = data.normalizedSessionId;if(typeof data36 === "string"){if(func1(data36) > 160){const err99 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}if(func1(data36) < 1){const err100 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}else {const err101 = {instancePath:instancePath+"/normalizedSessionId",schemaPath:"#/properties/normalizedSessionId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}}if(data.turnId !== undefined){let data37 = data.turnId;if(typeof data37 === "string"){if(func1(data37) > 160){const err102 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}if(func1(data37) < 1){const err103 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}else {const err104 = {instancePath:instancePath+"/turnId",schemaPath:"#/properties/turnId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}if(data.itemId !== undefined){let data38 = data.itemId;if(typeof data38 === "string"){if(func1(data38) > 160){const err105 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}if(func1(data38) < 1){const err106 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}else {const err107 = {instancePath:instancePath+"/itemId",schemaPath:"#/properties/itemId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}if(data.eventType !== undefined){let data39 = data.eventType;if(!(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((data39 === "runner.connected") || (data39 === "runner.reconnected")) || (data39 === "runner.reconciled")) || (data39 === "runner.disconnected")) || (data39 === "runner.draining")) || (data39 === "runner.suspending")) || (data39 === "runner.suspended")) || (data39 === "runner.stopped")) || (data39 === "runner.diagnostic")) || (data39 === "runtime.phase.changed")) || (data39 === "sandbox.metric")) || (data39 === "workspace.ready")) || (data39 === "workspace.change.updated")) || (data39 === "workspace.diff.recorded")) || (data39 === "workspace.file.referenced")) || (data39 === "harness.starting")) || (data39 === "harness.ready")) || (data39 === "harness.exited")) || (data39 === "harness.diagnostic")) || (data39 === "plan.updated")) || (data39 === "tool.execution.started")) || (data39 === "tool.execution.progressed")) || (data39 === "tool.execution.completed")) || (data39 === "research.started")) || (data39 === "research.progressed")) || (data39 === "research.completed")) || (data39 === "delegation.started")) || (data39 === "delegation.updated")) || (data39 === "delegation.completed")) || (data39 === "model.route.changed")) || (data39 === "model.verification.updated")) || (data39 === "context.compacted")) || (data39 === "artifact.viewed")) || (data39 === "artifact.generated")) || (data39 === "review.mode.changed")) || (data39 === "hook.started")) || (data39 === "hook.completed")) || (data39 === "memory.citation.referenced")) || (data39 === "safety.review.started")) || (data39 === "safety.review.completed")) || (data39 === "terminal.input.sent")) || (data39 === "wait.started")) || (data39 === "wait.completed")) || (data39 === "provider.notice.recorded")) || (data39 === "session.starting")) || (data39 === "session.started")) || (data39 === "session.resuming")) || (data39 === "session.resumed")) || (data39 === "session.reconciled")) || (data39 === "session.updated")) || (data39 === "session.closed")) || (data39 === "session.failed")) || (data39 === "session.capabilities.updated")) || (data39 === "session.goal.snapshot")) || (data39 === "session.goal.updated")) || (data39 === "session.goal.cleared")) || (data39 === "turn.submitted")) || (data39 === "turn.accepted")) || (data39 === "turn.started")) || (data39 === "turn.completed")) || (data39 === "turn.failed")) || (data39 === "turn.interrupted")) || (data39 === "turn.cancelled")) || (data39 === "item.started")) || (data39 === "item.delta")) || (data39 === "item.completed")) || (data39 === "item.failed")) || (data39 === "usage.reported")) || (data39 === "semantic_tool.input")) || (data39 === "semantic_tool.result")) || (data39 === "mcp_app.discovered")) || (data39 === "mcp_app.resource.resolved")) || (data39 === "mcp_app.initializing")) || (data39 === "mcp_app.ready")) || (data39 === "mcp_app.tool_input")) || (data39 === "mcp_app.tool_result")) || (data39 === "mcp_app.action.requested")) || (data39 === "mcp_app.action.resolved")) || (data39 === "mcp_app.host_context.changed")) || (data39 === "mcp_app.failed")) || (data39 === "mcp_app.teardown")) || (data39 === "runtime_request.created")) || (data39 === "runtime_request.resolved")) || (data39 === "runtime_request.expired")) || (data39 === "runtime_request.cancelled")) || (data39 === "interaction.request.proposed")) || (data39 === "interaction.request.materialized")) || (data39 === "interaction.request.rejected")) || (data39 === "interaction.response.progressed")) || (data39 === "interaction.response.resolved")) || (data39 === "interaction.response.delivered")) || (data39 === "run.attached")) || (data39 === "run.detached")) || (data39 === "run.result.proposed")) || (data39 === "run.result.accepted")) || (data39 === "run.result.rejected")) || (data39 === "attention.request.proposed")) || (data39 === "attention.request.routed")) || (data39 === "attention.request.resolved")) || (data39 === "attention.request.expired")) || (data39 === "attention.request.superseded")) || (data39 === "work.assessment.recorded")) || (data39 === "issue.status.decision.recorded")) || (data39 === "issue.status.decision.applied")) || (data39 === "issue.status.decision.rejected")) || (data39 === "issue.status.decision.superseded")) || (data39 === "run.terminal"))){const err108 = {instancePath:instancePath+"/eventType",schemaPath:"#/properties/eventType/enum",keyword:"enum",params:{allowedValues: schema200.properties.eventType.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}if(data.schemaVersion !== undefined){if(2 !== data.schemaVersion){const err109 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 2},message:"must be equal to constant"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}if(data.priority !== undefined){let data41 = data.priority;if(!(((data41 === 0) || (data41 === 1)) || (data41 === 2))){const err110 = {instancePath:instancePath+"/priority",schemaPath:"#/properties/priority/enum",keyword:"enum",params:{allowedValues: schema200.properties.priority.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}if(data.emittedAt !== undefined){let data42 = data.emittedAt;if(typeof data42 === "string"){if(!(formats0.test(data42))){const err111 = {instancePath:instancePath+"/emittedAt",schemaPath:"#/properties/emittedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}else {const err112 = {instancePath:instancePath+"/emittedAt",schemaPath:"#/properties/emittedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}if(data.observedAt !== undefined){let data43 = data.observedAt;if(typeof data43 === "string"){if(!(formats0.test(data43))){const err113 = {instancePath:instancePath+"/observedAt",schemaPath:"#/properties/observedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}else {const err114 = {instancePath:instancePath+"/observedAt",schemaPath:"#/properties/observedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}}if(data.payload !== undefined){let data44 = data.payload;if(data44 && typeof data44 == "object" && !Array.isArray(data44)){}else {const err115 = {instancePath:instancePath+"/payload",schemaPath:"#/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}if(data.debug !== undefined){let data45 = data.debug;if(data45 && typeof data45 == "object" && !Array.isArray(data45)){}else {const err116 = {instancePath:instancePath+"/debug",schemaPath:"#/properties/debug/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}}else {const err117 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}validate109.errors = vErrors;return errors === 0;}validate109.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema206 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/request.schema.json","title":"PRP request","oneOf":[{"type":"object","required":["schema","requestKind","requestId","type","status","prompt","choices","origin","turnId"],"properties":{"schema":{"const":"paperclip.runtime_request.v2"},"requestKind":{"const":"permission_approval"},"requestId":{"type":"string","minLength":1,"maxLength":160},"type":{"const":"permission"},"status":{"enum":["pending","resolved","expired","cancelled"]},"prompt":{"type":"string","minLength":1,"maxLength":4000},"choices":{"type":"array","minItems":1,"maxItems":4,"items":{"type":"object","required":["key","label"],"properties":{"key":{"enum":["accept","accept_for_session","decline","cancel"]},"label":{"type":"string","minLength":1,"maxLength":500}},"additionalProperties":false},"allOf":[{"contains":{"type":"object","required":["key"],"properties":{"key":{"const":"accept"}}},"minContains":0,"maxContains":1},{"contains":{"type":"object","required":["key"],"properties":{"key":{"const":"accept_for_session"}}},"minContains":0,"maxContains":1},{"contains":{"type":"object","required":["key"],"properties":{"key":{"const":"decline"}}},"minContains":0,"maxContains":1},{"contains":{"type":"object","required":["key"],"properties":{"key":{"const":"cancel"}}},"minContains":0,"maxContains":1}]},"details":{"type":"object","additionalProperties":true},"origin":{"type":"object","required":["adapter"],"properties":{"adapter":{"type":"string","minLength":1,"maxLength":160},"provider":{"type":"string","minLength":1,"maxLength":160},"method":{"type":"string","minLength":1,"maxLength":500}},"additionalProperties":false},"turnId":{"type":"string","minLength":1,"maxLength":240},"itemId":{"type":["string","null"],"minLength":1,"maxLength":240}},"additionalProperties":false},{"type":"object","required":["schema","requestKind","requestId","type","status","prompt","input"],"properties":{"schema":{"const":"paperclip.runtime_request.v2"},"requestKind":{"const":"runtime"},"requestId":{"type":"string","minLength":1,"maxLength":160},"type":{"const":"input"},"status":{"enum":["pending","resolved","expired","cancelled"]},"prompt":{"type":"string","minLength":1,"maxLength":4000},"input":{"$ref":"https://paperclip.dev/schemas/prp/v1/question-set.schema.json"},"origin":{"type":"object","required":["adapter"],"properties":{"adapter":{"type":"string","minLength":1,"maxLength":160},"provider":{"type":"string","minLength":1,"maxLength":160},"method":{"type":"string","minLength":1,"maxLength":500}},"additionalProperties":false},"turnId":{"type":"string","minLength":1,"maxLength":240},"itemId":{"type":"string","minLength":1,"maxLength":240}},"additionalProperties":false},{"type":"object","required":["schema","requestKind","requestId","type","status","prompt"],"properties":{"schema":{"const":"paperclip.runtime_request.v1"},"requestKind":{"const":"runtime"},"requestId":{"type":"string","minLength":1,"maxLength":160},"type":{"enum":["permission","input"]},"status":{"enum":["pending","resolved","expired","cancelled"]},"prompt":{"type":"string","minLength":1,"maxLength":4000},"choices":{"type":"array","items":{"type":"object","required":["key","label"],"properties":{"key":{"type":"string","minLength":1},"label":{"type":"string","minLength":1}},"additionalProperties":true}}},"additionalProperties":true},{"type":"object","required":["schema","requestKind","requestId","kind","blocking","payload"],"properties":{"schema":{"const":"paperclip.interaction_request.v1"},"requestKind":{"const":"issue_thread"},"requestId":{"type":"string","minLength":1,"maxLength":160},"kind":{"enum":["request_confirmation","request_checkbox_confirmation","request_item_verdicts","ask_user_questions","suggest_tasks"]},"blocking":{"type":"boolean"},"payload":{"type":"object","additionalProperties":true}},"additionalProperties":true}]};const schema207 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/question-set.schema.json","title":"Paperclip question set","type":"object","required":["schema","questions"],"properties":{"schema":{"const":"paperclip.question_set.v1"},"title":{"type":"string","maxLength":1000},"description":{"type":"string","maxLength":100000},"submitLabel":{"type":"string","maxLength":200},"questions":{"type":"array","minItems":1,"maxItems":64,"items":{"$ref":"#/$defs/question"}}},"$defs":{"option":{"type":"object","required":["id","label"],"properties":{"id":{"type":"string","minLength":1,"maxLength":160},"label":{"type":"string","minLength":1,"maxLength":1000},"description":{"type":"string","maxLength":4000},"recommended":{"type":"boolean"}},"additionalProperties":false},"customAnswer":{"type":"object","required":["enabled"],"properties":{"enabled":{"const":true},"label":{"type":"string","maxLength":1000},"placeholder":{"type":"string","maxLength":1000}},"additionalProperties":false},"textValidation":{"type":"object","properties":{"minLength":{"type":"integer","minimum":0,"maximum":100000},"maxLength":{"type":"integer","minimum":0,"maximum":100000},"pattern":{"type":"string","maxLength":1000},"inputType":{"enum":["text","number","integer"]},"minimum":{"type":"number"},"maximum":{"type":"number"}},"additionalProperties":false},"question":{"type":"object","required":["id","prompt","required","answerMode"],"properties":{"id":{"type":"string","minLength":1,"maxLength":160},"header":{"type":"string","maxLength":1000},"prompt":{"type":"string","minLength":1,"maxLength":4000},"helpText":{"type":"string","maxLength":4000},"required":{"type":"boolean"},"answerMode":{"enum":["single_select","multi_select","text"]},"options":{"type":"array","maxItems":128,"items":{"$ref":"#/$defs/option"}},"customAnswer":{"$ref":"#/$defs/customAnswer"},"textValidation":{"$ref":"#/$defs/textValidation"}},"allOf":[{"if":{"properties":{"answerMode":{"const":"text"}},"required":["answerMode"]},"then":{"properties":{"options":{"type":"array","maxItems":0}},"not":{"required":["customAnswer"]}}},{"if":{"properties":{"answerMode":{"enum":["single_select","multi_select"]}},"required":["answerMode"]},"then":{"required":["options"],"properties":{"options":{"type":"array","minItems":1}}}}],"additionalProperties":false}},"additionalProperties":false};const schema208 = {"type":"object","required":["id","prompt","required","answerMode"],"properties":{"id":{"type":"string","minLength":1,"maxLength":160},"header":{"type":"string","maxLength":1000},"prompt":{"type":"string","minLength":1,"maxLength":4000},"helpText":{"type":"string","maxLength":4000},"required":{"type":"boolean"},"answerMode":{"enum":["single_select","multi_select","text"]},"options":{"type":"array","maxItems":128,"items":{"$ref":"#/$defs/option"}},"customAnswer":{"$ref":"#/$defs/customAnswer"},"textValidation":{"$ref":"#/$defs/textValidation"}},"allOf":[{"if":{"properties":{"answerMode":{"const":"text"}},"required":["answerMode"]},"then":{"properties":{"options":{"type":"array","maxItems":0}},"not":{"required":["customAnswer"]}}},{"if":{"properties":{"answerMode":{"enum":["single_select","multi_select"]}},"required":["answerMode"]},"then":{"required":["options"],"properties":{"options":{"type":"array","minItems":1}}}}],"additionalProperties":false};const schema209 = {"type":"object","required":["id","label"],"properties":{"id":{"type":"string","minLength":1,"maxLength":160},"label":{"type":"string","minLength":1,"maxLength":1000},"description":{"type":"string","maxLength":4000},"recommended":{"type":"boolean"}},"additionalProperties":false};const schema210 = {"type":"object","required":["enabled"],"properties":{"enabled":{"const":true},"label":{"type":"string","maxLength":1000},"placeholder":{"type":"string","maxLength":1000}},"additionalProperties":false};const schema211 = {"type":"object","properties":{"minLength":{"type":"integer","minimum":0,"maximum":100000},"maxLength":{"type":"integer","minimum":0,"maximum":100000},"pattern":{"type":"string","maxLength":1000},"inputType":{"enum":["text","number","integer"]},"minimum":{"type":"number"},"maximum":{"type":"number"}},"additionalProperties":false};function validate114(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate114.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){let missing0;if((data.answerMode === undefined) && (missing0 = "answerMode")){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}else {if(data.answerMode !== undefined){if("text" !== data.answerMode){const err1 = {};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;const _errs6 = errors;const _errs7 = errors;if(data && typeof data == "object" && !Array.isArray(data)){let missing1;if((data.customAnswer === undefined) && (missing1 = "customAnswer")){const err2 = {};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}var valid3 = _errs7 === errors;if(valid3){const err3 = {instancePath,schemaPath:"#/allOf/0/then/not",keyword:"not",params:{},message:"must NOT be valid"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}else {errors = _errs6;if(vErrors !== null){if(_errs6){vErrors.length = _errs6;}else {vErrors = null;}}}if(data && typeof data == "object" && !Array.isArray(data)){if(data.options !== undefined){let data1 = data.options;if(Array.isArray(data1)){if(data1.length > 0){const err4 = {instancePath:instancePath+"/options",schemaPath:"#/allOf/0/then/properties/options/maxItems",keyword:"maxItems",params:{limit: 0},message:"must NOT have more than 0 items"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}else {const err5 = {instancePath:instancePath+"/options",schemaPath:"#/allOf/0/then/properties/options/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}}var _valid0 = _errs5 === errors;valid1 = _valid0;if(valid1){var props0 = {};props0.options = true;props0.answerMode = true;}}if(!valid1){const err6 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}const _errs11 = errors;let valid5 = true;const _errs12 = errors;if(data && typeof data == "object" && !Array.isArray(data)){let missing2;if((data.answerMode === undefined) && (missing2 = "answerMode")){const err7 = {};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}else {if(data.answerMode !== undefined){let data2 = data.answerMode;if(!((data2 === "single_select") || (data2 === "multi_select"))){const err8 = {};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}}}var _valid1 = _errs12 === errors;errors = _errs11;if(vErrors !== null){if(_errs11){vErrors.length = _errs11;}else {vErrors = null;}}if(_valid1){const _errs14 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.options === undefined){const err9 = {instancePath,schemaPath:"#/allOf/1/then/required",keyword:"required",params:{missingProperty: "options"},message:"must have required property '"+"options"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.options !== undefined){let data3 = data.options;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/options",schemaPath:"#/allOf/1/then/properties/options/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/options",schemaPath:"#/allOf/1/then/properties/options/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}}var _valid1 = _errs14 === errors;valid5 = _valid1;if(valid5){var props1 = {};props1.options = true;props1.answerMode = true;}}if(!valid5){const err12 = {instancePath,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(props0 !== true && props1 !== undefined){if(props1 === true){props0 = true;}else {props0 = props0 || {};Object.assign(props0, props1);}}if(data && typeof data == "object" && !Array.isArray(data)){if(data.id === undefined){const err13 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(data.prompt === undefined){const err14 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "prompt"},message:"must have required property '"+"prompt"+"'"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(data.required === undefined){const err15 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "required"},message:"must have required property '"+"required"+"'"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(data.answerMode === undefined){const err16 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "answerMode"},message:"must have required property '"+"answerMode"+"'"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}for(const key0 in data){if(!(func58.call(schema208.properties, key0))){const err17 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.id !== undefined){let data4 = data.id;if(typeof data4 === "string"){if(func1(data4) > 160){const err18 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(func1(data4) < 1){const err19 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}else {const err20 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data.header !== undefined){let data5 = data.header;if(typeof data5 === "string"){if(func1(data5) > 1000){const err21 = {instancePath:instancePath+"/header",schemaPath:"#/properties/header/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/header",schemaPath:"#/properties/header/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data.prompt !== undefined){let data6 = data.prompt;if(typeof data6 === "string"){if(func1(data6) > 4000){const err23 = {instancePath:instancePath+"/prompt",schemaPath:"#/properties/prompt/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(func1(data6) < 1){const err24 = {instancePath:instancePath+"/prompt",schemaPath:"#/properties/prompt/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}else {const err25 = {instancePath:instancePath+"/prompt",schemaPath:"#/properties/prompt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data.helpText !== undefined){let data7 = data.helpText;if(typeof data7 === "string"){if(func1(data7) > 4000){const err26 = {instancePath:instancePath+"/helpText",schemaPath:"#/properties/helpText/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}else {const err27 = {instancePath:instancePath+"/helpText",schemaPath:"#/properties/helpText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data.required !== undefined){if(typeof data.required !== "boolean"){const err28 = {instancePath:instancePath+"/required",schemaPath:"#/properties/required/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data.answerMode !== undefined){let data9 = data.answerMode;if(!(((data9 === "single_select") || (data9 === "multi_select")) || (data9 === "text"))){const err29 = {instancePath:instancePath+"/answerMode",schemaPath:"#/properties/answerMode/enum",keyword:"enum",params:{allowedValues: schema208.properties.answerMode.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data.options !== undefined){let data10 = data.options;if(Array.isArray(data10)){if(data10.length > 128){const err30 = {instancePath:instancePath+"/options",schemaPath:"#/properties/options/maxItems",keyword:"maxItems",params:{limit: 128},message:"must NOT have more than 128 items"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}const len0 = data10.length;for(let i0=0; i0 160){const err34 = {instancePath:instancePath+"/options/" + i0+"/id",schemaPath:"#/$defs/option/properties/id/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(func1(data12) < 1){const err35 = {instancePath:instancePath+"/options/" + i0+"/id",schemaPath:"#/$defs/option/properties/id/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}else {const err36 = {instancePath:instancePath+"/options/" + i0+"/id",schemaPath:"#/$defs/option/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data11.label !== undefined){let data13 = data11.label;if(typeof data13 === "string"){if(func1(data13) > 1000){const err37 = {instancePath:instancePath+"/options/" + i0+"/label",schemaPath:"#/$defs/option/properties/label/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(func1(data13) < 1){const err38 = {instancePath:instancePath+"/options/" + i0+"/label",schemaPath:"#/$defs/option/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}else {const err39 = {instancePath:instancePath+"/options/" + i0+"/label",schemaPath:"#/$defs/option/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data11.description !== undefined){let data14 = data11.description;if(typeof data14 === "string"){if(func1(data14) > 4000){const err40 = {instancePath:instancePath+"/options/" + i0+"/description",schemaPath:"#/$defs/option/properties/description/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}else {const err41 = {instancePath:instancePath+"/options/" + i0+"/description",schemaPath:"#/$defs/option/properties/description/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}if(data11.recommended !== undefined){if(typeof data11.recommended !== "boolean"){const err42 = {instancePath:instancePath+"/options/" + i0+"/recommended",schemaPath:"#/$defs/option/properties/recommended/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}}else {const err43 = {instancePath:instancePath+"/options/" + i0,schemaPath:"#/$defs/option/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}}else {const err44 = {instancePath:instancePath+"/options",schemaPath:"#/properties/options/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data.customAnswer !== undefined){let data16 = data.customAnswer;if(data16 && typeof data16 == "object" && !Array.isArray(data16)){if(data16.enabled === undefined){const err45 = {instancePath:instancePath+"/customAnswer",schemaPath:"#/$defs/customAnswer/required",keyword:"required",params:{missingProperty: "enabled"},message:"must have required property '"+"enabled"+"'"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}for(const key2 in data16){if(!(((key2 === "enabled") || (key2 === "label")) || (key2 === "placeholder"))){const err46 = {instancePath:instancePath+"/customAnswer",schemaPath:"#/$defs/customAnswer/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data16.enabled !== undefined){if(true !== data16.enabled){const err47 = {instancePath:instancePath+"/customAnswer/enabled",schemaPath:"#/$defs/customAnswer/properties/enabled/const",keyword:"const",params:{allowedValue: true},message:"must be equal to constant"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data16.label !== undefined){let data18 = data16.label;if(typeof data18 === "string"){if(func1(data18) > 1000){const err48 = {instancePath:instancePath+"/customAnswer/label",schemaPath:"#/$defs/customAnswer/properties/label/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}else {const err49 = {instancePath:instancePath+"/customAnswer/label",schemaPath:"#/$defs/customAnswer/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data16.placeholder !== undefined){let data19 = data16.placeholder;if(typeof data19 === "string"){if(func1(data19) > 1000){const err50 = {instancePath:instancePath+"/customAnswer/placeholder",schemaPath:"#/$defs/customAnswer/properties/placeholder/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}else {const err51 = {instancePath:instancePath+"/customAnswer/placeholder",schemaPath:"#/$defs/customAnswer/properties/placeholder/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}}else {const err52 = {instancePath:instancePath+"/customAnswer",schemaPath:"#/$defs/customAnswer/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}if(data.textValidation !== undefined){let data20 = data.textValidation;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){for(const key3 in data20){if(!((((((key3 === "minLength") || (key3 === "maxLength")) || (key3 === "pattern")) || (key3 === "inputType")) || (key3 === "minimum")) || (key3 === "maximum"))){const err53 = {instancePath:instancePath+"/textValidation",schemaPath:"#/$defs/textValidation/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data20.minLength !== undefined){let data21 = data20.minLength;if(!(((typeof data21 == "number") && (!(data21 % 1) && !isNaN(data21))) && (isFinite(data21)))){const err54 = {instancePath:instancePath+"/textValidation/minLength",schemaPath:"#/$defs/textValidation/properties/minLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}if((typeof data21 == "number") && (isFinite(data21))){if(data21 > 100000 || isNaN(data21)){const err55 = {instancePath:instancePath+"/textValidation/minLength",schemaPath:"#/$defs/textValidation/properties/minLength/maximum",keyword:"maximum",params:{comparison: "<=", limit: 100000},message:"must be <= 100000"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}if(data21 < 0 || isNaN(data21)){const err56 = {instancePath:instancePath+"/textValidation/minLength",schemaPath:"#/$defs/textValidation/properties/minLength/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}}if(data20.maxLength !== undefined){let data22 = data20.maxLength;if(!(((typeof data22 == "number") && (!(data22 % 1) && !isNaN(data22))) && (isFinite(data22)))){const err57 = {instancePath:instancePath+"/textValidation/maxLength",schemaPath:"#/$defs/textValidation/properties/maxLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}if((typeof data22 == "number") && (isFinite(data22))){if(data22 > 100000 || isNaN(data22)){const err58 = {instancePath:instancePath+"/textValidation/maxLength",schemaPath:"#/$defs/textValidation/properties/maxLength/maximum",keyword:"maximum",params:{comparison: "<=", limit: 100000},message:"must be <= 100000"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}if(data22 < 0 || isNaN(data22)){const err59 = {instancePath:instancePath+"/textValidation/maxLength",schemaPath:"#/$defs/textValidation/properties/maxLength/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}}if(data20.pattern !== undefined){let data23 = data20.pattern;if(typeof data23 === "string"){if(func1(data23) > 1000){const err60 = {instancePath:instancePath+"/textValidation/pattern",schemaPath:"#/$defs/textValidation/properties/pattern/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}else {const err61 = {instancePath:instancePath+"/textValidation/pattern",schemaPath:"#/$defs/textValidation/properties/pattern/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data20.inputType !== undefined){let data24 = data20.inputType;if(!(((data24 === "text") || (data24 === "number")) || (data24 === "integer"))){const err62 = {instancePath:instancePath+"/textValidation/inputType",schemaPath:"#/$defs/textValidation/properties/inputType/enum",keyword:"enum",params:{allowedValues: schema211.properties.inputType.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(data20.minimum !== undefined){let data25 = data20.minimum;if(!((typeof data25 == "number") && (isFinite(data25)))){const err63 = {instancePath:instancePath+"/textValidation/minimum",schemaPath:"#/$defs/textValidation/properties/minimum/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data20.maximum !== undefined){let data26 = data20.maximum;if(!((typeof data26 == "number") && (isFinite(data26)))){const err64 = {instancePath:instancePath+"/textValidation/maximum",schemaPath:"#/$defs/textValidation/properties/maximum/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}}else {const err65 = {instancePath:instancePath+"/textValidation",schemaPath:"#/$defs/textValidation/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}}else {const err66 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}validate114.errors = vErrors;return errors === 0;}validate114.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate113(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/question-set.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate113.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.questions === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "questions"},message:"must have required property '"+"questions"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}for(const key0 in data){if(!(((((key0 === "schema") || (key0 === "title")) || (key0 === "description")) || (key0 === "submitLabel")) || (key0 === "questions"))){const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.schema !== undefined){if("paperclip.question_set.v1" !== data.schema){const err3 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.question_set.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.title !== undefined){let data1 = data.title;if(typeof data1 === "string"){if(func1(data1) > 1000){const err4 = {instancePath:instancePath+"/title",schemaPath:"#/properties/title/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}else {const err5 = {instancePath:instancePath+"/title",schemaPath:"#/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.description !== undefined){let data2 = data.description;if(typeof data2 === "string"){if(func1(data2) > 100000){const err6 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/maxLength",keyword:"maxLength",params:{limit: 100000},message:"must NOT have more than 100000 characters"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}else {const err7 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.submitLabel !== undefined){let data3 = data.submitLabel;if(typeof data3 === "string"){if(func1(data3) > 200){const err8 = {instancePath:instancePath+"/submitLabel",schemaPath:"#/properties/submitLabel/maxLength",keyword:"maxLength",params:{limit: 200},message:"must NOT have more than 200 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/submitLabel",schemaPath:"#/properties/submitLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.questions !== undefined){let data4 = data.questions;if(Array.isArray(data4)){if(data4.length > 64){const err10 = {instancePath:instancePath+"/questions",schemaPath:"#/properties/questions/maxItems",keyword:"maxItems",params:{limit: 64},message:"must NOT have more than 64 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(data4.length < 1){const err11 = {instancePath:instancePath+"/questions",schemaPath:"#/properties/questions/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}const len0 = data4.length;for(let i0=0; i0 160){const err12 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/0/properties/requestId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(func1(data2) < 1){const err13 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/0/properties/requestId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}else {const err14 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/0/properties/requestId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.type !== undefined){if("permission" !== data.type){const err15 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/0/properties/type/const",keyword:"const",params:{allowedValue: "permission"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.status !== undefined){let data4 = data.status;if(!((((data4 === "pending") || (data4 === "resolved")) || (data4 === "expired")) || (data4 === "cancelled"))){const err16 = {instancePath:instancePath+"/status",schemaPath:"#/oneOf/0/properties/status/enum",keyword:"enum",params:{allowedValues: schema206.oneOf[0].properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.prompt !== undefined){let data5 = data.prompt;if(typeof data5 === "string"){if(func1(data5) > 4000){const err17 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/0/properties/prompt/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(func1(data5) < 1){const err18 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/0/properties/prompt/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/0/properties/prompt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data.choices !== undefined){let data6 = data.choices;if(Array.isArray(data6)){const _errs15 = errors;const len0 = data6.length;let valid3 = true;if(data6.length > 0){let count0 = 0;for(let i0=0; i0 1){valid3 = false;break;}if(count0 >= 0){valid3 = true;}}}}if(!valid3){const err23 = {instancePath:instancePath+"/choices",schemaPath:"#/oneOf/0/properties/choices/allOf/0/contains",keyword:"contains",params:{minContains: 0, maxContains: 1},message:"must contain at least 0 and no more than 1 valid item(s)"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}else {errors = _errs15;if(vErrors !== null){if(_errs15){vErrors.length = _errs15;}else {vErrors = null;}}}}if(Array.isArray(data6)){const _errs20 = errors;const len1 = data6.length;let valid5 = true;if(data6.length > 0){let count1 = 0;for(let i1=0; i1 1){valid5 = false;break;}if(count1 >= 0){valid5 = true;}}}}if(!valid5){const err27 = {instancePath:instancePath+"/choices",schemaPath:"#/oneOf/0/properties/choices/allOf/1/contains",keyword:"contains",params:{minContains: 0, maxContains: 1},message:"must contain at least 0 and no more than 1 valid item(s)"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}else {errors = _errs20;if(vErrors !== null){if(_errs20){vErrors.length = _errs20;}else {vErrors = null;}}}}if(Array.isArray(data6)){const _errs25 = errors;const len2 = data6.length;let valid7 = true;if(data6.length > 0){let count2 = 0;for(let i2=0; i2 1){valid7 = false;break;}if(count2 >= 0){valid7 = true;}}}}if(!valid7){const err31 = {instancePath:instancePath+"/choices",schemaPath:"#/oneOf/0/properties/choices/allOf/2/contains",keyword:"contains",params:{minContains: 0, maxContains: 1},message:"must contain at least 0 and no more than 1 valid item(s)"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}else {errors = _errs25;if(vErrors !== null){if(_errs25){vErrors.length = _errs25;}else {vErrors = null;}}}}if(Array.isArray(data6)){const _errs30 = errors;const len3 = data6.length;let valid9 = true;if(data6.length > 0){let count3 = 0;for(let i3=0; i3 1){valid9 = false;break;}if(count3 >= 0){valid9 = true;}}}}if(!valid9){const err35 = {instancePath:instancePath+"/choices",schemaPath:"#/oneOf/0/properties/choices/allOf/3/contains",keyword:"contains",params:{minContains: 0, maxContains: 1},message:"must contain at least 0 and no more than 1 valid item(s)"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}else {errors = _errs30;if(vErrors !== null){if(_errs30){vErrors.length = _errs30;}else {vErrors = null;}}}}if(Array.isArray(data6)){if(data6.length > 4){const err36 = {instancePath:instancePath+"/choices",schemaPath:"#/oneOf/0/properties/choices/maxItems",keyword:"maxItems",params:{limit: 4},message:"must NOT have more than 4 items"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}if(data6.length < 1){const err37 = {instancePath:instancePath+"/choices",schemaPath:"#/oneOf/0/properties/choices/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}const len4 = data6.length;for(let i4=0; i4 500){const err42 = {instancePath:instancePath+"/choices/" + i4+"/label",schemaPath:"#/oneOf/0/properties/choices/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 500},message:"must NOT have more than 500 characters"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}if(func1(data17) < 1){const err43 = {instancePath:instancePath+"/choices/" + i4+"/label",schemaPath:"#/oneOf/0/properties/choices/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}else {const err44 = {instancePath:instancePath+"/choices/" + i4+"/label",schemaPath:"#/oneOf/0/properties/choices/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}}else {const err45 = {instancePath:instancePath+"/choices/" + i4,schemaPath:"#/oneOf/0/properties/choices/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}}else {const err46 = {instancePath:instancePath+"/choices",schemaPath:"#/oneOf/0/properties/choices/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data.details !== undefined){let data18 = data.details;if(data18 && typeof data18 == "object" && !Array.isArray(data18)){}else {const err47 = {instancePath:instancePath+"/details",schemaPath:"#/oneOf/0/properties/details/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data.origin !== undefined){let data19 = data.origin;if(data19 && typeof data19 == "object" && !Array.isArray(data19)){if(data19.adapter === undefined){const err48 = {instancePath:instancePath+"/origin",schemaPath:"#/oneOf/0/properties/origin/required",keyword:"required",params:{missingProperty: "adapter"},message:"must have required property '"+"adapter"+"'"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}for(const key2 in data19){if(!(((key2 === "adapter") || (key2 === "provider")) || (key2 === "method"))){const err49 = {instancePath:instancePath+"/origin",schemaPath:"#/oneOf/0/properties/origin/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data19.adapter !== undefined){let data20 = data19.adapter;if(typeof data20 === "string"){if(func1(data20) > 160){const err50 = {instancePath:instancePath+"/origin/adapter",schemaPath:"#/oneOf/0/properties/origin/properties/adapter/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}if(func1(data20) < 1){const err51 = {instancePath:instancePath+"/origin/adapter",schemaPath:"#/oneOf/0/properties/origin/properties/adapter/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}else {const err52 = {instancePath:instancePath+"/origin/adapter",schemaPath:"#/oneOf/0/properties/origin/properties/adapter/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}if(data19.provider !== undefined){let data21 = data19.provider;if(typeof data21 === "string"){if(func1(data21) > 160){const err53 = {instancePath:instancePath+"/origin/provider",schemaPath:"#/oneOf/0/properties/origin/properties/provider/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}if(func1(data21) < 1){const err54 = {instancePath:instancePath+"/origin/provider",schemaPath:"#/oneOf/0/properties/origin/properties/provider/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}else {const err55 = {instancePath:instancePath+"/origin/provider",schemaPath:"#/oneOf/0/properties/origin/properties/provider/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}if(data19.method !== undefined){let data22 = data19.method;if(typeof data22 === "string"){if(func1(data22) > 500){const err56 = {instancePath:instancePath+"/origin/method",schemaPath:"#/oneOf/0/properties/origin/properties/method/maxLength",keyword:"maxLength",params:{limit: 500},message:"must NOT have more than 500 characters"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}if(func1(data22) < 1){const err57 = {instancePath:instancePath+"/origin/method",schemaPath:"#/oneOf/0/properties/origin/properties/method/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}else {const err58 = {instancePath:instancePath+"/origin/method",schemaPath:"#/oneOf/0/properties/origin/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}}else {const err59 = {instancePath:instancePath+"/origin",schemaPath:"#/oneOf/0/properties/origin/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}if(data.turnId !== undefined){let data23 = data.turnId;if(typeof data23 === "string"){if(func1(data23) > 240){const err60 = {instancePath:instancePath+"/turnId",schemaPath:"#/oneOf/0/properties/turnId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}if(func1(data23) < 1){const err61 = {instancePath:instancePath+"/turnId",schemaPath:"#/oneOf/0/properties/turnId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}else {const err62 = {instancePath:instancePath+"/turnId",schemaPath:"#/oneOf/0/properties/turnId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(data.itemId !== undefined){let data24 = data.itemId;if((typeof data24 !== "string") && (data24 !== null)){const err63 = {instancePath:instancePath+"/itemId",schemaPath:"#/oneOf/0/properties/itemId/type",keyword:"type",params:{type: schema206.oneOf[0].properties.itemId.type},message:"must be string,null"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}if(typeof data24 === "string"){if(func1(data24) > 240){const err64 = {instancePath:instancePath+"/itemId",schemaPath:"#/oneOf/0/properties/itemId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}if(func1(data24) < 1){const err65 = {instancePath:instancePath+"/itemId",schemaPath:"#/oneOf/0/properties/itemId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}}}else {const err66 = {instancePath,schemaPath:"#/oneOf/0/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}var _valid0 = _errs1 === errors;if(_valid0){valid0 = true;passing0 = 0;var props0 = true;}const _errs56 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err67 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}if(data.requestKind === undefined){const err68 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "requestKind"},message:"must have required property '"+"requestKind"+"'"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}if(data.requestId === undefined){const err69 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "requestId"},message:"must have required property '"+"requestId"+"'"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}if(data.type === undefined){const err70 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if(data.status === undefined){const err71 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}if(data.prompt === undefined){const err72 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "prompt"},message:"must have required property '"+"prompt"+"'"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}if(data.input === undefined){const err73 = {instancePath,schemaPath:"#/oneOf/1/required",keyword:"required",params:{missingProperty: "input"},message:"must have required property '"+"input"+"'"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}for(const key3 in data){if(!(func58.call(schema206.oneOf[1].properties, key3))){const err74 = {instancePath,schemaPath:"#/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}if(data.schema !== undefined){if("paperclip.runtime_request.v2" !== data.schema){const err75 = {instancePath:instancePath+"/schema",schemaPath:"#/oneOf/1/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.runtime_request.v2"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data.requestKind !== undefined){if("runtime" !== data.requestKind){const err76 = {instancePath:instancePath+"/requestKind",schemaPath:"#/oneOf/1/properties/requestKind/const",keyword:"const",params:{allowedValue: "runtime"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}if(data.requestId !== undefined){let data27 = data.requestId;if(typeof data27 === "string"){if(func1(data27) > 160){const err77 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/1/properties/requestId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(func1(data27) < 1){const err78 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/1/properties/requestId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}else {const err79 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/1/properties/requestId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}if(data.type !== undefined){if("input" !== data.type){const err80 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/1/properties/type/const",keyword:"const",params:{allowedValue: "input"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data.status !== undefined){let data29 = data.status;if(!((((data29 === "pending") || (data29 === "resolved")) || (data29 === "expired")) || (data29 === "cancelled"))){const err81 = {instancePath:instancePath+"/status",schemaPath:"#/oneOf/1/properties/status/enum",keyword:"enum",params:{allowedValues: schema206.oneOf[1].properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}if(data.prompt !== undefined){let data30 = data.prompt;if(typeof data30 === "string"){if(func1(data30) > 4000){const err82 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/1/properties/prompt/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}if(func1(data30) < 1){const err83 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/1/properties/prompt/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}else {const err84 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/1/properties/prompt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}if(data.input !== undefined){if(!(validate113(data.input, {instancePath:instancePath+"/input",parentData:data,parentDataProperty:"input",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate113.errors : vErrors.concat(validate113.errors);errors = vErrors.length;}}if(data.origin !== undefined){let data32 = data.origin;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){if(data32.adapter === undefined){const err85 = {instancePath:instancePath+"/origin",schemaPath:"#/oneOf/1/properties/origin/required",keyword:"required",params:{missingProperty: "adapter"},message:"must have required property '"+"adapter"+"'"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}for(const key4 in data32){if(!(((key4 === "adapter") || (key4 === "provider")) || (key4 === "method"))){const err86 = {instancePath:instancePath+"/origin",schemaPath:"#/oneOf/1/properties/origin/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}if(data32.adapter !== undefined){let data33 = data32.adapter;if(typeof data33 === "string"){if(func1(data33) > 160){const err87 = {instancePath:instancePath+"/origin/adapter",schemaPath:"#/oneOf/1/properties/origin/properties/adapter/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}if(func1(data33) < 1){const err88 = {instancePath:instancePath+"/origin/adapter",schemaPath:"#/oneOf/1/properties/origin/properties/adapter/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}else {const err89 = {instancePath:instancePath+"/origin/adapter",schemaPath:"#/oneOf/1/properties/origin/properties/adapter/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}if(data32.provider !== undefined){let data34 = data32.provider;if(typeof data34 === "string"){if(func1(data34) > 160){const err90 = {instancePath:instancePath+"/origin/provider",schemaPath:"#/oneOf/1/properties/origin/properties/provider/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}if(func1(data34) < 1){const err91 = {instancePath:instancePath+"/origin/provider",schemaPath:"#/oneOf/1/properties/origin/properties/provider/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}else {const err92 = {instancePath:instancePath+"/origin/provider",schemaPath:"#/oneOf/1/properties/origin/properties/provider/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}if(data32.method !== undefined){let data35 = data32.method;if(typeof data35 === "string"){if(func1(data35) > 500){const err93 = {instancePath:instancePath+"/origin/method",schemaPath:"#/oneOf/1/properties/origin/properties/method/maxLength",keyword:"maxLength",params:{limit: 500},message:"must NOT have more than 500 characters"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}if(func1(data35) < 1){const err94 = {instancePath:instancePath+"/origin/method",schemaPath:"#/oneOf/1/properties/origin/properties/method/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}else {const err95 = {instancePath:instancePath+"/origin/method",schemaPath:"#/oneOf/1/properties/origin/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}}else {const err96 = {instancePath:instancePath+"/origin",schemaPath:"#/oneOf/1/properties/origin/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}}if(data.turnId !== undefined){let data36 = data.turnId;if(typeof data36 === "string"){if(func1(data36) > 240){const err97 = {instancePath:instancePath+"/turnId",schemaPath:"#/oneOf/1/properties/turnId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}if(func1(data36) < 1){const err98 = {instancePath:instancePath+"/turnId",schemaPath:"#/oneOf/1/properties/turnId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}else {const err99 = {instancePath:instancePath+"/turnId",schemaPath:"#/oneOf/1/properties/turnId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}if(data.itemId !== undefined){let data37 = data.itemId;if(typeof data37 === "string"){if(func1(data37) > 240){const err100 = {instancePath:instancePath+"/itemId",schemaPath:"#/oneOf/1/properties/itemId/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}if(func1(data37) < 1){const err101 = {instancePath:instancePath+"/itemId",schemaPath:"#/oneOf/1/properties/itemId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}}else {const err102 = {instancePath:instancePath+"/itemId",schemaPath:"#/oneOf/1/properties/itemId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}}else {const err103 = {instancePath,schemaPath:"#/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}var _valid0 = _errs56 === errors;if(_valid0 && valid0){valid0 = false;passing0 = [passing0, 1];}else {if(_valid0){valid0 = true;passing0 = 1;if(props0 !== true){props0 = true;}}const _errs81 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err104 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}if(data.requestKind === undefined){const err105 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "requestKind"},message:"must have required property '"+"requestKind"+"'"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}if(data.requestId === undefined){const err106 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "requestId"},message:"must have required property '"+"requestId"+"'"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}if(data.type === undefined){const err107 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}if(data.status === undefined){const err108 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "status"},message:"must have required property '"+"status"+"'"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}if(data.prompt === undefined){const err109 = {instancePath,schemaPath:"#/oneOf/2/required",keyword:"required",params:{missingProperty: "prompt"},message:"must have required property '"+"prompt"+"'"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}if(data.schema !== undefined){if("paperclip.runtime_request.v1" !== data.schema){const err110 = {instancePath:instancePath+"/schema",schemaPath:"#/oneOf/2/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.runtime_request.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}if(data.requestKind !== undefined){if("runtime" !== data.requestKind){const err111 = {instancePath:instancePath+"/requestKind",schemaPath:"#/oneOf/2/properties/requestKind/const",keyword:"const",params:{allowedValue: "runtime"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}if(data.requestId !== undefined){let data40 = data.requestId;if(typeof data40 === "string"){if(func1(data40) > 160){const err112 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/2/properties/requestId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}if(func1(data40) < 1){const err113 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/2/properties/requestId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}else {const err114 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/2/properties/requestId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}}if(data.type !== undefined){let data41 = data.type;if(!((data41 === "permission") || (data41 === "input"))){const err115 = {instancePath:instancePath+"/type",schemaPath:"#/oneOf/2/properties/type/enum",keyword:"enum",params:{allowedValues: schema206.oneOf[2].properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}if(data.status !== undefined){let data42 = data.status;if(!((((data42 === "pending") || (data42 === "resolved")) || (data42 === "expired")) || (data42 === "cancelled"))){const err116 = {instancePath:instancePath+"/status",schemaPath:"#/oneOf/2/properties/status/enum",keyword:"enum",params:{allowedValues: schema206.oneOf[2].properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}if(data.prompt !== undefined){let data43 = data.prompt;if(typeof data43 === "string"){if(func1(data43) > 4000){const err117 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/2/properties/prompt/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}if(func1(data43) < 1){const err118 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/2/properties/prompt/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}else {const err119 = {instancePath:instancePath+"/prompt",schemaPath:"#/oneOf/2/properties/prompt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}if(data.choices !== undefined){let data44 = data.choices;if(Array.isArray(data44)){const len5 = data44.length;for(let i5=0; i5 160){const err137 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/3/properties/requestId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}if(func1(data50) < 1){const err138 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/3/properties/requestId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}else {const err139 = {instancePath:instancePath+"/requestId",schemaPath:"#/oneOf/3/properties/requestId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}if(data.kind !== undefined){let data51 = data.kind;if(!(((((data51 === "request_confirmation") || (data51 === "request_checkbox_confirmation")) || (data51 === "request_item_verdicts")) || (data51 === "ask_user_questions")) || (data51 === "suggest_tasks"))){const err140 = {instancePath:instancePath+"/kind",schemaPath:"#/oneOf/3/properties/kind/enum",keyword:"enum",params:{allowedValues: schema206.oneOf[3].properties.kind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}}if(data.blocking !== undefined){if(typeof data.blocking !== "boolean"){const err141 = {instancePath:instancePath+"/blocking",schemaPath:"#/oneOf/3/properties/blocking/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}if(data.payload !== undefined){let data53 = data.payload;if(data53 && typeof data53 == "object" && !Array.isArray(data53)){}else {const err142 = {instancePath:instancePath+"/payload",schemaPath:"#/oneOf/3/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}}else {const err143 = {instancePath,schemaPath:"#/oneOf/3/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}var _valid0 = _errs101 === errors;if(_valid0 && valid0){valid0 = false;passing0 = [passing0, 3];}else {if(_valid0){valid0 = true;passing0 = 3;if(props0 !== true){props0 = true;}}}}}if(!valid0){const err144 = {instancePath,schemaPath:"#/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}else {errors = _errs0;if(vErrors !== null){if(_errs0){vErrors.length = _errs0;}else {vErrors = null;}}}validate112.errors = vErrors;evaluated0.props = props0;return errors === 0;}validate112.evaluated = {"dynamicProps":true,"dynamicItems":false};const pattern12 = new RegExp("^[a-z][a-z0-9_.-]*$", "u");function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://paperclip.dev/schemas/prp/v1/fixture.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate20.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.fixtureVersion === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "fixtureVersion"},message:"must have required property '"+"fixtureVersion"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.protocolVersion === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "protocolVersion"},message:"must have required property '"+"protocolVersion"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.name === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "name"},message:"must have required property '"+"name"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.description === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "description"},message:"must have required property '"+"description"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.identity === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "identity"},message:"must have required property '"+"identity"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.capabilities === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "capabilities"},message:"must have required property '"+"capabilities"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.commands === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "commands"},message:"must have required property '"+"commands"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.events === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "events"},message:"must have required property '"+"events"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(data.result === undefined){const err9 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "result"},message:"must have required property '"+"result"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.schema !== undefined){if("paperclip.prp.fixture.v1" !== data.schema){const err10 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.fixture.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.fixtureVersion !== undefined){if(1 !== data.fixtureVersion){const err11 = {instancePath:instancePath+"/fixtureVersion",schemaPath:"#/properties/fixtureVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.protocolVersion !== undefined){let data2 = data.protocolVersion;if(!((data2 === 1) || (data2 === 2))){const err12 = {instancePath:instancePath+"/protocolVersion",schemaPath:"#/properties/protocolVersion/enum",keyword:"enum",params:{allowedValues: schema31.properties.protocolVersion.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.name !== undefined){let data3 = data.name;if(typeof data3 === "string"){if(func1(data3) > 120){const err13 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/maxLength",keyword:"maxLength",params:{limit: 120},message:"must NOT have more than 120 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(func1(data3) < 1){const err14 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.description !== undefined){let data4 = data.description;if(typeof data4 === "string"){if(func1(data4) > 1000){const err16 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/maxLength",keyword:"maxLength",params:{limit: 1000},message:"must NOT have more than 1000 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(func1(data4) < 1){const err17 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}else {const err18 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.identity !== undefined){if(!(validate21(data.identity, {instancePath:instancePath+"/identity",parentData:data,parentDataProperty:"identity",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate21.errors : vErrors.concat(validate21.errors);errors = vErrors.length;}}if(data.capabilities !== undefined){let data6 = data.capabilities;const _errs11 = errors;let valid1 = false;let passing0 = null;const _errs12 = errors;if(data6 && typeof data6 == "object" && !Array.isArray(data6)){if(data6.schema === undefined){const err19 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(data6.sessionReusePolicy === undefined){const err20 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "sessionReusePolicy"},message:"must have required property '"+"sessionReusePolicy"+"'"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(data6.driver === undefined){const err21 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "driver"},message:"must have required property '"+"driver"+"'"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(data6.steer === undefined){const err22 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "steer"},message:"must have required property '"+"steer"+"'"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(data6.interrupt === undefined){const err23 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "interrupt"},message:"must have required property '"+"interrupt"+"'"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(data6.resume === undefined){const err24 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "resume"},message:"must have required property '"+"resume"+"'"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(data6.runtimeRequests === undefined){const err25 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "runtimeRequests"},message:"must have required property '"+"runtimeRequests"+"'"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(data6.structuredResult === undefined){const err26 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "structuredResult"},message:"must have required property '"+"structuredResult"+"'"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(data6.typedEvents === undefined){const err27 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/required",keyword:"required",params:{missingProperty: "typedEvents"},message:"must have required property '"+"typedEvents"+"'"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data6.schema !== undefined){if("paperclip.prp.capabilities.v1" !== data6.schema){const err28 = {instancePath:instancePath+"/capabilities/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.capabilities.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data6.sessionReusePolicy !== undefined){let data8 = data6.sessionReusePolicy;if(!(((data8 === "new_per_run") || (data8 === "reuse_per_issue")) || (data8 === "reuse_per_workspace"))){const err29 = {instancePath:instancePath+"/capabilities/sessionReusePolicy",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/sessionReusePolicy/enum",keyword:"enum",params:{allowedValues: schema41.properties.sessionReusePolicy.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data6.driver !== undefined){let data9 = data6.driver;if(data9 && typeof data9 == "object" && !Array.isArray(data9)){if(data9.kind === undefined){const err30 = {instancePath:instancePath+"/capabilities/driver",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(data9.version === undefined){const err31 = {instancePath:instancePath+"/capabilities/driver",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(data9.kind !== undefined){let data10 = data9.kind;if(typeof data10 === "string"){if(func1(data10) > 80){const err32 = {instancePath:instancePath+"/capabilities/driver/kind",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/kind/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(func1(data10) < 1){const err33 = {instancePath:instancePath+"/capabilities/driver/kind",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/kind/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}else {const err34 = {instancePath:instancePath+"/capabilities/driver/kind",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/kind/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}if(data9.version !== undefined){let data11 = data9.version;if(typeof data11 === "string"){if(func1(data11) > 80){const err35 = {instancePath:instancePath+"/capabilities/driver/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/version/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(func1(data11) < 1){const err36 = {instancePath:instancePath+"/capabilities/driver/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/version/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}else {const err37 = {instancePath:instancePath+"/capabilities/driver/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/properties/version/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}}else {const err38 = {instancePath:instancePath+"/capabilities/driver",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/driver/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data6.steer !== undefined){if(typeof data6.steer !== "boolean"){const err39 = {instancePath:instancePath+"/capabilities/steer",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/steer/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data6.interrupt !== undefined){if(typeof data6.interrupt !== "boolean"){const err40 = {instancePath:instancePath+"/capabilities/interrupt",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/interrupt/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data6.resume !== undefined){if(typeof data6.resume !== "boolean"){const err41 = {instancePath:instancePath+"/capabilities/resume",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/resume/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}if(data6.runtimeRequests !== undefined){if(typeof data6.runtimeRequests !== "boolean"){const err42 = {instancePath:instancePath+"/capabilities/runtimeRequests",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/runtimeRequests/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data6.structuredResult !== undefined){if(typeof data6.structuredResult !== "boolean"){const err43 = {instancePath:instancePath+"/capabilities/structuredResult",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/structuredResult/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data6.typedEvents !== undefined){if(typeof data6.typedEvents !== "boolean"){const err44 = {instancePath:instancePath+"/capabilities/typedEvents",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEvents/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data6.typedEventFamilies !== undefined){let data18 = data6.typedEventFamilies;if(Array.isArray(data18)){if(data18.length > 64){const err45 = {instancePath:instancePath+"/capabilities/typedEventFamilies",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/maxItems",keyword:"maxItems",params:{limit: 64},message:"must NOT have more than 64 items"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}const len0 = data18.length;for(let i0=0; i0 160){const err51 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/family",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/family/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}if(func1(data20) < 1){const err52 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/family",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/family/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(!pattern12.test(data20)){const err53 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/family",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/family/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_.-]*$"},message:"must match pattern \""+"^[a-z][a-z0-9_.-]*$"+"\""};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/family",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/family/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data19.version !== undefined){if(1 !== data19.version){const err55 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}if(data19.availability !== undefined){let data22 = data19.availability;if(!(((data22 === "available") || (data22 === "unsupported")) || (data22 === "policy_disabled"))){const err56 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/availability",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/availability/enum",keyword:"enum",params:{allowedValues: schema41.properties.typedEventFamilies.items.properties.availability.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data19.detailLevel !== undefined){let data23 = data19.detailLevel;if(!((data23 === "summary") || (data23 === "structured"))){const err57 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0+"/detailLevel",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/properties/detailLevel/enum",keyword:"enum",params:{allowedValues: schema41.properties.typedEventFamilies.items.properties.detailLevel.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}}else {const err58 = {instancePath:instancePath+"/capabilities/typedEventFamilies/" + i0,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}}else {const err59 = {instancePath:instancePath+"/capabilities/typedEventFamilies",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/typedEventFamilies/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}if(data6.semanticTools !== undefined){let data24 = data6.semanticTools;if(data24 && typeof data24 == "object" && !Array.isArray(data24)){if(data24.schema === undefined){const err60 = {instancePath:instancePath+"/capabilities/semanticTools",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}if(data24.schemaVersion === undefined){const err61 = {instancePath:instancePath+"/capabilities/semanticTools",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}if(data24.operations === undefined){const err62 = {instancePath:instancePath+"/capabilities/semanticTools",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/required",keyword:"required",params:{missingProperty: "operations"},message:"must have required property '"+"operations"+"'"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(data24.schema !== undefined){if("paperclip.prp.semantic_tools.v1" !== data24.schema){const err63 = {instancePath:instancePath+"/capabilities/semanticTools/schema",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.semantic_tools.v1"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data24.schemaVersion !== undefined){if(1 !== data24.schemaVersion){const err64 = {instancePath:instancePath+"/capabilities/semanticTools/schemaVersion",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/schemaVersion/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}if(data24.operations !== undefined){let data27 = data24.operations;if(Array.isArray(data27)){const len1 = data27.length;for(let i1=0; i1 160){const err69 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/operationId",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/operationId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}if(func1(data29) < 1){const err70 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/operationId",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/operationId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if(!pattern4.test(data29)){const err71 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/operationId",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/operationId/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._:-]*$"+"\""};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}else {const err72 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/operationId",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/operationId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data28.version !== undefined){if(1 !== data28.version){const err73 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/version",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}if(data28.availability !== undefined){let data31 = data28.availability;if(!(((data31 === "available") || (data31 === "denied")) || (data31 === "unsupported"))){const err74 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/availability",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/availability/enum",keyword:"enum",params:{allowedValues: schema41.properties.semanticTools.properties.operations.items.properties.availability.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}if(data28.redactionDisposition !== undefined){if("digest_only" !== data28.redactionDisposition){const err75 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/redactionDisposition",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/redactionDisposition/const",keyword:"const",params:{allowedValue: "digest_only"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data28.requiredClaims !== undefined){let data33 = data28.requiredClaims;if(Array.isArray(data33)){const len2 = data33.length;for(let i2=0; i2 160){const err76 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims/" + i2,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/items/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}if(func1(data34) < 1){const err77 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims/" + i2,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/items/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}else {const err78 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims/" + i2,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}let i3 = data33.length;let j0;if(i3 > 1){const indices0 = {};for(;i3--;){let item0 = data33[i3];if(typeof item0 !== "string"){continue;}if(typeof indices0[item0] == "number"){j0 = indices0[item0];const err79 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/uniqueItems",keyword:"uniqueItems",params:{i: i3, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i3+" are identical)"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;break;}indices0[item0] = i3;}}}else {const err80 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/requiredClaims",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/requiredClaims/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data28.reasonCode !== undefined){let data35 = data28.reasonCode;if(typeof data35 === "string"){if(func1(data35) > 160){const err81 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/reasonCode/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}if(func1(data35) < 1){const err82 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/reasonCode/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}if(!pattern14.test(data35)){const err83 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/reasonCode/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9_.:-]*$"},message:"must match pattern \""+"^[a-z][a-z0-9_.:-]*$"+"\""};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}else {const err84 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1+"/reasonCode",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/properties/reasonCode/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}}else {const err85 = {instancePath:instancePath+"/capabilities/semanticTools/operations/" + i1,schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}}else {const err86 = {instancePath:instancePath+"/capabilities/semanticTools/operations",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/properties/operations/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}}else {const err87 = {instancePath:instancePath+"/capabilities/semanticTools",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/semanticTools/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}if(data6.unsupported !== undefined){let data36 = data6.unsupported;if(Array.isArray(data36)){const len3 = data36.length;for(let i4=0; i4 1){const indices1 = {};for(;i5--;){let item1 = data36[i5];if(typeof item1 !== "string"){continue;}if(typeof indices1[item1] == "number"){j1 = indices1[item1];const err90 = {instancePath:instancePath+"/capabilities/unsupported",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/unsupported/uniqueItems",keyword:"uniqueItems",params:{i: i5, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i5+" are identical)"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;break;}indices1[item1] = i5;}}}else {const err91 = {instancePath:instancePath+"/capabilities/unsupported",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/properties/unsupported/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}}else {const err92 = {instancePath:instancePath+"/capabilities",schemaPath:"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}var _valid0 = _errs12 === errors;if(_valid0){valid1 = true;passing0 = 0;var props0 = true;}const _errs72 = errors;if(!(validate23(data6, {instancePath:instancePath+"/capabilities",parentData:data,parentDataProperty:"capabilities",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate23.errors : vErrors.concat(validate23.errors);errors = vErrors.length;}var _valid0 = _errs72 === errors;if(_valid0 && valid1){valid1 = false;passing0 = [passing0, 1];}else {if(_valid0){valid1 = true;passing0 = 1;if(props0 !== true){props0 = true;}}}if(!valid1){const err93 = {instancePath:instancePath+"/capabilities",schemaPath:"#/properties/capabilities/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}else {errors = _errs11;if(vErrors !== null){if(_errs11){vErrors.length = _errs11;}else {vErrors = null;}}}}if(data.commands !== undefined){let data38 = data.commands;if(Array.isArray(data38)){const len4 = data38.length;for(let i6=0; i6 160){const err101 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/commandId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}if(func1(data41) < 1){const err102 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/commandId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}else {const err103 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/commandId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}if(data39.controllerSeq !== undefined){let data42 = data39.controllerSeq;if(!(((typeof data42 == "number") && (!(data42 % 1) && !isNaN(data42))) && (isFinite(data42)))){const err104 = {instancePath:instancePath+"/commands/" + i6+"/controllerSeq",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/controllerSeq/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}if((typeof data42 == "number") && (isFinite(data42))){if(data42 < 1 || isNaN(data42)){const err105 = {instancePath:instancePath+"/commands/" + i6+"/controllerSeq",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/controllerSeq/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}}if(data39.type !== undefined){let data43 = data39.type;if(!((((((((((((((((((data43 === "run.prepare") || (data43 === "run.attach")) || (data43 === "session.open")) || (data43 === "turn.start")) || (data43 === "turn.steer")) || (data43 === "turn.interrupt")) || (data43 === "turn.stop")) || (data43 === "request.resolve")) || (data43 === "interaction.receipt")) || (data43 === "semantic_tool.result")) || (data43 === "session.snapshot")) || (data43 === "session.close")) || (data43 === "session.budget.increase")) || (data43 === "session.destroy")) || (data43 === "run.cancel")) || (data43 === "runner.drain")) || (data43 === "runner.suspend")) || (data43 === "runner.shutdown"))){const err106 = {instancePath:instancePath+"/commands/" + i6+"/type",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/type/enum",keyword:"enum",params:{allowedValues: schema45.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}if(data39.issuedAt !== undefined){let data44 = data39.issuedAt;if(typeof data44 === "string"){if(!(formats0.test(data44))){const err107 = {instancePath:instancePath+"/commands/" + i6+"/issuedAt",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/issuedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/commands/" + i6+"/issuedAt",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/issuedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}if(data39.deadlineAt !== undefined){let data45 = data39.deadlineAt;if(typeof data45 === "string"){if(!(formats0.test(data45))){const err109 = {instancePath:instancePath+"/commands/" + i6+"/deadlineAt",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/deadlineAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}else {const err110 = {instancePath:instancePath+"/commands/" + i6+"/deadlineAt",schemaPath:"https://paperclip.dev/schemas/prp/v1/command.schema.json/properties/deadlineAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}if(data39.precondition !== undefined){let data46 = data39.precondition;if(data46 && typeof data46 == "object" && !Array.isArray(data46)){if(data46.runnerState !== undefined){let data47 = data46.runnerState;if(Array.isArray(data47)){const len5 = data47.length;for(let i7=0; i7 160){const err122 = {instancePath:instancePath+"/commands/" + i6+"/payload/requestId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/requestId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}if(func1(data57) < 1){const err123 = {instancePath:instancePath+"/commands/" + i6+"/payload/requestId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/requestId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}else {const err124 = {instancePath:instancePath+"/commands/" + i6+"/payload/requestId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/requestId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}if(data56.objective !== undefined){let data58 = data56.objective;if(typeof data58 === "string"){if(func1(data58) > 4000){const err125 = {instancePath:instancePath+"/commands/" + i6+"/payload/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/objective/maxLength",keyword:"maxLength",params:{limit: 4000},message:"must NOT have more than 4000 characters"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}if(func1(data58) < 1){const err126 = {instancePath:instancePath+"/commands/" + i6+"/payload/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/objective/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}else {const err127 = {instancePath:instancePath+"/commands/" + i6+"/payload/objective",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/objective/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}if(data56.status !== undefined){let data59 = data56.status;if(!((data59 === "active") || (data59 === "paused"))){const err128 = {instancePath:instancePath+"/commands/" + i6+"/payload/status",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/status/enum",keyword:"enum",params:{allowedValues: schema46.allOf[0].then.properties.payload.properties.status.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}if(data56.tokenBudget !== undefined){let data60 = data56.tokenBudget;if((!(((typeof data60 == "number") && (!(data60 % 1) && !isNaN(data60))) && (isFinite(data60)))) && (data60 !== null)){const err129 = {instancePath:instancePath+"/commands/" + i6+"/payload/tokenBudget",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/tokenBudget/type",keyword:"type",params:{type: schema46.allOf[0].then.properties.payload.properties.tokenBudget.type},message:"must be integer,null"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}if((typeof data60 == "number") && (isFinite(data60))){if(data60 < 1 || isNaN(data60)){const err130 = {instancePath:instancePath+"/commands/" + i6+"/payload/tokenBudget",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/properties/tokenBudget/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}}}}else {const err131 = {instancePath:instancePath+"/commands/" + i6+"/payload",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/then/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}}var _valid2 = _errs118 === errors;valid32 = _valid2;if(valid32){var props2 = {};props2.payload = true;props2.type = true;}}if(!valid32){const err132 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}if(data39 && typeof data39 == "object" && !Array.isArray(data39)){if(data39.schema === undefined){const err133 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}if(data39.commandId === undefined){const err134 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "commandId"},message:"must have required property '"+"commandId"+"'"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}if(data39.controllerSeq === undefined){const err135 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "controllerSeq"},message:"must have required property '"+"controllerSeq"+"'"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}if(data39.type === undefined){const err136 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}if(data39.issuedAt === undefined){const err137 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "issuedAt"},message:"must have required property '"+"issuedAt"+"'"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}if(data39.payload === undefined){const err138 = {instancePath:instancePath+"/commands/" + i6,schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/required",keyword:"required",params:{missingProperty: "payload"},message:"must have required property '"+"payload"+"'"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}if(data39.schema !== undefined){if("paperclip.prp.command.v2" !== data39.schema){const err139 = {instancePath:instancePath+"/commands/" + i6+"/schema",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/schema/const",keyword:"const",params:{allowedValue: "paperclip.prp.command.v2"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}if(data39.commandId !== undefined){let data62 = data39.commandId;if(typeof data62 === "string"){if(func1(data62) > 160){const err140 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/commandId/maxLength",keyword:"maxLength",params:{limit: 160},message:"must NOT have more than 160 characters"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}if(func1(data62) < 1){const err141 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/commandId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}else {const err142 = {instancePath:instancePath+"/commands/" + i6+"/commandId",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/commandId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}if(data39.controllerSeq !== undefined){let data63 = data39.controllerSeq;if(!(((typeof data63 == "number") && (!(data63 % 1) && !isNaN(data63))) && (isFinite(data63)))){const err143 = {instancePath:instancePath+"/commands/" + i6+"/controllerSeq",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/controllerSeq/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}if((typeof data63 == "number") && (isFinite(data63))){if(data63 < 1 || isNaN(data63)){const err144 = {instancePath:instancePath+"/commands/" + i6+"/controllerSeq",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/controllerSeq/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}}if(data39.type !== undefined){let data64 = data39.type;if(!(((((((((((((((((((((data64 === "run.prepare") || (data64 === "run.attach")) || (data64 === "session.open")) || (data64 === "turn.start")) || (data64 === "turn.steer")) || (data64 === "turn.interrupt")) || (data64 === "turn.stop")) || (data64 === "request.resolve")) || (data64 === "interaction.receipt")) || (data64 === "semantic_tool.result")) || (data64 === "session.snapshot")) || (data64 === "session.close")) || (data64 === "session.budget.increase")) || (data64 === "session.destroy")) || (data64 === "run.cancel")) || (data64 === "runner.drain")) || (data64 === "runner.suspend")) || (data64 === "runner.shutdown")) || (data64 === "session.goal.get")) || (data64 === "session.goal.set")) || (data64 === "session.goal.clear"))){const err145 = {instancePath:instancePath+"/commands/" + i6+"/type",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/type/enum",keyword:"enum",params:{allowedValues: schema46.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}if(data39.issuedAt !== undefined){let data65 = data39.issuedAt;if(typeof data65 === "string"){if(!(formats0.test(data65))){const err146 = {instancePath:instancePath+"/commands/" + i6+"/issuedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/issuedAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}}else {const err147 = {instancePath:instancePath+"/commands/" + i6+"/issuedAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/issuedAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}}if(data39.deadlineAt !== undefined){let data66 = data39.deadlineAt;if(typeof data66 === "string"){if(!(formats0.test(data66))){const err148 = {instancePath:instancePath+"/commands/" + i6+"/deadlineAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/deadlineAt/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}}else {const err149 = {instancePath:instancePath+"/commands/" + i6+"/deadlineAt",schemaPath:"https://paperclip.dev/schemas/prp/v2/command.schema.json/properties/deadlineAt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}}if(data39.precondition !== undefined){let data67 = data39.precondition;if(data67 && typeof data67 == "object" && !Array.isArray(data67)){if(data67.runnerState !== undefined){let data68 = data67.runnerState;if(Array.isArray(data68)){const len8 = data68.length;for(let i10=0; i10 12000){const err182 = {instancePath:instancePath+"/result/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/maxLength",keyword:"maxLength",params:{limit: 12000},message:"must NOT have more than 12000 characters"};if(vErrors === null){vErrors = [err182];}else {vErrors.push(err182);}errors++;}if(func1(data85) < 1){const err183 = {instancePath:instancePath+"/result/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err183];}else {vErrors.push(err183);}errors++;}}else {const err184 = {instancePath:instancePath+"/result/summary",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/summary/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err184];}else {vErrors.push(err184);}errors++;}}if(data80.completionClaim !== undefined){let data86 = data80.completionClaim;if(data86 && typeof data86 == "object" && !Array.isArray(data86)){if(data86.contractRevision === undefined){const err185 = {instancePath:instancePath+"/result/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "contractRevision"},message:"must have required property '"+"contractRevision"+"'"};if(vErrors === null){vErrors = [err185];}else {vErrors.push(err185);}errors++;}if(data86.objectiveSatisfied === undefined){const err186 = {instancePath:instancePath+"/result/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "objectiveSatisfied"},message:"must have required property '"+"objectiveSatisfied"+"'"};if(vErrors === null){vErrors = [err186];}else {vErrors.push(err186);}errors++;}if(data86.criteria === undefined){const err187 = {instancePath:instancePath+"/result/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "criteria"},message:"must have required property '"+"criteria"+"'"};if(vErrors === null){vErrors = [err187];}else {vErrors.push(err187);}errors++;}if(data86.remainingWork === undefined){const err188 = {instancePath:instancePath+"/result/completionClaim",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/required",keyword:"required",params:{missingProperty: "remainingWork"},message:"must have required property '"+"remainingWork"+"'"};if(vErrors === null){vErrors = [err188];}else {vErrors.push(err188);}errors++;}if(data86.contractRevision !== undefined){let data87 = data86.contractRevision;if(typeof data87 === "string"){if(func1(data87) < 1){const err189 = {instancePath:instancePath+"/result/completionClaim/contractRevision",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/contractRevision/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err189];}else {vErrors.push(err189);}errors++;}}else {const err190 = {instancePath:instancePath+"/result/completionClaim/contractRevision",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/contractRevision/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err190];}else {vErrors.push(err190);}errors++;}}if(data86.objectiveSatisfied !== undefined){if(typeof data86.objectiveSatisfied !== "boolean"){const err191 = {instancePath:instancePath+"/result/completionClaim/objectiveSatisfied",schemaPath:"https://paperclip.dev/schemas/prp/v1/result.schema.json/properties/completionClaim/properties/objectiveSatisfied/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err191];}else {vErrors.push(err191);}errors++;}}if(data86.criteria !== undefined){let data89 = data86.criteria;if(Array.isArray(data89)){const len13 = data89.length;for(let i15=0; i15 12000){const err16 = {instancePath:instancePath+"/summary",schemaPath:"#/properties/summary/maxLength",keyword:"maxLength",params:{limit: 12000},message:"must NOT have more than 12000 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(func1(data4) < 1){const err17 = {instancePath:instancePath+"/summary",schemaPath:"#/properties/summary/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}else {const err18 = {instancePath:instancePath+"/summary",schemaPath:"#/properties/summary/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.completionClaim !== undefined){let data5 = data.completionClaim;if(data5 && typeof data5 == "object" && !Array.isArray(data5)){if(data5.contractRevision === undefined){const err19 = {instancePath:instancePath+"/completionClaim",schemaPath:"#/properties/completionClaim/required",keyword:"required",params:{missingProperty: "contractRevision"},message:"must have required property '"+"contractRevision"+"'"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(data5.objectiveSatisfied === undefined){const err20 = {instancePath:instancePath+"/completionClaim",schemaPath:"#/properties/completionClaim/required",keyword:"required",params:{missingProperty: "objectiveSatisfied"},message:"must have required property '"+"objectiveSatisfied"+"'"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(data5.criteria === undefined){const err21 = {instancePath:instancePath+"/completionClaim",schemaPath:"#/properties/completionClaim/required",keyword:"required",params:{missingProperty: "criteria"},message:"must have required property '"+"criteria"+"'"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(data5.remainingWork === undefined){const err22 = {instancePath:instancePath+"/completionClaim",schemaPath:"#/properties/completionClaim/required",keyword:"required",params:{missingProperty: "remainingWork"},message:"must have required property '"+"remainingWork"+"'"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}if(data5.contractRevision !== undefined){let data6 = data5.contractRevision;if(typeof data6 === "string"){if(func1(data6) < 1){const err23 = {instancePath:instancePath+"/completionClaim/contractRevision",schemaPath:"#/properties/completionClaim/properties/contractRevision/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/completionClaim/contractRevision",schemaPath:"#/properties/completionClaim/properties/contractRevision/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data5.objectiveSatisfied !== undefined){if(typeof data5.objectiveSatisfied !== "boolean"){const err25 = {instancePath:instancePath+"/completionClaim/objectiveSatisfied",schemaPath:"#/properties/completionClaim/properties/objectiveSatisfied/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data5.criteria !== undefined){let data8 = data5.criteria;if(Array.isArray(data8)){const len0 = data8.length;for(let i0=0; i0 { + it("admits the durable Rust projection and direct-driver item identity", () => { + expect(validate(permission), JSON.stringify(validate.errors)).toBe(true); + expect(validate({ ...permission, itemId: "permission-1" })).toBe(true); + }); + + it("rejects unknown, duplicate and unbounded approval choices", () => { + for (const choices of [ + [], + [{ key: "accept_always", label: "Allow forever" }], + [{ key: "accept", label: "Once" }, { key: "accept", label: "Different label" }], + [{ key: "accept", label: "x".repeat(501) }], + [{ key: "accept", label: "Once", permissionPolicy: "full-auto" }], + ]) expect(validate({ ...permission, choices })).toBe(false); + }); + + it("requires turn and origin binding and preserves the separate input contract", () => { + const { turnId: _turn, ...withoutTurn } = permission; + const { origin: _origin, ...withoutOrigin } = permission; + expect(validate(withoutTurn)).toBe(false); + expect(validate(withoutOrigin)).toBe(false); + expect(validate({ ...permission, type: "input" })).toBe(false); + expect(validate({ ...permission, requestKind: "runtime" })).toBe(false); + expect(validate({ ...permission, resolverPolicy: "human_only" })).toBe(false); + }); +}); diff --git a/packages/paperclip-runner/src/protocol/provider-descriptor.test.ts b/packages/paperclip-runner/src/protocol/provider-descriptor.test.ts index a4f1be6acd..130d033d30 100644 --- a/packages/paperclip-runner/src/protocol/provider-descriptor.test.ts +++ b/packages/paperclip-runner/src/protocol/provider-descriptor.test.ts @@ -2,10 +2,34 @@ import Ajv2020 from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { providerDescriptorSchema } from "./generated/schema-bundle.js"; +import { validatePrpEvent, type PrpEvent } from "./replay-contract.js"; const validate = new Ajv2020({ allErrors: true, strict: false }).compile(providerDescriptorSchema); describe("provider runtime descriptor", () => { + it("validates negotiated turn controls in descriptors and capability events", () => { + const descriptor = { provider: "acpx", driver: "acpx_runtime", agent: "pi", model: "explicit-model", + requestedModel: "explicit-model", executionKind: "local_process", providerVersion: "0.13.1", + agentProcessId: 42, acpProtocolVersion: 1, agentServerPackage: "pi-acp", agentServerVersion: "0.0.33", + acpxRecordId: "record", turnControls: { steering: true, queuedFollowUp: true } }; + expect(validate(descriptor), JSON.stringify(validate.errors)).toBe(true); + for (const turnControls of [{ steering: "true", queuedFollowUp: true }, { steering: true }, + { steering: true, queuedFollowUp: true, unverified: true }]) { + expect(validate({ ...descriptor, turnControls })).toBe(false); + } + const event: PrpEvent = { + schema: "paperclip.prp.event.v2", schemaVersion: 2, sourceEventId: "controls", sourceSeq: 1, + sourceInstanceId: "runner", sourceKind: "runner", runId: "run", eventType: "session.capabilities.updated", + priority: 0, emittedAt: "2026-09-28T12:00:00.000Z", payload: { + sessionGoals: { availability: "unsupported", reason: "provider has no native goals", actions: [], + autonomousUpdates: false, persistentAcrossResume: false, maxObjectiveChars: 4000, + tokenBudgetControl: false, usageReporting: false }, + turnControls: descriptor.turnControls, + }, + }; + expect(validatePrpEvent(event).ok).toBe(true); + expect(validatePrpEvent({ ...event, payload: { ...event.payload, turnControls: { steering: "true" } } }).ok).toBe(false); + }); it("accepts ACPX sidecar and child process identities separately", () => { expect(validate({ provider: "acpx", diff --git a/packages/paperclip-runner/src/reducer/session-reducer.test.ts b/packages/paperclip-runner/src/reducer/session-reducer.test.ts index 8abc6267a7..e85359f14c 100644 --- a/packages/paperclip-runner/src/reducer/session-reducer.test.ts +++ b/packages/paperclip-runner/src/reducer/session-reducer.test.ts @@ -113,4 +113,20 @@ describe("deterministic PRP session reducer", () => { "Resolved permission: Allow the fake command?", ]); }); + + it.each(["expired", "cancelled"] as const)("replays authoritative ACP request %s before terminal without leaving an actionable prompt", async status => { + const fixture = await loadFixture("interrupted-run"); + const base = fixture.events.find(event => event.eventType === "runtime_request.created")!; + const created: PrpEvent = { ...base, sourceEventId: "acpx-created", sourceSeq: 1 }; + const ended: PrpEvent = { ...base, sourceEventId: "acpx-ended", sourceSeq: 2, + eventType: `runtime_request.${status}`, + payload: { requestId: "request_interrupted_permission", requestKind: "permission_approval", requestType: "permission", + reason: status === "expired" ? "provider_process_lost" : "explicit_cancellation", replayAllowed: false } }; + const terminal: PrpEvent = { ...base, sourceEventId: "acpx-terminal", sourceSeq: 3, eventType: "run.terminal", + payload: { schema: "paperclip.prp.terminal.v1", status: "failed", turnTerminalState: "failed", runTerminalState: "failed", reportedWorkDisposition: "unknown" } }; + const events = [created, ended, terminal]; + const result = reduceSessionEvents(createSessionSnapshot(fixture), events); + expect(result.requests).toEqual([expect.objectContaining({ requestId: "request_interrupted_permission", status })]); + expect(reduceSessionEvents(result, events).requests).toEqual(result.requests); + }); }); diff --git a/packages/paperclip-runner/test/acpx-pi-receipt-package-contract.test.mjs b/packages/paperclip-runner/test/acpx-pi-receipt-package-contract.test.mjs new file mode 100644 index 0000000000..59d13f84fc --- /dev/null +++ b/packages/paperclip-runner/test/acpx-pi-receipt-package-contract.test.mjs @@ -0,0 +1,90 @@ +import assert from "node:assert/strict"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { pathToFileURL } from "node:url"; +import test from "node:test"; + +const require = createRequire(import.meta.url); +const root = dirname(require.resolve("acpx/package.json")); +const { + _: createConversation, y: recordSubmission, n: runPromptTurn, + dt: serialize, ut: parse, tt: assertKeyPolicy, +} = await import(pathToFileURL(join(root, "dist/live-checkpoint-BSIrfgVo.js"))); + +async function recordReceipt(metadata, usage = { inputTokens: 12, outputTokens: 3 }) { + const conversation = createConversation(); + const promptMessageId = recordSubmission(conversation, "Fixture prompt"); + await runPromptTurn({ + client: { prompt: async () => ({ stopReason: "end_turn", usage: { ...usage, _meta: { paperclipPi: metadata, secret: "DROP_ME" } } }) }, + sessionId: "session-1", prompt: "Fixture prompt", conversation, promptMessageId, + }); + return { conversation, promptMessageId }; +} + +test("Pi prompt receipts preserve exact per-request provenance and USD cost including zero", async () => { + for (const provenance of ["assistant_message_receipts", "assistant_message_and_compaction_receipts"]) for (const costUsd of [0, 0.0025]) { + const { conversation, promptMessageId } = await recordReceipt({ provenance, costUsd, extra: "DROP_ME" }); + assert.deepEqual(conversation.request_token_usage[promptMessageId].paperclip_pi, { provenance, cost_usd: costUsd }); + assert.equal(conversation.cumulative_token_usage.paperclip_pi, undefined); + assert.equal(conversation.cumulative_cost, undefined); + assert.equal(JSON.stringify(conversation).includes("DROP_ME"), false); + } +}); + +test("missing cost stays missing and untrusted provenance or invalid costs never become receipts", async () => { + const missing = await recordReceipt({ provenance: "assistant_message_receipts" }, {}); + assert.deepEqual(missing.conversation.request_token_usage[missing.promptMessageId].paperclip_pi, { provenance: "assistant_message_receipts" }); + for (const metadata of [{ provenance: "estimated", costUsd: 1 }, { provenance: "compaction_receipts", costUsd: 1 }, { provenance: "assistant_message_and_compaction_receipts", costUsd: -1 }, { provenance: "assistant_message_receipts", costUsd: -1 }, { provenance: "assistant_message_receipts", costUsd: Infinity }, { provenance: "assistant_message_receipts", costUsd: "0.01" }]) { + const { conversation, promptMessageId } = await recordReceipt(metadata); + assert.equal(conversation.request_token_usage[promptMessageId].paperclip_pi, undefined); + } +}); + +test("terminal failures preserve billable Pi receipts for the failing request before reporting failure", async () => { + for (const provenance of ["assistant_message_receipts", "assistant_message_and_compaction_receipts"]) { + const { conversation, promptMessageId: previousId } = await recordReceipt({ provenance, costUsd: 0.001 }); + const promptMessageId = recordSubmission(conversation, "Fail after paid work"); + let failureReceipt; + await assert.rejects(runPromptTurn({ + client: { prompt: async () => ({ + stopReason: "end_turn", + usage: { inputTokens: 21, outputTokens: 8, _meta: { paperclipPi: { provenance, costUsd: 0.003 }, secret: "DROP_ME" } }, + _meta: { jetbrains: { air: { version: 1, sessionFailure: { severity: "error", category: "limit" } } } }, + }) }, + sessionId: "session-1", prompt: "Fail after paid work", conversation, promptMessageId, + onTerminalSessionFailure() { failureReceipt = structuredClone(conversation.request_token_usage[promptMessageId]); }, + }), /ACP agent reported a terminal limit failure/); + assert.equal(failureReceipt?.input_tokens, 21); + assert.equal(failureReceipt?.output_tokens, 8); + assert.deepEqual(failureReceipt?.paperclip_pi, { provenance, cost_usd: 0.003 }); + assert.equal(conversation.request_token_usage[previousId].paperclip_pi.cost_usd, 0.001); + const disk = serialize({ + schema: "acpx.session.v1", acpxRecordId: "record-1", acpSessionId: "session-1", agentCommand: "verified-pi", + cwd: "/workspace", createdAt: "2026-09-28T00:00:00Z", lastUsedAt: "2026-09-28T00:00:00Z", lastSeq: 0, + ...conversation, + }); + assertKeyPolicy(disk); + const restored = parse(JSON.parse(JSON.stringify(disk))); + assert.ok(restored); + assert.deepEqual(restored.request_token_usage[promptMessageId].paperclip_pi, { provenance, cost_usd: 0.003 }); + assert.equal(restored.request_token_usage[promptMessageId].input_tokens, 21); + assert.equal(restored.request_token_usage[promptMessageId].output_tokens, 8); + assert.equal(JSON.stringify(restored).includes("DROP_ME"), false); + } +}); + +for (const provenance of ["assistant_message_receipts", "assistant_message_and_compaction_receipts"]) test(`${provenance} survives canonical disk serialization/reload with a closed metadata shape`, async () => { + const { conversation, promptMessageId } = await recordReceipt({ provenance, costUsd: 0.01 }); + const record = { + schema: "acpx.session.v1", acpxRecordId: "record-1", acpSessionId: "session-1", agentCommand: "verified-pi", + cwd: "/workspace", createdAt: "2026-09-28T00:00:00Z", lastUsedAt: "2026-09-28T00:00:00Z", lastSeq: 0, + ...conversation, + }; + const disk = serialize(record); + assertKeyPolicy(disk); + disk.request_token_usage[promptMessageId].paperclip_pi.extra = "DROP_ME"; + const restored = parse(JSON.parse(JSON.stringify(disk))); + assert.ok(restored); + assert.deepEqual(restored.request_token_usage[promptMessageId].paperclip_pi, { provenance, cost_usd: 0.01 }); + assert.equal(JSON.stringify(restored).includes("DROP_ME"), false); +}); diff --git a/packages/paperclip-runner/test/acpx-response-delivery-package-contract.test.mjs b/packages/paperclip-runner/test/acpx-response-delivery-package-contract.test.mjs new file mode 100644 index 0000000000..7d972be567 --- /dev/null +++ b/packages/paperclip-runner/test/acpx-response-delivery-package-contract.test.mjs @@ -0,0 +1,100 @@ +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { mkdtemp, rm } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { pathToFileURL } from "node:url"; +import test from "node:test"; + +const require = createRequire(import.meta.url); +const acpxRoot = process.env.PAPERCLIP_TEST_ACPX_PACKAGE_ROOT ?? dirname(require.resolve("acpx/package.json")); +const { k: AcpClient } = await import(pathToFileURL(join(acpxRoot, "dist/live-checkpoint-BSIrfgVo.js"))); +const deferred = () => Promise.withResolvers(); +const nextTick = () => new Promise(resolve => setImmediate(resolve)); + +function tapped(write) { + const client = new AcpClient({ agentCommand: "fixture", cwd: process.cwd(), permissionMode: "approve-all" }); + client.activePrompt = { sessionId: "session", elicitationController: new AbortController() }; + const stream = client.createTappedStream({ readable: new ReadableStream({ start() {} }), writable: new WritableStream({ write }) }); + return { client, stream, controller: client.activePrompt.elicitationController }; +} + +test("receipt stays pending until the exact JSON-RPC result reaches the underlying writer", async () => { + const gate = deferred(); let receipt; let delivered = false; + const { stream } = tapped(() => gate.promise); + const result = await stream.withResponseDelivery(0, new AbortController().signal, async promise => { + receipt = promise; void receipt.then(() => { delivered = true; }); return { selected: "yes" }; + }); + const writer = stream.writable.getWriter(); + const written = writer.write({ jsonrpc: "2.0", id: 0, result }); + await nextTick(); assert.equal(delivered, false); + gate.resolve(); await written; await receipt; assert.equal(delivered, true); + stream.abortResponseDeliveries(); writer.releaseLock(); +}); + +for (const failure of ["write", "cancel", "disconnect", "changed", "error"]) { + test(`receipt rejects ${failure} after callback resolution instead of falsely acknowledging delivery`, async () => { + const gate = deferred(); let receipt; + const { stream, controller } = tapped(() => gate.promise); + const result = await stream.withResponseDelivery("request", new AbortController().signal, async promise => { receipt = promise; return { selected: "yes" }; }); + const rejected = assert.rejects(receipt); + const writer = stream.writable.getWriter(); + const wire = failure === "error" ? { error: { code: -32000, message: "cancelled" } } + : { result: failure === "changed" ? { selected: "no" } : result }; + const written = writer.write({ jsonrpc: "2.0", id: "request", ...wire }); + if (failure === "write") { const writeFailed = assert.rejects(written); gate.reject(new Error("fixture write failure")); await writeFailed; } + else { + if (failure === "cancel") controller.abort(); + if (failure === "disconnect") stream.abortResponseDeliveries(); + gate.resolve(); await written; + } + await rejected; stream.abortResponseDeliveries(); writer.releaseLock(); + }); +} + +test("duplicate unresolved IDs poison receipts; an old response cannot acknowledge a new request", async () => { + let receipt; const pending = deferred(); + const { stream } = tapped(() => {}); + const first = stream.withResponseDelivery(0, new AbortController().signal, async promise => { receipt = promise; await pending.promise; return {}; }); + const rejected = assert.rejects(receipt); + await assert.rejects(stream.withResponseDelivery(0, new AbortController().signal, async () => ({})), /duplicated/); + await rejected; const expired = assert.rejects(first, /outlived/); pending.resolve(); await expired; +}); + +const fixture = String.raw` +const readline = require('node:readline'); +const send = value => process.stdout.write(JSON.stringify({jsonrpc:'2.0',...value})+'\n'); +let promptId; +readline.createInterface({input:process.stdin}).on('line', line => { + const message=JSON.parse(line); + if(message.method==='initialize') send({id:message.id,result:{protocolVersion:1,agentCapabilities:{},authMethods:[]}}); + else if(message.method==='session/new') send({id:message.id,result:{sessionId:'session'}}); + else if(message.method==='session/prompt') { + promptId=message.id; const mode=message.params.prompt[0].text; + if(mode==='permission') send({id:0,method:'session/request_permission',params:{sessionId:'session',toolCall:{toolCallId:'tool',title:'Edit',kind:'edit',status:'pending'},options:[{optionId:'yes',name:'Allow',kind:'allow_once'},{optionId:'no',name:'Deny',kind:'reject_once'}]}}); + else if(mode==='elicitation') send({id:0,method:'elicitation/create',params:{sessionId:'session',mode:'form',message:'Choose',requestedSchema:{type:'object',properties:{name:{type:'string'}}}}}); + else send({id:0,method:'fixture/question',params:{sessionId:'session'}}); + } else if(message.id===0 && !message.method) { + send({method:'session/update',params:{sessionId:'session',update:{sessionUpdate:'agent_message_chunk',content:{type:'text',text:JSON.stringify(message)}}}}); + send({id:promptId,result:{stopReason:'end_turn'}}); + } +}); +`; +for (const mode of ["extension", "permission", "elicitation"]) { + test(`real ${mode} request supplies a stream-owned receipt without callback deadlock`, { timeout: 10000 }, async () => { + const cwd = await mkdtemp(join(tmpdir(), "acpx-response-delivery-")); + let delivery; let seen = 0; + const remember = context => { seen++; assert.ok(context.responseDelivery instanceof Promise); delivery = context.responseDelivery; }; + const client = new AcpClient({ agentCommand: "fixture", cwd, authPolicy: "skip", permissionMode: "approve-all", fs: false, terminal: false, elicitationModes: ["form"], extensionMethods: ["fixture/question"], + spawnAgent: () => spawn(process.execPath, ["-e", fixture], { cwd, stdio: ["pipe", "pipe", "pipe"] }), + onExtensionRequest: async (_method, _params, context) => { remember(context); return { status: "accepted" }; }, + onPermissionRequest: async (_params, context) => { remember(context); return { outcome: "allow_once" }; }, + }); + try { + await client.start(); await client.createSession(); + const result = await client.prompt("session", mode, undefined, async (_params, context) => { remember(context); return { action: "accept", content: { name: "Paperclip" } }; }); + assert.equal(result.stopReason, "end_turn"); assert.equal(seen, 1); await delivery; + } finally { await client.close(); await rm(cwd, { recursive: true, force: true }); } + }); +} diff --git a/packages/paperclip-runner/test/acpx-rich-extensions-package-contract.test.mjs b/packages/paperclip-runner/test/acpx-rich-extensions-package-contract.test.mjs new file mode 100644 index 0000000000..103557b497 --- /dev/null +++ b/packages/paperclip-runner/test/acpx-rich-extensions-package-contract.test.mjs @@ -0,0 +1,119 @@ +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { mkdtemp, rm, readFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { pathToFileURL } from "node:url"; +import test from "node:test"; + +const require = createRequire(import.meta.url); +const acpxRoot = dirname(require.resolve("acpx/package.json")); +const { k: AcpClient } = await import(pathToFileURL(join(acpxRoot, "dist/live-checkpoint-BSIrfgVo.js"))); +const fixture = String.raw` +const readline = require('node:readline'); +const send = (value) => process.stdout.write(JSON.stringify({jsonrpc:'2.0',...value})+'\n'); +let promptId; +readline.createInterface({input:process.stdin}).on('line',(line)=>{ + const message=JSON.parse(line); + if (message.method==='initialize') send({id:message.id,result:{protocolVersion:1,agentCapabilities:{},authMethods:[],_meta:{receivedCapabilities:message.params.clientCapabilities}}}); + else if (message.method==='session/new') send({id:message.id,result:{sessionId:'session-1'}}); + else if (message.method==='session/prompt') { + promptId=message.id; + const mode=message.params.prompt[0].text; + send({method:'fixture/activity',params:{sessionId:'session-1',value:'active'}}); + send({method:'fixture/ignored',params:{sessionId:'session-1'}}); + send({method:'fixture/activity',params:{sessionId:'wrong-session'}}); + send({id:0,method:mode==='unknown'?'fixture/not-enabled':'fixture/question',params:{sessionId:mode==='wrong-session'?'wrong-session':'session-1',value:'private-question'}}); + } else if(message.method==='session/cancel') { send({id:promptId,result:{stopReason:'cancelled'}}); promptId=undefined; } + else if(message.id===0 && !message.method) { + send({method:'session/update',params:{sessionId:'session-1',update:{sessionUpdate:'agent_message_chunk',content:{type:'text',text:JSON.stringify(message)}}}}); + if(promptId!==undefined) { send({id:promptId,result:{stopReason:'end_turn'}}); promptId=undefined; } + } +}); +`; + +async function withClient(options, run) { + const cwd = await mkdtemp(join(tmpdir(), "paperclip-acpx-extensions-")); + const client = new AcpClient({ + agentCommand: "paperclip-test-verified-command", cwd, permissionMode: "approve-all", + fs: false, terminal: false, authPolicy: "skip", elicitationModes: ["form"], + suppressSdkConsoleErrors: true, + spawnAgent: () => spawn(process.execPath, ["-e", fixture], { cwd, stdio: ["pipe", "pipe", "pipe"] }), + extensionMethods: ["fixture/question", "fixture/activity"], + ...options, + }); + try { await client.start(); await client.createSession(); await run(client); } + finally { await client.close(); await rm(cwd, { recursive: true, force: true }); } +} + +test("real ACP stream delivers request ID zero and only allowlisted, session-bound notifications", { timeout: 10000 }, async () => { + const requests = []; const notifications = []; const wire = []; + await withClient({ + onAcpMessage: (direction, value) => wire.push({direction, value}), + onExtensionRequest: async (method, params, context) => { + requests.push({ method, params, requestId: context.requestId }); + assert.equal(context.signal.aborted, false); + return { answer: "private-answer" }; + }, + onExtensionNotification: (method, params) => notifications.push({method, params}), + }, async (client) => { + assert.equal((await client.prompt("session-1", "question")).stopReason, "end_turn"); + assert.equal(requests.length, 1); assert.equal(requests[0].requestId, 0); + assert.deepEqual(notifications, [{ method: "fixture/activity", params: { sessionId: "session-1", value: "active" } }]); + assert.equal(wire.some(({value}) => value.method === "fixture/question"), false); + assert.equal(wire.some(({value}) => value.id === 0 && !value.method && value.result?.answer === "private-answer"), false); + }); +}); + +test("initialize retains mandatory capabilities while merging provider metadata", { timeout: 10000 }, async () => { + await withClient({ clientCapabilities: { fs: { readTextFile: true }, terminal: true, elicitation: { url: {} }, _meta: { "github.com/copilot": { events: ["session.idle"] }, jetbrains: { removed: true } } } }, async (client) => { + const actual = client.initializeResult._meta.receivedCapabilities; + assert.deepEqual(actual.fs, { readTextFile: false, writeTextFile: false }); + assert.equal(actual.terminal, false); + assert.deepEqual(actual.elicitation, { form: {} }); + assert.deepEqual(actual._meta["github.com/copilot"], { events: ["session.idle"] }); + assert.deepEqual(actual._meta.jetbrains.air.capabilities, ["sessionFailure"]); + }); +}); + +test("unknown methods and cross-session requests never reach the host callback", { timeout: 10000 }, async () => { + let calls = 0; + await withClient({ onExtensionRequest: async () => { calls++; return {}; } }, async (client) => { + await client.prompt("session-1", "unknown"); + await client.prompt("session-1", "wrong-session"); + }); + assert.equal(calls, 0); +}); + +test("cancellation aborts unresolved extension requests even when the callback ignores it", { timeout: 10000 }, async () => { + let observe; const started = new Promise((resolve) => { observe = resolve; }); + let resolveCallback; let signal; + await withClient({ onExtensionRequest: async (_method, _params, context) => { + signal = context.signal; observe(); + return await new Promise((resolve) => { resolveCallback = resolve; }); + } }, async (client) => { + const pending = client.prompt("session-1", "question"); + await started; await client.cancel("session-1"); + await pending; assert.equal(signal.aborted, true); + resolveCallback({ answer: "too-late" }); + }); +}); + +test("extension allowlists cannot replace protocol handlers or grow without bound", () => { + const base = { agentCommand: "fixture", cwd: process.cwd(), permissionMode: "approve-all" }; + for (const method of ["session/request_permission", "fs/read_text_file", "elicitation/create", "initialize", "bad method/name"]) { + assert.throws(() => new AcpClient({ ...base, extensionMethods: [method] }), /extension method/); + } + assert.throws(() => new AcpClient({ ...base, extensionMethods: Array(65).fill("fixture/method") }), /bounded allowlist/); +}); + +test("runtime forwards extension hooks through ephemeral client options", async () => { + const runtime = await readFile(join(acpxRoot, "dist/runtime.js"), "utf8"); + for (const field of ["clientCapabilities", "extensionMethods", "onExtensionRequest", "onExtensionNotification"]) { + assert.ok(runtime.includes(`${field}: this.options.${field}`)); + } + const implementation = await readFile(join(acpxRoot, "dist/live-checkpoint-BSIrfgVo.js"), "utf8"); + const serialization = implementation.slice(implementation.indexOf("function serializeSessionRecordForDisk"), implementation.indexOf("function serializeSessionRecordForDisk") + 6000); + assert.doesNotMatch(serialization, /onExtensionRequest|onExtensionNotification|clientCapabilities|extensionMethods/); +}); diff --git a/packages/paperclip-runner/test/acpx-sidecar-contract.test.mjs b/packages/paperclip-runner/test/acpx-sidecar-contract.test.mjs index f3d2314b8e..770f9ffece 100644 --- a/packages/paperclip-runner/test/acpx-sidecar-contract.test.mjs +++ b/packages/paperclip-runner/test/acpx-sidecar-contract.test.mjs @@ -60,6 +60,24 @@ test("the ACPX sidecar schema shares the durable stable-identity boundary", () = } }); +test("rich activity has a closed display-only envelope and explicit run/turn scope", () => { + const message = { + ...messages[2], + eventType: "runtime.rich_event", + payload: { eventType: "plan.updated", itemId: "display-1", payload: {} }, + }; + assert.equal(validate(message), true, JSON.stringify(validate.errors)); + for (const field of ["runId", "turnId"]) { + assert.equal(validate({ ...message, [field]: null }), false); + } + for (const eventType of ["turn.completed", "run.result.proposed", "semantic_tool.input", "unknown"]) { + assert.equal(validate({ ...message, payload: { ...message.payload, eventType } }), false); + } + for (const field of ["sourceRef", "priority", "runId"]) { + assert.equal(validate({ ...message, payload: { ...message.payload, [field]: "forged" } }), false); + } +}); + test("the ACPX sidecar schema fails closed on drift", () => { for (const message of [ { ...messages[0], protocolVersion: protocolVersion + 1 }, @@ -106,3 +124,14 @@ function error() { retryable: false, }; } + +test("turn control schema preserves explicit modes and rejects open-ended dispatch", () => { + const message = { protocolVersion, id: 1, command: "turn.steer", params: { + turnId: "turn-1", controlId: "control-1", mode: "follow_up", message: "Then validate", + } }; + assert.equal(validate(message), true, JSON.stringify(validate.errors)); + for (const params of [ { ...message.params, mode: "cancel" }, { ...message.params, method: "arbitrary" }, + { ...message.params, controlId: "" }, { ...message.params, turnId: "wrong turn" }, { ...message.params, mode: undefined } ]) { + assert.equal(validate({ ...message, params }), false); + } +}); diff --git a/packages/shared/src/validators/issue.ts b/packages/shared/src/validators/issue.ts index 96ab22cbe9..95e9b23070 100644 --- a/packages/shared/src/validators/issue.ts +++ b/packages/shared/src/validators/issue.ts @@ -1346,7 +1346,7 @@ export const paperclipQuestionSetPayloadSchema = z .object({ schema: z.literal("paperclip.question_set.v1"), title: z.string().max(1000).optional(), - description: z.string().max(4000).optional(), + description: z.string().max(100_000).optional(), submitLabel: z.string().max(200).optional(), questions: z.array(paperclipQuestionSchema).min(1).max(64), }) diff --git a/patches/acpx@0.13.1.patch b/patches/acpx@0.13.1.patch index cd662aba25..2d535d0b32 100644 --- a/patches/acpx@0.13.1.patch +++ b/patches/acpx@0.13.1.patch @@ -11,7 +11,7 @@ index 5e2113a..b7b5151 100644 private setSessionModelThroughConfig; private setSessionModelThroughLegacyMethod; diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js -index d454fd7..991d21d 100644 +index d454fd7..4312cdd 100644 --- a/dist/live-checkpoint-BSIrfgVo.js +++ b/dist/live-checkpoint-BSIrfgVo.js @@ -1068,6 +1068,7 @@ function serializeSessionRecordForDisk(record) { @@ -22,7 +22,17 @@ index d454fd7..991d21d 100644 title: canonical.title, messages: canonical.messages, updated_at: canonical.updated_at, -@@ -1541,6 +1542,7 @@ function parseSessionRecord(raw) { +@@ -1239,7 +1240,8 @@ function parseRequestTokenUsage(raw) { + for (const [key, value] of Object.entries(record)) { + const parsed = parseTokenUsage(value); + if (parsed == null) return null; +- usage[key] = parsed; ++ const receipt = parsePaperclipPiReceipt(asRecord$4(value)?.paperclip_pi); ++ usage[key] = receipt ? { ...parsed, paperclip_pi: receipt } : parsed; + } + return usage; + } +@@ -1541,6 +1543,7 @@ function parseSessionRecord(raw) { lastAgentDisconnectReason: optionals.lastAgentDisconnectReason, protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0, agentCapabilities: asRecord$4(record.agent_capabilities), @@ -30,7 +40,7 @@ index d454fd7..991d21d 100644 title: conversation.title, messages: conversation.messages, updated_at: conversation.updated_at, -@@ -1661,7 +1663,7 @@ const ZED_TAG_KEYS = /* @__PURE__ */ new Set([ +@@ -1661,7 +1664,7 @@ const ZED_TAG_KEYS = /* @__PURE__ */ new Set([ "RedactedThinking", "ToolUse" ]); @@ -39,7 +49,7 @@ index d454fd7..991d21d 100644 const OPAQUE_VALUE_PATHS = /* @__PURE__ */ new Set([ "agent_capabilities", "messages.Agent.content.ToolUse.input", -@@ -3104,10 +3106,10 @@ function authEnvKey(methodId) { +@@ -3104,10 +3107,10 @@ function authEnvKey(methodId) { authEnvKeyCache.set(methodId, key); return key; } @@ -52,7 +62,7 @@ index d454fd7..991d21d 100644 if (typeof value === "string" && value.trim().length > 0) return value; } function protectedEnvKey(key) { -@@ -3135,8 +3137,21 @@ function promotePrefixedAuthEnvironment(env) { +@@ -3135,8 +3138,21 @@ function promotePrefixedAuthEnvironment(env) { } return protectedKeys; } @@ -76,7 +86,7 @@ index d454fd7..991d21d 100644 const protectedAuthEnvKeys = promotePrefixedAuthEnvironment(env); if (authCredentials) for (const [methodId, credential] of Object.entries(authCredentials)) { addAuthCredentialEnvKeys(protectedAuthEnvKeys, methodId, credential); -@@ -3178,10 +3193,10 @@ function resolveConfiguredAuthCredential(methodId, authCredentials) { +@@ -3178,10 +3194,10 @@ function resolveConfiguredAuthCredential(methodId, authCredentials) { const configCredentials = authCredentials ?? {}; return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)]; } @@ -89,7 +99,7 @@ index d454fd7..991d21d 100644 stdio: [ "pipe", "pipe", -@@ -3961,10 +3976,13 @@ function resolveClientCapabilities(params) { +@@ -3961,10 +3977,13 @@ function resolveClientCapabilities(params) { ...params.elicitationModes.includes("url") ? { url: {} } : {} } } : {} }; @@ -105,7 +115,46 @@ index d454fd7..991d21d 100644 }; } function hasResponseField(response, field) { -@@ -4216,9 +4234,21 @@ var AcpClient = class { +@@ -4067,6 +4086,27 @@ function createNdJsonMessageStream(agentCommand, output, input) { + } }) + }; + } ++function boundedExtensionRecord(value) { ++ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError("ACP extension payload must be an object"); ++ const prototype = Object.getPrototypeOf(value); ++ if (prototype !== Object.prototype && prototype !== null) throw new TypeError("ACP extension payload must be a plain object"); ++ const serialized = JSON.stringify(value); ++ if (Buffer.byteLength(serialized) > 256 * 1024) throw new TypeError("ACP extension payload exceeds its bounded size"); ++ return JSON.parse(serialized); ++} ++function closedExtensionMethods(value) { ++ if (value === void 0) return Object.freeze([]); ++ if (!Array.isArray(value) || value.length > 64) throw new TypeError("ACP extension methods must be a bounded allowlist"); ++ for (const method of value) { ++ if (typeof method !== "string" || !/^[A-Za-z_][A-Za-z0-9._/-]{1,255}$/.test(method) || !method.includes("/") || /^(?:session|fs|terminal|elicitation|notifications)\//.test(method)) throw new TypeError("ACP extension method cannot override a standard method"); ++ } ++ return Object.freeze([...new Set(value)]); ++} ++function mergeRuntimeClientCapabilities(base, extra) { ++ if (extra === void 0) return base; ++ const additional = boundedExtensionRecord(extra); ++ return { ...additional, ...base, _meta: { ...additional._meta, ...base._meta } }; ++} + var AcpClient = class { + options; + connection; +@@ -4105,7 +4145,9 @@ var AcpClient = class { + cwd: asAbsoluteCwd(options.cwd), + authPolicy: options.authPolicy ?? "skip", + permissionPolicy: snapshotPermissionPolicy(options.permissionPolicy), +- elicitationModes: normalizeElicitationModes(options.elicitationModes) ++ elicitationModes: normalizeElicitationModes(options.elicitationModes), ++ extensionMethods: closedExtensionMethods(options.extensionMethods), ++ clientCapabilities: options.clientCapabilities === void 0 ? void 0 : boundedExtensionRecord(options.clientCapabilities) + }; + this.eventHandlers = { + onAcpMessage: this.options.onAcpMessage, +@@ -4216,9 +4258,21 @@ var AcpClient = class { this.lastAgentExit = void 0; this.lastKnownPid = child.pid ?? void 0; this.attachAgentLifecycleObservers(child); @@ -127,7 +176,15 @@ index d454fd7..991d21d 100644 if (!this.options.verbose) return; process.stderr.write(chunk); }); -@@ -4253,7 +4283,12 @@ var AcpClient = class { +@@ -4226,6 +4280,7 @@ var AcpClient = class { + const output = Readable.toWeb(child.stdout); + const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output)); + const connection = this.createConnection(stream, launch); ++ connection.signal.addEventListener("abort", () => stream.abortResponseDeliveries(), { once: true }); + connection.signal.addEventListener("abort", () => { + this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null); + }, { once: true }); +@@ -4253,7 +4308,12 @@ var AcpClient = class { geminiAcp: isGeminiAcpCommand(spawnCommand, args), copilotAcp: isCopilotAcpCommand(spawnCommand, args), claudeAcp: isClaudeAcpCommand(spawnCommand, args), @@ -141,7 +198,7 @@ index d454fd7..991d21d 100644 }; } logAgentLaunch(plan) { -@@ -4279,10 +4314,17 @@ var AcpClient = class { +@@ -4279,10 +4339,17 @@ var AcpClient = class { } async spawnAgentProcess(plan) { const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform, plan.spawnOptions.env); @@ -161,7 +218,143 @@ index d454fd7..991d21d 100644 try { await waitForSpawn$1(spawnedChild); } catch (error) { -@@ -4422,7 +4464,10 @@ var AcpClient = class { +@@ -4293,10 +4360,10 @@ var AcpClient = class { + createConnection(stream, launch) { + const app = client({ name: "acpx" }).onNotification(methods.client.session.update, async ({ params }) => { + await this.handleSessionUpdate(params); +- }).onNotification(methods.client.elicitation.complete, async () => {}).onRequest(methods.client.session.requestPermission, async ({ params }) => { +- return await this.handlePermissionRequest(params); ++ }).onNotification(methods.client.elicitation.complete, async () => {}).onRequest(methods.client.session.requestPermission, async ({ params, requestId, signal }) => { ++ return await stream.withResponseDelivery(requestId, signal, (responseDelivery) => this.handlePermissionRequest(params, responseDelivery)); + }).onRequest(methods.client.elicitation.create, async ({ params, requestId, signal }) => { +- return await this.handleElicitationRequest(params, requestId, signal); ++ return await stream.withResponseDelivery(requestId, signal, (responseDelivery) => this.handleElicitationRequest(params, requestId, signal, responseDelivery)); + }).onRequest(methods.client.fs.readTextFile, async ({ params }) => { + return await this.handleReadTextFile(params); + }).onRequest(methods.client.fs.writeTextFile, async ({ params }) => { +@@ -4312,6 +4379,11 @@ var AcpClient = class { + }).onRequest(methods.client.terminal.release, async ({ params }) => { + return await this.handleReleaseTerminal(params); + }); ++ for (const method of this.options.extensionMethods) { ++ if (this.options.onExtensionRequest) app.onRequest(method, boundedExtensionRecord, async ({ params, requestId, signal }) => { ++ return await stream.withResponseDelivery(requestId, signal, (responseDelivery) => this.handleExtensionRequest(method, params, requestId, signal, responseDelivery)); ++ }); ++ } + if (launch.devinAcp) app.onRequest("_cognition.ai/request_diagnostics", (params) => { + return params && typeof params === "object" && !Array.isArray(params) ? params : {}; + }, async () => ({})); +@@ -4333,12 +4405,12 @@ var AcpClient = class { + async initializeProtocolConnection(connection, launch) { + const initializePromise = connection.initialize({ + protocolVersion: PROTOCOL_VERSION, +- clientCapabilities: resolveClientCapabilities({ ++ clientCapabilities: mergeRuntimeClientCapabilities(resolveClientCapabilities({ + devinAcp: launch.devinAcp, + fs: this.options.fs !== false, + terminal: this.options.terminal !== false, + elicitationModes: this.options.elicitationModes ?? [] +- }), ++ }), this.options.clientCapabilities), + clientInfo: resolveClientInfo(launch.devinAcp) + }); + const initialized = launch.geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise; +@@ -4358,6 +4430,36 @@ var AcpClient = class { + throw normalizedError; + } + createTappedStream(base) { ++ const deliveries = new Map(); ++ const scope = new AbortController(); ++ const abortResponseDeliveries = () => scope.abort(new Error("ACP response connection closed before delivery")); ++ const withResponseDelivery = async (id, requestSignal, handler) => { ++ if (!(id === null || typeof id === "string" || typeof id === "number" && Number.isFinite(id))) throw new Error("ACP response request id is invalid"); ++ if (deliveries.has(id) || deliveries.size >= 128) { ++ abortResponseDeliveries(); ++ throw new Error("ACP response request id is duplicated or pending deliveries exceeded their bound"); ++ } ++ const active = this.activePrompt; ++ const signal = AbortSignal.any([scope.signal, requestSignal, ...(active ? [active.elicitationController.signal] : [])]); ++ let settle, reject; ++ const responseDelivery = new Promise((resolve, fail) => { settle = resolve; reject = fail; }); ++ // The host may decline the request without awaiting its receipt. ++ void responseDelivery.catch(() => {}); ++ const abort = () => reject(new Error("ACP response delivery was cancelled or disconnected")); ++ const receipt = { settle, reject, expected: void 0, cleanup: () => signal.removeEventListener("abort", abort) }; ++ deliveries.set(id, receipt); ++ signal.addEventListener("abort", abort, { once: true }); ++ if (signal.aborted) abort(); ++ try { ++ const result = await handler(responseDelivery); ++ if (signal.aborted || this.activePrompt !== active) throw new Error("ACP response outlived its admitted prompt"); ++ receipt.expected = JSON.stringify(result); ++ return result; ++ } catch (error) { ++ reject(new Error("ACP request failed before response delivery")); ++ throw error; ++ } ++ }; + const onAcpMessage = () => this.eventHandlers.onAcpMessage; + const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage; + const elicitationRequestIds = /* @__PURE__ */ new Set(); +@@ -4367,8 +4469,9 @@ var AcpClient = class { + const shouldSuppressInboundReplaySessionUpdate = (message) => { + return this.suppressReplaySessionUpdateMessages && isSessionUpdateNotification(message); + }; ++ const onExtensionNotification = (message) => this.handleExtensionNotification(message); + const observeInbound = (message) => { +- const requestId = elicitationRequestId(message); ++ const requestId = elicitationRequestId(message) ?? ("id" in message && this.options.extensionMethods.includes(message.method) ? message.id : void 0); + if (requestId !== void 0) { + elicitationRequestIds.add(requestId); + return; +@@ -4379,6 +4482,7 @@ var AcpClient = class { + } + }; + return { ++ withResponseDelivery, abortResponseDeliveries, + readable: new ReadableStream({ async start(controller) { + const reader = base.readable.getReader(); + try { +@@ -4387,10 +4491,16 @@ var AcpClient = class { + if (done) break; + if (!value) continue; + observeInbound(value); +- if (isExtensionNotification(value)) continue; ++ if (isExtensionNotification(value)) { ++ onExtensionNotification(value); ++ continue; ++ } + controller.enqueue(value); + } + } finally { ++ abortResponseDeliveries(); ++ for (const receipt of deliveries.values()) receipt.cleanup(); ++ deliveries.clear(); + reader.releaseLock(); + controller.close(); + } +@@ -4403,10 +4513,20 @@ var AcpClient = class { + onAcpOutputMessage()?.("outbound", message); + onAcpMessage()?.("outbound", message); + } ++ const receipt = id === void 0 ? void 0 : deliveries.get(id); + const writer = base.writable.getWriter(); + try { + await writer.write(message); ++ if (receipt) { ++ if (!("result" in message) || receipt.expected === void 0 || JSON.stringify(message.result) !== receipt.expected) receipt.reject(new Error("ACP wrote an error or changed response instead of the selected resolution")); ++ else receipt.settle(); ++ } ++ } catch (error) { ++ receipt?.reject(new Error("ACP response write failed")); ++ abortResponseDeliveries(); ++ throw error; + } finally { ++ if (receipt) { receipt.cleanup(); deliveries.delete(id); } + writer.releaseLock(); + } + } }) +@@ -4422,7 +4542,10 @@ var AcpClient = class { const createPromise = this.runConnectionRequest(() => connection.newSession({ cwd: sessionCwd, mcpServers: this.options.mcpServers ?? [], @@ -173,7 +366,7 @@ index d454fd7..991d21d 100644 })); result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise; } catch (error) { -@@ -4864,7 +4909,7 @@ var AcpClient = class { +@@ -4864,7 +4987,7 @@ var AcpClient = class { } selectAuthMethod(methods) { for (const method of methods) { @@ -182,7 +375,7 @@ index d454fd7..991d21d 100644 if (envCredential) return { methodId: method.id, credential: envCredential, -@@ -4890,7 +4935,7 @@ var AcpClient = class { +@@ -4890,7 +5013,7 @@ var AcpClient = class { } readAgentSpecificEnvCredential(methodId) { if (!this.isGrokBuildAcpCommand() || methodId !== "xai.api_key") return; @@ -191,7 +384,89 @@ index d454fd7..991d21d 100644 return typeof value === "string" && value.trim().length > 0 ? value : void 0; } selectAgentManagedAuthMethod(methodId) { -@@ -5028,6 +5073,12 @@ var AcpClient = class { +@@ -4915,9 +5038,9 @@ var AcpClient = class { + await connection.authenticate({ methodId: selected.methodId }); + this.log(`authenticated with method ${selected.methodId} (${selected.source})`); + } +- async handlePermissionRequest(params) { ++ async handlePermissionRequest(params, responseDelivery) { + if (this.cancellingSessionIds.has(params.sessionId)) return cancelledPermissionResponse(); +- const hostResponse = await this.tryHandlePermissionRequestWithHost(params); ++ const hostResponse = await this.tryHandlePermissionRequestWithHost(params, responseDelivery); + if (hostResponse) return hostResponse; + const { response, recorded } = await this.resolvePermissionRequestFromMode(params); + if (!recorded) { +@@ -4926,14 +5049,49 @@ var AcpClient = class { + } + return response; + } +- async handleElicitationRequest(request, requestId, requestSignal) { ++ async handleExtensionRequest(method, params, requestId, requestSignal, responseDelivery) { ++ const active = this.activePrompt; ++ const connection = this.connection; ++ if (!this.options.extensionMethods.includes(method) || !this.options.onExtensionRequest) throw new Error("ACP extension method is not enabled"); ++ if (!active || !connection || this.closing || (params.sessionId !== void 0 && params.sessionId !== active.sessionId)) throw new Error("ACP extension request has no matching active prompt"); ++ if (!(typeof requestId === "string" || typeof requestId === "number" && Number.isFinite(requestId))) throw new Error("ACP extension request id is invalid"); ++ const signal = AbortSignal.any([requestSignal, connection.signal, active.elicitationController.signal]); ++ signal.throwIfAborted(); ++ let onAbort; ++ const aborted = new Promise((_, reject) => { ++ onAbort = () => reject(new Error("ACP extension request was cancelled")); ++ signal.addEventListener("abort", onAbort, { once: true }); ++ }); ++ try { ++ const result = await Promise.race([Promise.resolve().then(() => this.options.onExtensionRequest(method, boundedExtensionRecord(params), { requestId, signal, responseDelivery })), aborted]); ++ signal.throwIfAborted(); ++ if (this.activePrompt !== active || this.connection !== connection) throw new Error("ACP extension request outlived its prompt"); ++ return boundedExtensionRecord(result); ++ } finally { ++ signal.removeEventListener("abort", onAbort); ++ } ++ } ++ handleExtensionNotification(message) { ++ if (!this.options.extensionMethods.includes(message.method) || !this.options.onExtensionNotification || this.closing) return; ++ const active = this.activePrompt; ++ if (!active || active.elicitationController.signal.aborted) return; ++ try { ++ const params = boundedExtensionRecord(message.params); ++ if (params.sessionId !== void 0 && params.sessionId !== active.sessionId) return; ++ this.options.onExtensionNotification(message.method, params); ++ } catch { ++ this.log("ACP extension notification was rejected"); ++ } ++ } ++ async handleElicitationRequest(request, requestId, requestSignal, responseDelivery) { + const resolved = this.resolveElicitationOwner(request); + if ("response" in resolved) return resolved.response; + const { active, handler } = resolved.owner; + const signal = AbortSignal.any([requestSignal, active.elicitationController.signal]); + const handlerAttempt = Promise.resolve().then(async () => await handler(request, { + requestId, +- signal ++ signal, ++ responseDelivery + })).then((response) => ({ + kind: "response", + response +@@ -4970,7 +5128,7 @@ var AcpClient = class { + isElicitationSessionCancelling(sessionId) { + return this.closing || this.cancellingSessionIds.has(sessionId); + } +- async tryHandlePermissionRequestWithHost(params) { ++ async tryHandlePermissionRequestWithHost(params, responseDelivery) { + if (!this.options.onPermissionRequest) return; + const signal = this.cancellationSignalForSession(params.sessionId); + try { +@@ -4978,7 +5136,7 @@ var AcpClient = class { + sessionId: params.sessionId, + raw: params, + inferredKind: inferToolKind(params) +- }, { signal }); ++ }, { signal, responseDelivery }); + return this.hostPermissionDecisionResponse(params, signal, decision); + } catch (error) { + return this.hostPermissionErrorResponse(params, signal, error); +@@ -5028,6 +5186,12 @@ var AcpClient = class { attachAgentLifecycleObservers(child) { child.once("exit", (exitCode, signal) => { this.recordAgentExit("process_exit", exitCode, signal); @@ -204,7 +479,30 @@ index d454fd7..991d21d 100644 }); child.once("close", (exitCode, signal) => { this.recordAgentExit("process_close", exitCode, signal); -@@ -6435,6 +6486,27 @@ async function withConnectedSession(options) { +@@ -5771,10 +5935,20 @@ function recordSessionUpdate(conversation, state, notification, timestamp = isoN + trimConversationForRuntime(conversation); + return acpx; + } ++function parsePaperclipPiReceipt(raw, costField = "cost_usd") { ++ const receipt = asRecord$4(raw); ++ if (receipt?.provenance !== "assistant_message_receipts" && receipt?.provenance !== "assistant_message_and_compaction_receipts") return; ++ const cost = receipt[costField]; ++ if (cost !== void 0 && !isNonNegativeFiniteNumber(cost)) return; ++ return { provenance: receipt.provenance, ...cost !== void 0 ? { cost_usd: cost } : {} }; ++} + function recordPromptResponseUsage(conversation, usage, promptMessageId, timestamp = isoNow()) { + const tokenUsage = sourceToTokenUsage(usage); +- if (!tokenUsage) return false; +- applyTokenUsage(conversation, tokenUsage, promptMessageId); ++ const receipt = parsePaperclipPiReceipt(asRecord$4(asRecord$4(usage)?._meta)?.paperclipPi, "costUsd"); ++ if (!tokenUsage && !receipt) return false; ++ if (tokenUsage) applyTokenUsage(conversation, tokenUsage, promptMessageId); ++ const userId = promptMessageId ?? lastUserMessageId(conversation); ++ if (receipt && userId) conversation.request_token_usage[userId] = { ...conversation.request_token_usage[userId], paperclip_pi: receipt }; + updateConversationTimestamp(conversation, timestamp); + trimConversationForRuntime(conversation); + return true; +@@ -6435,6 +6609,27 @@ async function withConnectedSession(options) { //#region src/runtime/engine/prompt-turn.ts const SESSION_REPLY_IDLE_MS = 1e3; const SESSION_REPLY_DRAIN_TIMEOUT_MS = 5e3; @@ -232,10 +530,12 @@ index d454fd7..991d21d 100644 async function runPromptTurn(params) { try { const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestStarted, params.onElicitation); -@@ -6444,6 +6516,19 @@ async function runPromptTurn(params) { +@@ -6444,7 +6639,21 @@ async function runPromptTurn(params) { idleMs: SESSION_REPLY_IDLE_MS, timeoutMs: SESSION_REPLY_DRAIN_TIMEOUT_MS }).catch(() => {}); ++ // A failed terminal result may still include authoritative billable usage. + recordPromptResponseUsage(params.conversation, response.usage, params.promptMessageId); + const terminalFailureCategory = typedTerminalSessionFailureCategory(response); + if (terminalFailureCategory !== null) { + // Inspect provider text only in memory, before emitting the safe category. @@ -249,11 +549,18 @@ index d454fd7..991d21d 100644 + } catch {} + throw new Error(`ACP agent reported a terminal ${terminalFailureCategory} failure.`); + } - recordPromptResponseUsage(params.conversation, response.usage, params.promptMessageId); return { stopReason: response.stopReason, + source: "rpc" +@@ -6518,4 +6727,4 @@ var LiveSessionCheckpoint = class { + //#endregion + export { writeSessionRecord as $, PERMISSION_POLICY_ACTIONS as $t, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as A, TimeoutError as At, getAcpxVersion as B, formatErrorMessage as Bt, mergeSessionOptions as C, PromptInputValidationError as Ct, applyLifecycleSnapshotToRecord as D, promptToDisplayText as Dt, applyConversation as E, parsePromptSource as Et, modelStateFromConfigOptions as F, normalizeAgentName$1 as Ft, findSession as G, toAcpErrorPayload as Gt, DEFAULT_HISTORY_LIMIT as H, normalizeOutputError as Ht, normalizeAgentCommandInput as I, resolveAgentArgv as It, listSessions as J, NON_INTERACTIVE_PERMISSION_POLICIES as Jt, findSessionByDirectoryWalk as K, AUTH_POLICIES as Kt, renderArgvIdentity as L, resolveAgentCommand as Lt, RequestedModelUnsupportedError as M, withTimeout as Mt, assertRequestedModelSupported as N, DEFAULT_AGENT_NAME as Nt, reconcileAgentSessionId as O, textPrompt as Ot, isRequestedModelUnsupportedError as P, listBuiltInAgents as Pt, resolveSessionRecord as Q, PERMISSION_MODES as Qt, runTimedExecFile as R, resolveCanonicalAgentName as Rt, advertisedModelState as S, parsePromptStopReason as St, sessionOptionsFromRecord as T, mergePromptSourceWithText as Tt, absolutePath as U, extractAcpError as Ut, permissionModeSatisfies as V, isRetryablePromptError as Vt, findGitRepositoryRoot as W, isAcpResourceNotFoundError as Wt, normalizeName as X, OUTPUT_ERROR_ORIGINS as Xt, listSessionsForAgent as Y, OUTPUT_ERROR_CODES as Yt, pruneSessions as Z, OUTPUT_FORMATS as Zt, createSessionConversation as _, sessionEventLockPath as _t, applyRequestedModelIfAdvertised as a, measurePerf as at, recordSessionUpdate as b, isAcpJsonRpcMessage as bt, setCurrentModelId as c, setPerfGauge as ct, setDesiredModelId as d, serializeSessionRecordForDisk as dt, SESSION_RECORD_SCHEMA as en, createAtomicWriteTempPath as et, syncAdvertisedModelState as f, normalizeRuntimeSessionId as ft, cloneSessionConversation as g, sessionEventActivePath as gt, cloneSessionAcpxState as h, sessionBaseDir$1 as ht, connectAndLoadSession as i, QueueProtocolError as in, incrementPerfCounter as it, REQUESTED_MODEL_UNSUPPORTED_REASONS as j, withInterrupt as jt, AcpClient as k, InterruptedError as kt, setDesiredConfigOption as l, startPerfTimer as lt, applyConfigOptionsToState as m, defaultSessionEventLog as mt, runPromptTurn as n, AgentSpawnError as nn, formatPerfMetric as nt, currentModelIdFromSetModelResponse as o, recordPerfDuration as ot, applyConfigOptionsToRecord as p, DEFAULT_EVENT_SEGMENT_MAX_BYTES as pt, isoNow$2 as q, EXIT_CODES as qt, withConnectedSession as r, QueueConnectionError as rn, getPerfMetricsSnapshot as rt, clearDesiredConfigOption as s, resetPerfMetrics as st, LiveSessionCheckpoint as t, AcpxOperationalError as tn, assertPersistedKeyPolicy as tt, setDesiredModeId as u, parseSessionRecord as ut, recordClientOperation as v, sessionEventSegmentPath as vt, persistSessionOptions as w, isPromptInput as wt, trimConversationForRuntime as x, parseJsonRpcErrorMessage as xt, recordPromptSubmission as y, extractSessionUpdateNotification as yt, splitCommandLine as z, exitCodeForOutputErrorCode as zt }; + +-//# sourceMappingURL=live-checkpoint-BSIrfgVo.js.map +\ No newline at end of file ++//# sourceMappingURL=live-checkpoint-BSIrfgVo.js.map diff --git a/dist/runtime.d.ts b/dist/runtime.d.ts -index e8102ac..2277321 100644 +index e8102ac..d3a9ba6 100644 --- a/dist/runtime.d.ts +++ b/dist/runtime.d.ts @@ -1,7 +1,8 @@ @@ -297,10 +604,24 @@ index e8102ac..2277321 100644 } | { type: "tool_call"; text: string; -@@ -313,6 +324,35 @@ type AcpRuntimeOptions = { +@@ -312,7 +323,49 @@ type AcpRuntimeOptions = { + elicitationModes?: readonly AcpElicitationMode[]; onPermissionRequest?: (req: AcpPermissionRequest, ctx: { signal: AbortSignal; ++ /** Resolves after this exact response is written to the current provider pipe. */ ++ responseDelivery?: Promise; }) => Promise; ++ /** Ephemeral initialize capabilities; mandatory base capabilities take precedence. */ ++ clientCapabilities?: Record; ++ /** Closed list of inbound extension request and notification method names. */ ++ extensionMethods?: readonly string[]; ++ /** Bound to the current connection and active prompt; never persisted. */ ++ onExtensionRequest?: (method: string, params: Record, context: { ++ requestId: string | number; ++ signal: AbortSignal; ++ responseDelivery?: Promise; ++ }) => Promise>; ++ onExtensionNotification?: (method: string, params: Record) => void; + /** Ephemeral allowlisted environment evaluated immediately before child spawn. */ + spawnEnvironment?: () => Record; + /** Host-only spawn cwd; does not change the cwd advertised in session/new. */ @@ -333,7 +654,7 @@ index e8102ac..2277321 100644 }; type AcpFileSessionStoreOptions = { stateDir: string; -@@ -369,6 +409,7 @@ declare class AcpRuntimeManager { +@@ -369,6 +422,7 @@ declare class AcpRuntimeManager { private createAndSaveRuntimeRecord; private retainInitializedSessionOwner; startTurn(input: { @@ -341,7 +662,7 @@ index e8102ac..2277321 100644 handle: AcpRuntimeHandle; text: string; attachments?: AcpRuntimeTurnAttachment[]; -@@ -414,6 +455,7 @@ declare class AcpRuntimeManager { +@@ -414,6 +468,7 @@ declare class AcpRuntimeManager { private cleanupRuntimeTurn; private finalizeRuntimeTurnRecord; runTurn(input: { @@ -350,7 +671,7 @@ index e8102ac..2277321 100644 text: string; attachments?: AcpRuntimeTurnAttachment[]; diff --git a/dist/runtime.js b/dist/runtime.js -index a1f4a70..57117c9 100644 +index a1f4a70..fe2484d 100644 --- a/dist/runtime.js +++ b/dist/runtime.js @@ -371,7 +371,7 @@ const PROMPT_EVENT_PARSERS = { @@ -397,7 +718,7 @@ index a1f4a70..57117c9 100644 function normalizeUsageCost(value) { if (!isRecord(value)) return; const amount = asOptionalFiniteNumber(value.amount); -@@ -812,7 +840,17 @@ var AcpRuntimeManager = class { +@@ -812,7 +840,21 @@ var AcpRuntimeManager = class { this.deps = deps; } createClient(options) { @@ -410,13 +731,17 @@ index a1f4a70..57117c9 100644 + onAgentSpawn: this.options.onAgentSpawn, + onAgentStderr: this.options.onAgentStderr, + onAgentExit: this.options.onAgentExit, -+ onAcpMessage: this.options.onAcpMessage ++ onAcpMessage: this.options.onAcpMessage, ++ clientCapabilities: this.options.clientCapabilities, ++ extensionMethods: this.options.extensionMethods, ++ onExtensionRequest: this.options.onExtensionRequest, ++ onExtensionNotification: this.options.onExtensionNotification + }; + return this.deps.clientFactory?.(patchedOptions) ?? new AcpClient(patchedOptions); } createSessionOwner(input) { const owner = { -@@ -821,12 +859,16 @@ var AcpRuntimeManager = class { +@@ -821,12 +863,16 @@ var AcpRuntimeManager = class { pendingSessionUpdates: [] }; input.client.setEventHandlers({ @@ -433,7 +758,7 @@ index a1f4a70..57117c9 100644 const active = owner.activeTurn; if (active) { const { task, turn } = active; -@@ -863,6 +905,9 @@ var AcpRuntimeManager = class { +@@ -863,6 +909,9 @@ var AcpRuntimeManager = class { projection.checkpoint.request(); } routeOwnedClientOperation(owner, operation) { @@ -443,7 +768,7 @@ index a1f4a70..57117c9 100644 const active = owner.activeTurn; if (!active) return; const { task, turn } = active; -@@ -1008,6 +1053,8 @@ var AcpRuntimeManager = class { +@@ -1008,6 +1057,8 @@ var AcpRuntimeManager = class { record.closedAt = void 0; record.protocolVersion = owner.client.initializeResult?.protocolVersion; record.agentCapabilities = owner.client.initializeResult?.agentCapabilities; @@ -452,7 +777,7 @@ index a1f4a70..57117c9 100644 applyLifecycleSnapshotToRecord(record, owner.client.getAgentLifecycleSnapshot()); } async finishBufferedOwnerControl(owner, record) { -@@ -1086,6 +1133,11 @@ var AcpRuntimeManager = class { +@@ -1086,6 +1137,11 @@ var AcpRuntimeManager = class { record.closed = false; record.closedAt = void 0; this.closingActiveRecords.delete(record.acpxRecordId); @@ -464,7 +789,7 @@ index a1f4a70..57117c9 100644 await this.options.sessionStore.save(record); return record; } -@@ -1149,6 +1201,8 @@ var AcpRuntimeManager = class { +@@ -1149,6 +1205,8 @@ var AcpRuntimeManager = class { this.closingActiveRecords.delete(record.acpxRecordId); record.protocolVersion = client.initializeResult?.protocolVersion; record.agentCapabilities = client.initializeResult?.agentCapabilities; @@ -473,7 +798,7 @@ index a1f4a70..57117c9 100644 applyConfigOptionsToRecord(record, session.sessionResult); const modelApplication = await applyRequestedModelIfAdvertised({ client, -@@ -1301,6 +1355,7 @@ var AcpRuntimeManager = class { +@@ -1301,6 +1359,7 @@ var AcpRuntimeManager = class { client: turn.client, sessionId, prompt: task.promptInput, @@ -481,7 +806,7 @@ index a1f4a70..57117c9 100644 timeoutMs: task.input.timeoutMs ?? this.options.timeoutMs, conversation: turn.conversation, promptMessageId: turn.promptMessageId, -@@ -1469,6 +1524,10 @@ var AcpRuntimeManager = class { +@@ -1469,6 +1528,10 @@ var AcpRuntimeManager = class { setSessionConfigOption: async (configId, value) => { return (await task.state.activeController.setResolvedSessionConfigOption(configId, value)).response; }, @@ -492,7 +817,7 @@ index a1f4a70..57117c9 100644 setResolvedSessionConfigOption: async (configId, value) => await this.setRuntimeResolvedSessionConfigOption(task, turn, configId, value) }; } -@@ -1575,6 +1634,8 @@ var AcpRuntimeManager = class { +@@ -1575,6 +1638,8 @@ var AcpRuntimeManager = class { reconcileAgentSessionId(turn.record, turn.record.agentSessionId); turn.record.protocolVersion = turn.client.initializeResult?.protocolVersion; turn.record.agentCapabilities = turn.client.initializeResult?.agentCapabilities; @@ -501,7 +826,7 @@ index a1f4a70..57117c9 100644 turn.record.acpx = turn.acpxState; applyConversation(turn.record, turn.conversation); applyLifecycleSnapshotToRecord(turn.record, turn.client.getAgentLifecycleSnapshot()); -@@ -1707,6 +1768,17 @@ var AcpRuntimeManager = class { +@@ -1707,6 +1772,17 @@ var AcpRuntimeManager = class { }); await this.options.sessionStore.save(result.record); } @@ -519,7 +844,7 @@ index a1f4a70..57117c9 100644 async cancel(handle) { await this.activeControllers.get(handle.acpxRecordId ?? handle.sessionKey)?.requestCancelActivePrompt(); } -@@ -2055,6 +2127,7 @@ var AcpxRuntime = class { +@@ -2055,6 +2131,7 @@ var AcpxRuntime = class { const turnPromise = this.getManager().then((manager) => manager.startTurn({ handle, text: input.text, @@ -527,7 +852,7 @@ index a1f4a70..57117c9 100644 attachments: input.attachments, mode: input.mode, sessionMode: state.mode, -@@ -2087,6 +2160,7 @@ var AcpxRuntime = class { +@@ -2087,6 +2164,7 @@ var AcpxRuntime = class { yield* (await this.getManager()).runTurn({ handle, text: input.text, @@ -535,7 +860,7 @@ index a1f4a70..57117c9 100644 attachments: input.attachments, mode: input.mode, sessionMode: state.mode, -@@ -2119,6 +2193,14 @@ var AcpxRuntime = class { +@@ -2119,6 +2197,14 @@ var AcpxRuntime = class { const { handle, state } = this.resolveManagerHandle(input.handle); await (await this.getManager()).setConfigOption(handle, input.key, input.value, state.mode); } @@ -550,8 +875,15 @@ index a1f4a70..57117c9 100644 async cancel(input) { const { handle } = this.resolveManagerHandle(input.handle); await (await this.getManager()).cancel(handle); +@@ -2178,4 +2264,4 @@ function createRuntimeStore(options) { + //#endregion + export { ACPX_BACKEND_ID, AcpRuntimeError, AcpxRuntime, DEFAULT_AGENT_NAME, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE, REQUESTED_MODEL_UNSUPPORTED_REASONS, RequestedModelUnsupportedError, createAcpRuntime, createAgentRegistry, createFileSessionStore, createRuntimeStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState, isAcpRuntimeError, isRequestedModelUnsupportedError }; + +-//# sourceMappingURL=runtime.js.map +\ No newline at end of file ++//# sourceMappingURL=runtime.js.map diff --git a/dist/session-options-DwRDODlr.d.ts b/dist/session-options-DwRDODlr.d.ts -index c3da164..ab6c1bf 100644 +index c3da164..5ccc8dd 100644 --- a/dist/session-options-DwRDODlr.d.ts +++ b/dist/session-options-DwRDODlr.d.ts @@ -1,4 +1,5 @@ @@ -560,10 +892,30 @@ index c3da164..ab6c1bf 100644 //#region src/prompt-content.d.ts type PromptInput = ContentBlock[]; //#endregion -@@ -116,6 +117,24 @@ type AcpClientOptions = { +@@ -27,6 +28,8 @@ type AcpElicitationContext = { + requestId: JsonRpcId; + /** Aborts with the request itself or its owning prompt turn/session. */ + signal: AbortSignal; ++ /** Await only after returning the selected response from this callback. */ ++ responseDelivery?: Promise; + }; + type AcpElicitationResponseMeta = { + _meta?: Record | null; +@@ -116,6 +119,35 @@ type AcpClientOptions = { }; env?: Record; }; ++ /** Ephemeral initialize capabilities; mandatory base capabilities take precedence. */ ++ clientCapabilities?: Record; ++ /** Closed list of inbound extension request and notification method names. */ ++ extensionMethods?: readonly string[]; ++ /** Bound to the current connection and active prompt; never persisted. */ ++ onExtensionRequest?: (method: string, params: Record, context: { ++ requestId: string | number; ++ signal: AbortSignal; ++ responseDelivery?: Promise; ++ }) => Promise>; ++ onExtensionNotification?: (method: string, params: Record) => void; + /** Ephemeral child environment factory; its return value is never persisted. */ + spawnEnvironment?: () => Record; + /** Host-only child cwd, separate from the cwd advertised to ACP. */ @@ -585,7 +937,16 @@ index c3da164..ab6c1bf 100644 onAcpMessage?: (direction: AcpMessageDirection, message: AcpJsonRpcMessage) => void; onAcpOutputMessage?: (direction: AcpMessageDirection, message: AcpJsonRpcMessage) => void; onSessionUpdate?: (notification: SessionNotification) => void; -@@ -263,6 +282,7 @@ type SessionRecord = { +@@ -123,6 +155,8 @@ type AcpClientOptions = { + onPermissionEscalation?: (event: PermissionEscalationEvent) => void; + onPermissionRequest?: (req: AcpPermissionRequest, ctx: { + signal: AbortSignal; ++ /** Resolves after this exact response is written to the current provider pipe. */ ++ responseDelivery?: Promise; + }) => Promise; + }; + declare const SESSION_RECORD_SCHEMA: "acpx.session.v1"; +@@ -263,12 +297,15 @@ type SessionRecord = { lastAgentDisconnectReason?: string; protocolVersion?: number; agentCapabilities?: AgentCapabilities; @@ -593,7 +954,16 @@ index c3da164..ab6c1bf 100644 title?: string | null; messages: SessionMessage[]; updated_at: string; -@@ -295,4 +315,4 @@ type SessionAgentOptions = { + cumulative_token_usage: SessionTokenUsage; + cumulative_cost?: SessionUsageCost; +- request_token_usage: Record; ++ request_token_usage: Record; + acpx?: SessionAcpxState; + importedFrom?: SessionImportedFrom; + }; +@@ -295,4 +332,4 @@ type SessionAgentOptions = { }; //#endregion export { SessionRecord as _, AcpElicitationHandler as a, AcpElicitationResponse as c, AuthPolicy as d, McpServer$1 as f, PermissionStats as g, PermissionPolicy as h, AcpElicitationContext as i, AcpPermissionDecision as l, PermissionMode as m, SystemPromptOption as n, AcpElicitationMode as o, NonInteractivePermissionPolicy as p, AcpClientOptions as r, AcpElicitationRequest as s, SessionAgentOptions as t, AcpPermissionRequest as u, PromptInput as v }; diff --git a/scripts/check-node-version-policy.mjs b/scripts/check-node-version-policy.mjs index cceb9c7519..41f1dcce00 100644 --- a/scripts/check-node-version-policy.mjs +++ b/scripts/check-node-version-policy.mjs @@ -1,6 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { isNode24ImageTag } from "./node-image-policy.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const expectedEngine = ">=24.11.0"; @@ -48,7 +49,7 @@ walk(repoRoot, (filePath) => { if (!path.basename(filePath).startsWith("Dockerfile")) return; const source = fs.readFileSync(filePath, "utf8"); for (const match of source.matchAll(/^\s*FROM\s+node:([^\s]+)/gm)) { - if (!match[1].startsWith("24-")) failures.push(`${relative(filePath)}: Node base image must use the Node 24 major, found node:${match[1]}`); + if (!isNode24ImageTag(match[1])) failures.push(`${relative(filePath)}: Node base image must use the Node 24 major, found node:${match[1]}`); } for (const match of source.matchAll(/^\s*ARG\s+NODE_(?:MAJOR|VERSION)=([^\s]+)/gm)) { if (match[1] !== "24") failures.push(`${relative(filePath)}: Node build argument must be 24, found ${match[1]}`); diff --git a/scripts/node-image-policy.mjs b/scripts/node-image-policy.mjs new file mode 100644 index 0000000000..3936b25838 --- /dev/null +++ b/scripts/node-image-policy.mjs @@ -0,0 +1,7 @@ +// Accept the existing major tags and exact patch pins, with a required image variant. +// A digest, when present, must be a complete SHA-256 pin. +export function isNode24ImageTag(tag) { + const match = /^24(?:\.(0|[1-9]\d*)\.(?:0|[1-9]\d*))?-[a-z0-9][a-z0-9._-]*(?:@sha256:[a-f0-9]{64})?$/.exec(tag); + // Floating major tags follow Node 24; exact pins must meet engines.node >=24.11.0. + return match !== null && (match[1] === undefined || BigInt(match[1]) >= 11n); +} diff --git a/scripts/standard-image-contract.test.mjs b/scripts/standard-image-contract.test.mjs index c903e95664..af2d446297 100644 --- a/scripts/standard-image-contract.test.mjs +++ b/scripts/standard-image-contract.test.mjs @@ -2,6 +2,24 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import test from "node:test"; import { standardImageDigest, resolveStandardImageDigest } from "./standard-image-contract.mjs"; +import { isNode24ImageTag } from "./node-image-policy.mjs"; + +test("Node image policy accepts Node 24 major tags and exact security pins", () => { + for (const tag of ["24-trixie-slim", "24-bookworm-slim", "24-alpine3.23", "24.11.0-bookworm", "24.21.0-bookworm", + `24.21.0-bookworm@sha256:${"a".repeat(64)}`, `24-bookworm@sha256:${"0".repeat(64)}`]) { + assert.equal(isNode24ImageTag(tag), true, tag); + } +}); + +test("Node image policy rejects other majors, partial versions and malformed pins", () => { + for (const tag of ["240-bookworm", "25.21.0-bookworm", "v24.21.0-bookworm", "latest", + "24.0.0-bookworm", "24.10.0-bookworm", "24.10.99-bookworm", + "24.21-bookworm", "24.21.0", "24.21.0-", "24..21.0-bookworm", "24.021.0-bookworm", + "24.21.0-bookworm@sha256:short", `24.21.0-bookworm@@sha256:${"a".repeat(64)}`, + `24.21.0-bookworm@sha256:${"a".repeat(64)}junk`, "${NODE_VERSION}-bookworm", "24-$(whoami)"]) { + assert.equal(isNode24ImageTag(tag), false, tag); + } +}); const descriptor = architecture => ({ platform: { os: "linux", architecture }, digest: `sha256:${"a".repeat(64)}`, size: 100 }); const index = manifests => Buffer.from(JSON.stringify({ schemaVersion: 2, mediaType: "application/vnd.oci.image.index.v1+json", manifests })); test("signs the exact registry bytes, including both supported platforms", () => { diff --git a/server/src/__tests__/adapter-registry.test.ts b/server/src/__tests__/adapter-registry.test.ts index 805a959ac8..48539de9b4 100644 --- a/server/src/__tests__/adapter-registry.test.ts +++ b/server/src/__tests__/adapter-registry.test.ts @@ -334,6 +334,21 @@ describe("server adapter registry", () => { checks: [{ code: "paperclip_runner_acpx_agent_unavailable" }], }); }); + it("reports qualification-only readiness for an exact host-authorized candidate", async () => { + const key = "PAPERCLIP_RUNNER_ACPX_QUALIFICATION"; + const previous = process.env[key]; + process.env[key] = JSON.stringify([{ agent: "cursor", model: "exact-model" }]); + try { + const result = await requireServerAdapter("paperclip_runner").testEnvironment({ + companyId: "company-1", adapterType: "paperclip_runner", + config: { provider: "acpx", acpxAgent: "cursor", model: "exact-model" }, + }); + expect(result).toMatchObject({ status: "warn", checks: [{ code: "acpx_candidate_qualification_only" }] }); + } finally { + if (previous === undefined) delete process.env[key]; + else process.env[key] = previous; + } + }); it("wraps built-in npm runtime installs with the sandbox-aware install helper", () => { const expectedClaudeInstall = `if ! command -v 'claude' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@anthropic-ai/claude-code")}; fi`; const expectedCodexInstall = `if ! command -v 'codex' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@openai/codex")}; fi`; diff --git a/server/src/__tests__/heartbeat-cost-accounting.test.ts b/server/src/__tests__/heartbeat-cost-accounting.test.ts index 182b8acbb0..32ce3a1328 100644 --- a/server/src/__tests__/heartbeat-cost-accounting.test.ts +++ b/server/src/__tests__/heartbeat-cost-accounting.test.ts @@ -5,6 +5,27 @@ import { } from "../services/heartbeat.js"; describe("heartbeat cost accounting", () => { + it.each([null, undefined, Number.NaN, Number.POSITIVE_INFINITY, -1])( + "keeps a paused run without a valid cost receipt unpriced (%s)", + (costUsd) => { + expect(resolveLedgerCostStatus({ + costUsd, + inputTokens: 0, + cachedInputTokens: 0, + outputTokens: 0, + })).toBe("unpriced"); + }, + ); + + it("preserves an explicitly reported zero-dollar receipt without token usage", () => { + expect(resolveLedgerCostStatus({ + costUsd: 0, + inputTokens: 0, + cachedInputTokens: 0, + outputTokens: 0, + })).toBe("reported"); + }); + it("marks token-bearing CLI usage without a reported cost as unpriced", () => { expect(resolveLedgerCostStatus({ costUsd: null, diff --git a/server/src/__tests__/issue-feedback-routes.test.ts b/server/src/__tests__/issue-feedback-routes.test.ts index 31e625e6fe..e59fe6d1e9 100644 --- a/server/src/__tests__/issue-feedback-routes.test.ts +++ b/server/src/__tests__/issue-feedback-routes.test.ts @@ -72,61 +72,60 @@ const mockIssueReferenceService = vi.hoisted(() => ({ syncIssue: vi.fn(async () => undefined), })); -function registerModuleMocks() { - vi.doMock("@paperclipai/shared/telemetry", () => ({ - trackAgentTaskCompleted: vi.fn(), - trackErrorHandlerCrash: vi.fn(), - })); +// Keep service factories stable while each request uses the hoisted test doubles. +vi.mock("@paperclipai/shared/telemetry", () => ({ + trackAgentTaskCompleted: vi.fn(), + trackErrorHandlerCrash: vi.fn(), +})); - vi.doMock("../telemetry.js", () => ({ - getTelemetryClient: vi.fn(() => ({ track: vi.fn() })), - })); +vi.mock("../telemetry.js", () => ({ + getTelemetryClient: vi.fn(() => ({ track: vi.fn() })), +})); - vi.doMock("../services/index.js", () => ({ - companyService: () => ({ - getById: vi.fn(async () => ({ id: "company-1" })), - }), - accessService: () => mockAccessService, - agentService: () => mockAgentService, - companySkillService: () => ({ - completeTestRunForIssue: vi.fn(async () => null), - }), - documentAnnotationService: () => ({ remapOpenThreadsForDocument: async () => [] }), - documentService: () => ({}), - executionWorkspaceService: () => mockExecutionWorkspaceService, - goalService: () => ({}), - heartbeatService: () => mockHeartbeatService, - issueApprovalService: () => ({}), - issueReferenceService: () => mockIssueReferenceService, - issueRecoveryActionService: () => ({ - getActiveForIssue: vi.fn(async () => null), - listActiveForIssues: vi.fn(async () => new Map()), - }), - issueService: () => mockIssueService, - issueThreadInteractionService: () => mockIssueThreadInteractionService, - logActivity: mockLogActivity, - projectService: () => ({}), - routineService: () => mockRoutineService, - workProductService: () => ({}), - })); +vi.mock("../services/index.js", () => ({ + companyService: () => ({ + getById: vi.fn(async () => ({ id: "company-1" })), + }), + accessService: () => mockAccessService, + agentService: () => mockAgentService, + companySkillService: () => ({ + completeTestRunForIssue: vi.fn(async () => null), + }), + documentAnnotationService: () => ({ remapOpenThreadsForDocument: async () => [] }), + documentService: () => ({}), + executionWorkspaceService: () => mockExecutionWorkspaceService, + goalService: () => ({}), + heartbeatService: () => mockHeartbeatService, + issueApprovalService: () => ({}), + issueReferenceService: () => mockIssueReferenceService, + issueRecoveryActionService: () => ({ + getActiveForIssue: vi.fn(async () => null), + listActiveForIssues: vi.fn(async () => new Map()), + }), + issueService: () => mockIssueService, + issueThreadInteractionService: () => mockIssueThreadInteractionService, + logActivity: mockLogActivity, + projectService: () => ({}), + routineService: () => mockRoutineService, + workProductService: () => ({}), +})); - vi.doMock("../services/environments.js", () => ({ - environmentService: () => mockEnvironmentService, - })); +vi.mock("../services/environments.js", () => ({ + environmentService: () => mockEnvironmentService, +})); - vi.doMock("../services/execution-workspaces.js", () => ({ - executionWorkspaceService: () => mockExecutionWorkspaceService, - STALE_REOPEN_PENDING_CONSUMPTION_GRACE_MS: 5 * 60 * 1000, - })); +vi.mock("../services/execution-workspaces.js", () => ({ + executionWorkspaceService: () => mockExecutionWorkspaceService, + STALE_REOPEN_PENDING_CONSUMPTION_GRACE_MS: 5 * 60 * 1000, +})); - vi.doMock("../services/feedback.js", () => ({ - feedbackService: () => mockFeedbackService, - })); +vi.mock("../services/feedback.js", () => ({ + feedbackService: () => mockFeedbackService, +})); - vi.doMock("../services/instance-settings.js", () => ({ - instanceSettingsService: () => mockInstanceSettingsService, - })); -} +vi.mock("../services/instance-settings.js", () => ({ + instanceSettingsService: () => mockInstanceSettingsService, +})); async function createApp(actor: Record) { const [{ issueRoutes }, { errorHandler }] = await Promise.all([ @@ -152,17 +151,6 @@ async function createApp(actor: Record) { describe("issue feedback trace routes", () => { beforeEach(() => { - vi.resetModules(); - vi.doUnmock("@paperclipai/shared/telemetry"); - vi.doUnmock("../telemetry.js"); - vi.doUnmock("../services/index.js"); - vi.doUnmock("../services/environments.js"); - vi.doUnmock("../services/execution-workspaces.js"); - vi.doUnmock("../services/feedback.js"); - vi.doUnmock("../services/instance-settings.js"); - vi.doUnmock("../routes/issues.js"); - vi.doUnmock("../middleware/index.js"); - registerModuleMocks(); vi.clearAllMocks(); mockFeedbackExportService.flushPendingFeedbackTraces.mockResolvedValue({ attempted: 1, @@ -260,6 +248,8 @@ describe("issue feedback trace routes", () => { const res = await request(app).get("/api/feedback-traces/trace-1"); expect(res.status, JSON.stringify({ body: res.body, errors: app.locals.routeErrors })).toBe(404); + expect(mockFeedbackService.getFeedbackTraceById).toHaveBeenCalledExactlyOnceWith("trace-1", true); + expect(mockFeedbackService.getFeedbackTraceBundle).not.toHaveBeenCalled(); }); it("returns 404 for bundle fetches when a board user lacks access to the trace company", async () => { @@ -280,5 +270,7 @@ describe("issue feedback trace routes", () => { const res = await request(app).get("/api/feedback-traces/trace-1/bundle"); expect(res.status, JSON.stringify({ body: res.body, errors: app.locals.routeErrors })).toBe(404); + expect(mockFeedbackService.getFeedbackTraceBundle).toHaveBeenCalledExactlyOnceWith("trace-1"); + expect(mockFeedbackService.getFeedbackTraceById).not.toHaveBeenCalled(); }); }); diff --git a/server/src/adapters/registry.ts b/server/src/adapters/registry.ts index afa38c1ad0..62cef753d7 100644 --- a/server/src/adapters/registry.ts +++ b/server/src/adapters/registry.ts @@ -404,6 +404,15 @@ const paperclipRunnerAdapter: ServerAdapterModule = { }; } if (profile.provider === "acpx") { + if (["cursor", "copilot", "pi"].includes(profile.acpxAgent)) { + // The profile resolver already validated the isolated host's exact + // qualification pair. Do not report a production readiness pass. + return { + adapterType: "paperclip_runner", status: "warn" as const, testedAt: new Date().toISOString(), + checks: [{ code: "acpx_candidate_qualification_only", level: "warn" as const, + message: "This exact candidate and model are admitted for operator-controlled qualification only. Verified runtime installation, bound credentials, and model access are checked before execution; production support remains pending." }], + }; + } try { if (profile.acpxAgent !== "claude" && profile.acpxAgent !== "grok") throw new Error("Select Codex to use the native Codex runner."); const target = context.executionTarget; @@ -510,7 +519,7 @@ const paperclipRunnerAdapter: ServerAdapterModule = { ) : buildNpmRuntimeCommandSpec(config, "codex", "@openai/codex@0.156.0"), agentConfigurationDoc: - "# Paperclip Runner\n\nAdapter: paperclip_runner\n\nRuns Codex, OpenCode, Claude Managed, AWS AgentCore, or ACPX Claude/Grok Build through the Rust Paperclip runner and authenticated PRP transport. Pi is not available through the qualified ACPX profile. Managed providers use company-scoped qualified profiles, explicit retention acknowledgement, and spend limits.\n", + "# Paperclip Runner\n\nAdapter: paperclip_runner\n\nRuns Codex, OpenCode, Claude Managed, AWS AgentCore, or ACPX Claude/Grok Build through the Rust Paperclip runner and authenticated PRP transport. Cursor, GitHub Copilot, and Pi are awaiting local and Daytona qualification and are not enabled for production runs. Managed providers use company-scoped qualified profiles, explicit retention acknowledgement, and spend limits.\n", getConfigSchema: () => ({ fields: [ { @@ -523,9 +532,9 @@ const paperclipRunnerAdapter: ServerAdapterModule = { { value: "opencode", label: `OpenCode ${QUALIFIED_OPENCODE_RUNNER_VERSION}` }, { value: "claude_managed", label: "Claude Managed" }, { value: "aws_agentcore", label: "AWS AgentCore" }, - { value: "acpx", label: "ACPX (Claude / Grok Build)" }, + { value: "acpx", label: "ACP agents" }, ], - hint: "Select a local provider, company-qualified managed provider, or Claude or Grok Build through ACPX.", + hint: "Select a local provider, company-qualified managed provider, or an ACP agent.", }, { key: "acpxAgent", label: "ACP agent", type: "select" as const, default: "claude", diff --git a/server/src/services/heartbeat-runner-provider-config.test.ts b/server/src/services/heartbeat-runner-provider-config.test.ts index 638fac7bfb..2b589af21a 100644 --- a/server/src/services/heartbeat-runner-provider-config.test.ts +++ b/server/src/services/heartbeat-runner-provider-config.test.ts @@ -391,6 +391,6 @@ describe("Paperclip Runner native provider configuration", () => { backend: "acpx_runtime", adapterConfig: { provider: "acpx", acpxAgent: "pi", model: "pi-model" }, }), - ).toThrow("Pi is not available"); + ).toThrow("Pi is awaiting local and Daytona qualification"); }); }); diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 60372fa015..981ac9713a 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -5250,11 +5250,13 @@ export function resolveLedgerCostStatus(input: { cachedInputTokens: number; outputTokens: number; }): CostStatus { - const hasTokenUsage = - input.inputTokens > 0 || - input.cachedInputTokens > 0 || - input.outputTokens > 0; - return input.costUsd == null && hasTokenUsage ? "unpriced" : "reported"; + // A paused turn can have neither a token receipt nor a cost receipt. Zero + // normalized counters do not establish that its billed cost was zero. + return typeof input.costUsd === "number" && + Number.isFinite(input.costUsd) && + input.costUsd >= 0 + ? "reported" + : "unpriced"; } export function resolveCacheAdjustedCostUsd(input: { diff --git a/server/src/services/native-runtime/acpx-qualification.test.ts b/server/src/services/native-runtime/acpx-qualification.test.ts new file mode 100644 index 0000000000..e8be873d29 --- /dev/null +++ b/server/src/services/native-runtime/acpx-qualification.test.ts @@ -0,0 +1,43 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { readFileSync } from "node:fs"; +import { ACPX_QUALIFICATION_ENV, resolveAcpxQualification } from "./acpx-qualification.js"; +import type { NativeExecutionInput } from "../../vendor/paperclip-runner/index.js"; +import { resolvePaperclipRunnerProviderProfile, resolvePaperclipRunnerNativeProviderInput } from "./provider-profile.js"; + +const provider = { kind: "acpx", agent: "cursor", model: "exact-model", permissionMode: "approve-all" } as NativeExecutionInput["provider"]; +const authorize = (value: unknown) => ({ [ACPX_QUALIFICATION_ENV]: JSON.stringify(value) }); +describe("host ACPX qualification admission", () => { + afterEach(() => vi.unstubAllEnvs()); + it.each(["cursor", "copilot", "pi"])("admits %s through agent validation and native input only for the exact host pair", (agent) => { + const config = { provider: "acpx", acpxAgent: agent, model: "exact-model" }; + vi.stubEnv(ACPX_QUALIFICATION_ENV, undefined); + expect(() => resolvePaperclipRunnerProviderProfile(config)).toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_agent_unavailable" })); + expect(() => resolvePaperclipRunnerProviderProfile({ ...config, env: authorize([{ agent, model: config.model }]), [ACPX_QUALIFICATION_ENV]: JSON.stringify([{ agent, model: config.model }]) })).toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_agent_unavailable" })); + vi.stubEnv(ACPX_QUALIFICATION_ENV, JSON.stringify([{ agent, model: config.model }])); + expect(resolvePaperclipRunnerProviderProfile(config)).toMatchObject({ acpxAgent: agent, model: config.model }); + expect(resolvePaperclipRunnerNativeProviderInput({ backend: "acpx_runtime", adapterConfig: config })).toMatchObject({ acpxAgent: agent, model: config.model }); + expect(() => resolvePaperclipRunnerProviderProfile({ ...config, model: "other-model" })).toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_qualification_invalid" })); + expect(() => resolvePaperclipRunnerProviderProfile({ ...config, model: "" })).toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_model_required" })); + }); + it("keeps normal candidate execution closed and admits only the exact operator pair", () => { + expect(resolveAcpxQualification(provider, {})).toBeUndefined(); + expect(resolveAcpxQualification(provider, authorize([{ agent: "cursor", model: "exact-model" }]))).toBe("cursor"); + for (const entries of [[{ agent: "copilot", model: "exact-model" }], [{ agent: "cursor", model: "other-model" }]]) { + expect(() => resolveAcpxQualification(provider, authorize(entries))).toThrow("exact model"); + } + }); + it.each([[], {}, [{ agent: "cursor", model: "" }], [{ agent: "cursor", model: " exact-model" }], + [{ agent: "cursor", model: "exact-model", allowAll: true }], [{ agent: "claude", model: "exact-model" }], + [{ agent: "cursor", model: "exact-model" }, { agent: "cursor", model: "exact-model" }], + ])("rejects malformed or broad authorization %j", entries => { + expect(() => resolveAcpxQualification(provider, authorize(entries))).toThrow("Invalid ACPX"); + }); + it("does not alter existing qualified providers", () => { + expect(resolveAcpxQualification({ ...provider, agent: "codex" } as typeof provider, authorize([]))).toBeUndefined(); + }); + it("binds the executor to host process environment rather than agent configuration", () => { + const source = readFileSync(new URL("./native-session-executor.ts", import.meta.url), "utf8"); + expect(source).toContain("resolveAcpxQualification(input.execution.provider, process.env)"); + expect(source).not.toContain("resolveAcpxQualification(input.execution.provider, effectiveRunnerEnvironment)"); + }); +}); diff --git a/server/src/services/native-runtime/acpx-qualification.ts b/server/src/services/native-runtime/acpx-qualification.ts new file mode 100644 index 0000000000..76eb3cdd36 --- /dev/null +++ b/server/src/services/native-runtime/acpx-qualification.ts @@ -0,0 +1,32 @@ +import type { NativeExecutionInput } from "../../vendor/paperclip-runner/index.js"; + +/** Operator-only admission for an exact candidate/model during qualification. */ +export const ACPX_QUALIFICATION_ENV = "PAPERCLIP_RUNNER_ACPX_QUALIFICATION"; +export type AcpxQualificationCandidate = "cursor" | "copilot" | "pi"; + +export function resolveAcpxQualification( + provider: NativeExecutionInput["provider"] | { kind: "acpx"; agent: string; model: string }, + hostEnvironment: NodeJS.ProcessEnv, +): AcpxQualificationCandidate | undefined { + if (provider.kind !== "acpx" || !["cursor", "copilot", "pi"].includes(provider.agent)) return undefined; + const encoded = hostEnvironment[ACPX_QUALIFICATION_ENV]; + if (!encoded) return undefined; + let entries: unknown; + try { entries = JSON.parse(encoded); } catch { throw new Error("Invalid ACPX qualification authorization"); } + if (!Array.isArray(entries) || entries.length < 1 || entries.length > 3) { + throw new Error("Invalid ACPX qualification authorization"); + } + const agents = new Set(); + for (const entry of entries) { + if (!entry || typeof entry !== "object" || Array.isArray(entry) + || Object.keys(entry).sort().join(",") !== "agent,model" + || !["cursor", "copilot", "pi"].includes(entry.agent) + || typeof entry.model !== "string" || !entry.model.trim() || entry.model !== entry.model.trim() + || agents.has(entry.agent)) throw new Error("Invalid ACPX qualification authorization"); + agents.add(entry.agent); + } + if (!entries.some(entry => entry.agent === provider.agent && entry.model === provider.model)) { + throw new Error("ACPX qualification requires the explicitly authorized candidate and exact model"); + } + return provider.agent as AcpxQualificationCandidate; +} diff --git a/server/src/services/native-runtime/native-permission-projection.test.ts b/server/src/services/native-runtime/native-permission-projection.test.ts new file mode 100644 index 0000000000..718a57ba0a --- /dev/null +++ b/server/src/services/native-runtime/native-permission-projection.test.ts @@ -0,0 +1,61 @@ +import type { Db } from "@paperclipai/db"; +import type { PrpEvent } from "../../vendor/paperclip-runner/index.js"; +import { describe, expect, it, vi } from "vitest"; +import { projectNativeRuntimeRequest } from "./native-question-bridge.js"; + +const binding = { + companyId: "company-1", issueId: "issue-1", runId: "run-1", agentId: "agent-1", + normalizedSessionId: "session-1", runnerSourceInstanceId: "runner-1", +}; + +function permissionEvent(): PrpEvent { + return { + schema: "paperclip.prp.event.v1", schemaVersion: 1, sourceEventId: "permission-1", + sourceSeq: 1, sourceKind: "runner", sourceInstanceId: "runner-1", runId: "run-1", + normalizedSessionId: "session-1", turnId: "turn-1", itemId: "item-1", + eventType: "runtime_request.created", priority: 0, emittedAt: "2026-09-28T12:00:00Z", + payload: { request: { + schema: "paperclip.runtime_request.v2", requestKind: "permission_approval", type: "permission", + requestId: "permission-1", turnId: "turn-1", itemId: "item-1", status: "pending", + prompt: "Allow editing src/example.ts?", + choices: [{ key: "accept", label: "Allow once" }, { key: "decline", label: "Deny" }], + details: { toolCallId: "tool-1" }, + origin: { adapter: "acpx-runtime-sidecar", provider: "acpx", method: "session/request_permission" }, + } }, + }; +} + +describe("native permission projection", () => { + it("admits the existing privileged runtime card without a human-only question mutation", async () => { + const event = permissionEvent(); + const original = structuredClone(event); + const select = vi.fn(); + await expect(projectNativeRuntimeRequest({ db: { select } as unknown as Db, binding, event })).resolves.toBeNull(); + expect(select).not.toHaveBeenCalled(); + expect(event).toEqual(original); + }); + + it("retains permissions with no provider item identity", async () => { + const event = permissionEvent(); + delete event.itemId; + (event.payload.request as Record).itemId = null; + await expect(projectNativeRuntimeRequest({ db: {} as Db, binding, event })).resolves.toBeNull(); + }); + + it.each([ + { requestKind: "runtime" }, { type: "input" }, { turnId: "stale-turn" }, { itemId: "wrong-item" }, + { requestId: "../../forged" }, { prompt: "" }, { details: [] }, + { choices: [] }, { choices: [{ key: "allow_forever", label: "Always" }] }, + { choices: [{ key: "accept", label: "One" }, { key: "accept", label: "Two" }] }, + ])("rejects malformed or cross-bound permission data %j", async (override) => { + const event = permissionEvent(); + Object.assign(event.payload.request as object, override); + await expect(projectNativeRuntimeRequest({ db: {} as Db, binding, event })).rejects.toThrow(/native_runtime_/); + }); + + it.each(["runId", "normalizedSessionId", "sourceInstanceId"] as const)("rejects a foreign %s before dispatch", async (field) => { + const event = permissionEvent(); + event[field] = "foreign"; + await expect(projectNativeRuntimeRequest({ db: {} as Db, binding, event })).rejects.toThrow("native_runtime_request_binding_mismatch"); + }); +}); diff --git a/server/src/services/native-runtime/native-question-bridge.test.ts b/server/src/services/native-runtime/native-question-bridge.test.ts index 75b9ba4abf..3943cc1fbe 100644 --- a/server/src/services/native-runtime/native-question-bridge.test.ts +++ b/server/src/services/native-runtime/native-question-bridge.test.ts @@ -1,5 +1,9 @@ import { createLocalNativeQuestionBridge } from "./local-native-question-bridge.js"; import { randomUUID } from "node:crypto"; +import { mkdtempSync, rmSync } from "node:fs"; +import { createServer } from "node:http"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { eq, sql } from "drizzle-orm"; @@ -39,6 +43,14 @@ import { type IssuePostCommitAction, } from "../issues.js"; import { heartbeatService } from "../heartbeat.js"; +import { DurablePrpControlPlane } from "../../vendor/paperclip-runner/index.js"; +import { PaperclipControlPlanePort } from "./paperclip-control-plane-port.js"; +import { readPendingNativeRuntimeRequest } from "./runtime-request-resolution-authority.js"; +import { + queueRunnerPrpRuntimeRequestResolution, + registerRunnerPrpAuthority, + setupRunnerPrpWebSocketServer, +} from "../../realtime/runner-prp-ws.js"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; @@ -201,6 +213,91 @@ describeEmbeddedPostgres("native question bridge", () => { }; } + function permissionRequestEvent(): PrpEvent { + return { ...runtimeRequestEvent(), payload: { request: { + schema: "paperclip.runtime_request.v2", requestKind: "permission_approval", + requestId: "permission-1", turnId: "turn-1", itemId: "item-1", + type: "permission", status: "pending", prompt: "Allow editing src/example.ts?", + choices: [{ key: "accept", label: "Allow once" }, { key: "decline", label: "Deny" }], + details: { toolCallId: "tool-1" }, + origin: { adapter: "acpx-runtime-sidecar", provider: "acpx", method: "session/request_permission" }, + } } }; + } + + it("commits and acknowledges an ACP permission, then queues only an admin's exact turn-bound decision", async () => { + await seed(); + const event = permissionRequestEvent(); + const projection = vi.fn(async (committed: PrpEvent) => { + await projectNativeRuntimeRequest({ db, binding: binding(), event: committed }); + }); + const port = new PaperclipControlPlanePort(db, { + companyId, issueId, runId, agentId, sessionId, + completionContractId: binding().completionContractId, + completionContractSha256: binding().completionContractSha256, + sourceInstanceId: runnerInstanceId, controlPlaneSourceInstanceId: "control-1", + }, { onCommittedEvent: projection }); + const receipt = await port.appendEvent(event); + expect(receipt).toMatchObject({ disposition: "committed", highestContiguousSourceSeq: 1 }); + expect(projection).toHaveBeenCalledOnce(); + expect(await db.select().from(issueThreadInteractions)).toHaveLength(0); + const [persisted] = await db.select().from(heartbeatRunEvents).where(eq(heartbeatRunEvents.runId, runId)); + expect(persisted?.payload).toEqual({ prpEvent: event }); // task-chat's exact source, with only offered decisions + expect(await port.appendEvent(event)).toMatchObject({ disposition: "duplicate" }); + const pending = await readPendingNativeRuntimeRequest(db, { companyId, runId, requestId: "permission-1" }); + expect(pending).toMatchObject({ requestKind: "permission_approval", turnId: "turn-1", resolverPolicy: "instance_admin" }); + expect(await readPendingNativeRuntimeRequest(db, { companyId: randomUUID(), runId, requestId: "permission-1" })).toBeNull(); + + const stateDirectory = mkdtempSync(join(tmpdir(), "native-permission-authority-")); + const server = createServer(); + setupRunnerPrpWebSocketServer(server, { apiUrl: "http://127.0.0.1:3213" }); + const authority = new DurablePrpControlPlane({ + stateDirectory, + identity: { runnerInstanceId, environmentLeaseId: "lease-1", runId, normalizedSessionId: sessionId, turnId: "turn-1", itemId: "item-1" }, + expectedRunnerVersion: "0.3.0", expectedRunnerDigest: `sha256:${"a".repeat(64)}`, + }); + const registration = await registerRunnerPrpAuthority({ companyId, runId, authority }); + try { + const commandInput = { companyId, runId, pendingRequest: pending!, resolution: { action: "accept" as const }, + actor: { type: "user" as const, userId: "admin-1", isInstanceAdmin: true } }; + expect(() => queueRunnerPrpRuntimeRequestResolution({ ...commandInput, actor: { ...commandInput.actor, isInstanceAdmin: false } })) + .toThrow("native_runtime_request_resolver_denied"); + expect(() => queueRunnerPrpRuntimeRequestResolution({ ...commandInput, companyId: randomUUID() })) + .toThrow("runner_prp_authority_not_active"); + const queued = queueRunnerPrpRuntimeRequestResolution(commandInput); + expect(queueRunnerPrpRuntimeRequestResolution(commandInput)).toEqual(queued); + expect(authority.store.state.commands).toHaveLength(1); + expect(authority.store.state.commands[0]).toMatchObject({ type: "request.resolve", payload: { + requestId: "permission-1", requestKind: "permission_approval", turnId: "turn-1", + resolution: { action: "accept" }, resolutionActor: commandInput.actor, + } }); + expect(() => queueRunnerPrpRuntimeRequestResolution({ ...commandInput, resolution: { action: "decline" } })) + .toThrow("runtime_request_resolution_conflict"); + // Queueing is not delivery: only the runner's post-write receipt closes + // the request. ACP sidecar delivery tests exercise that provider edge. + expect(await readPendingNativeRuntimeRequest(db, { companyId, runId, requestId: "permission-1" })).toEqual(pending); + await port.appendEvent({ ...event, sourceEventId: "permission-delivered", sourceSeq: 2, + eventType: "runtime_request.resolved", payload: { requestId: "permission-1", requestKind: "permission_approval", + turnId: "turn-1", itemId: "item-1", status: "delivered", action: "accept" } }); + expect(await readPendingNativeRuntimeRequest(db, { companyId, runId, requestId: "permission-1" })).toBeNull(); + } finally { + await registration.release(); + await authority.stop(); + server.close(); + rmSync(stateDirectory, { recursive: true, force: true }); + } + }); + + it.each([ + { requestKind: "runtime" }, { type: "input" }, { turnId: "stale-turn" }, { itemId: "wrong-item" }, + { choices: [] }, { choices: [{ key: "allow_forever", label: "Always" }] }, + { choices: [{ key: "accept", label: "One" }, { key: "accept", label: "Two" }] }, + ])("rejects a malformed permission projection %j", async (override) => { + await seed(); + const event = permissionRequestEvent(); + Object.assign(event.payload.request as object, override); + await expect(projectNativeRuntimeRequest({ db, binding: binding(), event })).rejects.toThrow(/native_runtime_/); + }); + it("projects an executor question immediately and routes its durable answer into the same live turn", async () => { await seed(); const event = runtimeRequestEvent(); diff --git a/server/src/services/native-runtime/native-question-bridge.ts b/server/src/services/native-runtime/native-question-bridge.ts index 4d77db841a..12286bf71c 100644 --- a/server/src/services/native-runtime/native-question-bridge.ts +++ b/server/src/services/native-runtime/native-question-bridge.ts @@ -200,7 +200,7 @@ async function authorizedNativeRun( return run ? { ...run, requestId } : null; } -/** Materialize a canonical runtime input request as the existing task-thread card. */ +/** Materialize questions; permission requests use the privileged runtime card. */ export async function projectNativeRuntimeRequest(input: { db: Db; binding: Pick; @@ -218,14 +218,43 @@ export async function projectNativeRuntimeRequest(input: { if ( !request || request.schema !== "paperclip.runtime_request.v2" - || request.requestKind !== "runtime" - || request.type !== "input" || request.status !== "pending" || typeof request.requestId !== "string" || !REQUEST_ID_PATTERN.test(request.requestId) ) { throw new Error("native_runtime_request_invalid"); } + if (request.requestKind === "permission_approval" && request.type === "permission") { + const choices = Array.isArray(request.choices) ? request.choices.map(record) : []; + const supported = new Set(["accept", "accept_for_session", "decline", "cancel"]); + if ( + typeof request.turnId !== "string" + || request.turnId !== input.event.turnId + || (request.itemId != null && typeof request.itemId !== "string") + || (request.itemId ?? null) !== (input.event.itemId ?? null) + || typeof request.prompt !== "string" + || !request.prompt.trim() + || request.prompt.length > 4000 + || choices.length === 0 + || choices.length > 4 + || choices.some((choice) => !choice + || typeof choice.key !== "string" || !supported.has(choice.key) + || typeof choice.label !== "string" || !choice.label.trim() || choice.label.length > 500) + || new Set(choices.map((choice) => choice?.key)).size !== choices.length + || (request.details !== undefined && !record(request.details)) + ) { + throw new Error("native_runtime_permission_invalid"); + } + // The committed run event itself feeds TaskChatProtocolCard. Its decisions + // go through the instance-admin runtime-request route and the exact pending + // turn, not the human-only question-response delivery path. Returning here + // allows the durable coordinator to acknowledge the event without creating + // a second, less-privileged interaction or changing any offered choices. + return null; + } + if (request.requestKind !== "runtime" || request.type !== "input") { + throw new Error("native_runtime_request_invalid"); + } const questionSet = parsePaperclipQuestionSet(request.input); if (questionSet.questions.some((question) => question.textValidation?.pattern !== undefined)) { // JavaScript regular expressions have no execution budget. Provider-authored diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index 30ae4895d8..ca537cb0fb 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -35,6 +35,7 @@ import { validatePrpEvent, parseNativeExecutionInput, type NativeExecutionInputV1, + type NativeExecutionInput, type PrpEvent, } from "@paperclipai/paperclip-runner"; import { createHash } from "node:crypto"; @@ -270,6 +271,7 @@ vi.mock("./native-codex-runner.js", () => ({ import { continuingPendingInteractionIds, buildNativeProviderEnvironment, + resolveNativeProviderEnvironment, buildNativeHarnessBackupManifest, cancelNativeSession, closeWarmNativeSessionsForEnvironment, @@ -299,6 +301,7 @@ import { buildRemoteCodexLauncherCommand, mayUsePreinstalledRunnerArtifact, nativeUsageCostUsd, + nativeUsageBiller, normalizeNativeUsage, parseRemoteRunnerProcessIdentity, REMOTE_RUNNER_CHILD_LAUNCH_SCRIPT, @@ -973,6 +976,13 @@ describe("native incomplete-bootstrap evidence", () => { }); describe("native provider usage normalization", () => { + it.each([["cursor", "cursor"], ["copilot", "github"], ["pi", "openrouter"]] as const)("keeps %s cost unknown and attributes its actual biller", (agent, biller) => { + const provider = { kind: "acpx", agent, model: "exact-model" } as NativeExecutionInput["provider"]; + expect(nativeUsageBiller(provider)).toBe(biller); + const usage = { runDelta: { inputTokens: 100, outputTokens: 12, providerCostUsd: 0 }, cumulative: { providerCostUsd: 0.44 } }; + expect(nativeUsageCostUsd(usage, provider)).toBeUndefined(); + expect(normalizeNativeUsage(usage)).toMatchObject({ inputTokens: 100, outputTokens: 12 }); + }); it("reads remote runner run-delta tokens and provider cost", () => { const usage = { total: { @@ -1138,6 +1148,26 @@ describe("remote provider pack manifest", () => { expect(readRemoteProviderPackManifest(root).payload.pins.opencode).toBe( "1.18.32", ); + const candidatePath = "provider-assets/pi/linux-x64"; + await mkdir(join(root, candidatePath), { recursive: true }); + await writeFile(join(root, candidatePath, "runtime"), "pinned runtime"); + const candidates = { pi: { version: "0.0.33", profileDigest: digest("profile"), + closureDigest: digest("closure"), qualification: "pending", path: candidatePath, + sha256: sha256DirectoryTree(join(root, candidatePath)) } }; + Object.assign(payload, { candidateProviders: candidates }); + await writeManifest(); + expect(readRemoteProviderPackManifest(root).payload.candidateProviders?.pi?.qualification).toBe("pending"); + await writeFile(join(root, candidatePath, "runtime"), "substitute runtime"); + expect(() => readRemoteProviderPackManifest(root)).toThrow("candidate asset tree digest mismatch"); + await writeFile(join(root, candidatePath, "runtime"), "pinned runtime"); + for (const invalid of [{ path: "../outside" }, { qualification: "qualified" }]) { + const original = { ...candidates.pi }; + Object.assign(candidates.pi, invalid); + await writeManifest(); + expect(() => readRemoteProviderPackManifest(root)).toThrow("invalid candidate identity"); + candidates.pi = original; + } + await writeManifest(); for (const [artifactName, substituteName] of [ ["nodeCommand", "productionLock"], ["opencodeExecutable", "opencodeCommand"], @@ -2492,6 +2522,23 @@ describe("runtime question fallback", () => { }); describe("native provider bootstrap environment", () => { + it.each(["pi", "cursor", "copilot"] as const)("does not promote ambient %s credentials when bindings are omitted", agent => { + const provider = { kind: "acpx", agent } as NativeExecutionInput["provider"]; + const host = { PATH: "/host/bin", HOME: "/host/home", OPENROUTER_API_KEY: "ambient-pi", + CURSOR_API_KEY: "ambient-cursor", CURSOR_AUTH_TOKEN: "ambient-cursor-login", COPILOT_GITHUB_TOKEN: "ambient-copilot", + PAPERCLIP_ACPX_CREDENTIAL_BINDING: "ambient-forged-binding" }; + expect(resolveNativeProviderEnvironment(provider, undefined, host)).toEqual({ PATH: "/host/bin", HOME: "/host/home" }); + const explicit = { COPILOT_GITHUB_TOKEN: "explicit-company-binding" }; + expect(resolveNativeProviderEnvironment(provider, explicit, host)).toBe(explicit); + }); + + it("preserves existing qualified and legacy missing-environment behavior", () => { + const host = { OPENAI_API_KEY: "existing-host-key", OPENROUTER_API_KEY: "existing-opencode-key" }; + for (const provider of [{ kind: "codex" }, { kind: "opencode" }, { kind: "acpx", agent: "codex" }, { kind: "acpx", agent: "claude" }]) { + expect(resolveNativeProviderEnvironment(provider as NativeExecutionInput["provider"], undefined, host)).toBe(host); + } + }); + it("inherits the host executable and credential-home context", () => { expect( buildNativeProviderEnvironment( @@ -7859,11 +7906,11 @@ describe("native process ownership", () => { }, ); - it("rejects ACPX Pi before constructing a backend", async () => { + it.each(["pi", "cursor", "copilot"])("rejects ACPX candidate %s without host authorization before constructing a backend", async (agent) => { const piExecution = { ...execution, binding: { ...execution.binding, runId: "run-acpx-pi-rejected" }, - provider: { kind: "acpx", agent: "pi", model: "pi-model" }, + provider: { kind: "acpx", agent, model: "pi-model" }, session: { ...execution.session, driverKind: "acpx_runtime" }, } as unknown as NativeExecutionInputV1; state.createBackend.mockClear(); @@ -7874,7 +7921,7 @@ describe("native process ownership", () => { execution: piExecution, runnerInstanceId: "runner", }), - ).rejects.toThrow("descriptor-confined verified launch"); + ).rejects.toThrow("exact host qualification authorization"); expect(state.createBackend).not.toHaveBeenCalled(); }); }); diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index 2da691b5e1..36884c0980 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -1,3 +1,4 @@ +import { resolveAcpxQualification } from "./acpx-qualification.js"; import { readLocalAiCredentialFile } from "../local-ai-credential-file.js"; import { prepareGrokRunnerCredentials } from "./grok-runner-credentials.js"; import { copyBackGrokAuth } from "@paperclipai/adapter-grok-local/server"; @@ -587,6 +588,19 @@ export function buildNativeProviderEnvironment( return environment; } +/** Missing candidate bindings must not turn the controller's ambient credentials into explicit input. */ +export function resolveNativeProviderEnvironment( + provider: NativeExecutionInput["provider"], + configured: NodeJS.ProcessEnv | undefined, + host: NodeJS.ProcessEnv = process.env, +): NodeJS.ProcessEnv { + if (configured !== undefined) return configured; + if (provider.kind === "acpx" && ["pi", "cursor", "copilot"].includes(provider.agent)) { + return buildNativeProviderEnvironment({}, host); + } + return host; +} + type PlanSynchronization = { eventId: string; planId: string; @@ -7361,10 +7375,11 @@ async function executePaperclipNativeSessionWithinScope( } if ( input.execution.provider.kind === "acpx" && - input.execution.provider.agent === "pi" + ["pi", "cursor", "copilot"].includes(input.execution.provider.agent) && + !resolveAcpxQualification(input.execution.provider, process.env) ) { throw new Error( - "paperclip_runner_provider_unsupported: ACPX Pi is unavailable until descriptor-confined verified launch is implemented", + "paperclip_runner_provider_unsupported: ACPX candidate requires exact host qualification authorization", ); } const preparationSpans = input.preparationSpans ?? []; @@ -8198,7 +8213,7 @@ async function executePaperclipNativeSessionWithinScope( scope: input.execution.binding, target: input.runnerExecutionTarget, cwd: input.execution.workspace.cwd, - env: input.runnerEnvironment ?? process.env, + env: resolveNativeProviderEnvironment(input.execution.provider, input.runnerEnvironment), resolveCredentials: (binding) => resolveGitHubOperationCredentials(input.db, binding), onLog: input.onLog, }); @@ -8263,8 +8278,8 @@ async function executePaperclipNativeSessionWithinScope( createNativeSessionBackend(input.execution, { runnerInstanceId: input.runnerInstanceId, onSpawn: input.onSpawn, - opencodeEnvironment: input.runnerEnvironment ?? process.env, - acpxEnvironment: input.runnerEnvironment ?? process.env, + opencodeEnvironment: resolveNativeProviderEnvironment(input.execution.provider, input.runnerEnvironment), + acpxEnvironment: resolveNativeProviderEnvironment(input.execution.provider, input.runnerEnvironment), opencodeRuntimeDirectory: resolve( resolvePaperclipInstanceRoot(), "runtime", @@ -9106,10 +9121,10 @@ async function executePaperclipNativeSessionWithinScope( summary: native.result.summary, sessionId: native.normalizedSessionId, sessionDisplayId: native.providerSessionId ?? native.normalizedSessionId, - provider: "openai", + provider: nativeUsageBiller(input.execution.provider), model: input.execution.provider.model, usage: normalizeNativeUsage(native.usage), - costUsd: nativeUsageCostUsd(native.usage), + costUsd: nativeUsageCostUsd(native.usage, input.execution.provider), usageBasis: "per_run", nativeFinalization: finalization, }; @@ -9165,7 +9180,23 @@ function nativeUsageMeasurement(usage: Record) { ); } -export function nativeUsageCostUsd(usage: Record | null) { +export function nativeUsageBiller(provider: NativeExecutionInput["provider"]): string { + if (provider.kind === "acpx") { + if (provider.agent === "cursor") return "cursor"; + if (provider.agent === "copilot") return "github"; + if (provider.agent === "pi") return "openrouter"; + } + return "openai"; +} + +export function nativeUsageCostUsd( + usage: Record | null, + provider?: NativeExecutionInput["provider"], +) { + // The ACP normalization contract fills absent per-turn cost with zero and + // reports actual cost cumulatively. Until it carries an authoritative run + // delta with provenance, neither value is a candidate's billed USD receipt. + if (provider?.kind === "acpx" && ["cursor", "copilot", "pi"].includes(provider.agent)) return undefined; if (!usage) return undefined; const measurement = nativeUsageMeasurement(usage); const direct = @@ -9311,6 +9342,10 @@ type RemoteProviderPackManifest = { distDigest: string; bridgeDigest: string; acpxProfileDigests: typeof REMOTE_PROVIDER_PACK_PROFILE_DIGESTS; + candidateProviders?: Partial>; artifacts: { grokLauncher: { path: string; sha256: string }; nodeCommand: { path: string; sha256: string }; @@ -9475,6 +9510,28 @@ export function readRemoteProviderPackManifest( "runner_remote_provider_artifact_incompatible: provider dist tree digest mismatch", ); } + if (payload.candidateProviders !== undefined) { + const candidates = payload.candidateProviders; + if (!candidates || typeof candidates !== "object" || Array.isArray(candidates) || Object.keys(candidates).length > 3) { + throw new Error("runner_remote_provider_artifact_incompatible: invalid candidate inventory"); + } + for (const [provider, candidate] of Object.entries(candidates)) { + const expectedPath = `provider-assets/${provider}/${payload.target.platform}-${payload.target.architecture}`; + if (!["cursor", "copilot", "pi"].includes(provider) || !candidate + || Object.keys(candidate).some(key => !["version", "profileDigest", "closureDigest", "qualification", "path", "sha256"].includes(key)) + || candidate.qualification !== "pending" || candidate.path !== expectedPath + || typeof candidate.version !== "string" || !candidate.version || candidate.version.length > 120 + || !/^sha256:[a-f0-9]{64}$/.test(candidate.profileDigest) + || !/^sha256:[a-f0-9]{64}$/.test(candidate.closureDigest) + || !/^sha256:[a-f0-9]{64}$/.test(candidate.sha256)) { + throw new Error("runner_remote_provider_artifact_incompatible: invalid candidate identity"); + } + const candidatePath = providerPackRelativePath(candidate.path, "candidate assets"); + if (sha256DirectoryTree(resolve(packRoot, candidatePath)) !== candidate.sha256) { + throw new Error("runner_remote_provider_artifact_incompatible: candidate asset tree digest mismatch"); + } + } + } const bridgeDigest = `sha256:${createHash("sha256") .update(payload.artifacts.opencodeProxy.sha256) .update("\n") @@ -10881,6 +10938,7 @@ async function createRunnerdBackendWithinSessionClaim( "const tree=(treeRoot)=>{const digest=crypto.createHash('sha256');const visit=(directory,prefix='')=>{for(const entry of fs.readdirSync(directory,{withFileTypes:true}).sort((a,b)=>a.name.localeCompare(b.name))){const relative=prefix?prefix+'/'+entry.name:entry.name;const absolute=path.join(directory,entry.name);if(entry.isDirectory()){digest.update('directory\\0'+relative+'\\n');visit(absolute,relative)}else if(entry.isFile()){digest.update('file\\0'+relative+'\\0'+'sha256:'+crypto.createHash('sha256').update(fs.readFileSync(absolute)).digest('hex')+'\\n')}else if(entry.isSymbolicLink()){digest.update('symlink\\0'+relative+'\\0'+fs.readlinkSync(absolute)+'\\n')}else throw new Error('unsupported dist entry '+relative)}};visit(treeRoot);return 'sha256:'+digest.digest('hex')}", "for(const name of ['nodeCommand','productionLock','opencodeCommand','opencodeExecutable','opencodeProxy','acpxSidecar','grokLauncher']){const artifact=manifest.payload.artifacts[name];if(hash(artifact.path)!==artifact.sha256)throw new Error(name+' digest mismatch')}", "if(tree(path.join(root,'dist'))!==manifest.payload.distDigest)throw new Error('dist tree digest mismatch')", + "for(const candidate of Object.values(manifest.payload.candidateProviders||{})){if(tree(path.join(root,candidate.path))!==candidate.sha256)throw new Error('candidate asset tree digest mismatch')}", "const version=process.versions.node.split('.').map(Number)", "const minimum=manifest.payload.pins.nodeMinimum.split('.').map(Number)", "if(version[0] profile.value === acpxAgent && !profile.qualified); + if (pendingAcpxProfile) { + if (!model) throw new PaperclipRunnerProviderProfileError("paperclip_runner_acpx_model_required", `${pendingAcpxProfile.label} requires an explicit model ID; there is no default model.`); + // Qualification authority belongs to the isolated server operator. Never + // read this allowlist from adapter config, credential refs, or run env. + let qualification: AcpxQualificationCandidate | undefined; + try { + qualification = resolveAcpxQualification({ kind: "acpx", agent: pendingAcpxProfile.value, model }, process.env); + } catch { + throw new PaperclipRunnerProviderProfileError("paperclip_runner_acpx_qualification_invalid", "ACPX qualification requires an exact host-authorized candidate and model."); + } + if (qualification) { + return { provider: "acpx", backend: "acpx_runtime", model, acpxAgent: qualification }; + } + throw new PaperclipRunnerProviderProfileError("paperclip_runner_acpx_agent_unavailable", `${pendingAcpxProfile.label} is awaiting local and Daytona qualification. Its profile is not enabled for production runs.`); + } if (acpxAgent !== "claude" && acpxAgent !== "codex" && acpxAgent !== "grok") { throw new PaperclipRunnerProviderProfileError( "paperclip_runner_acpx_agent_unavailable", - "Paperclip Runner ACPX requires the qualified Claude or Codex agent profile; Pi is not available.", + "Paperclip Runner ACPX requires a qualified agent profile.", ); } const qualifiedModel = QUALIFIED_ACPX_RUNNER_MODELS[acpxAgent]; diff --git a/server/src/services/workspace-git-snapshot-streaming.test.ts b/server/src/services/workspace-git-snapshot-streaming.test.ts index 19bfa64887..fa6a8247cb 100644 --- a/server/src/services/workspace-git-snapshot-streaming.test.ts +++ b/server/src/services/workspace-git-snapshot-streaming.test.ts @@ -51,8 +51,10 @@ function localClient(commands: string[]): SandboxManagedRuntimeClient { }; } +// macOS adds per-file process and filesystem overhead to the real deletion lane. +const realGitTimeoutMs = process.platform === "darwin" ? 300_000 : 180_000; it("streams and stages all four real Git filename lanes above 32 MiB through the shared scheduler", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-streaming-real-git-")); + const root = await fs.realpath(await fs.mkdtemp(path.join(os.tmpdir(), "pc-stream-git-"))); directories.push(root); const repo = path.join(root, "repo"); await fs.mkdir(repo); @@ -60,7 +62,9 @@ it("streams and stages all four real Git filename lanes above 32 MiB through the const git = (args: string[]) => runLocalGit(repo, args, { env, timeout: 120_000, maxBuffer: 64 * 1024 }); await git(["init"]); await git(["commit", "--allow-empty", "-qm", "fixture"]); - const parent = path.join(repo, ...Array.from({ length: 4 }, () => "nested-".repeat(30)), "storybook-output"); + // Leave room for the fixture/staging root below macOS's 1,024-byte path + // limit while keeping each 40,000-name Git lane above the 32 MiB boundary. + const parent = path.join(repo, ...Array.from({ length: 3 }, () => "nested-".repeat(30)), "storybook-output"); await fs.mkdir(parent, { recursive: true }); const fileName = (index: number) => `${"asset-".repeat(36)}${index}.js`; const writeFiles = async (content: string) => { @@ -92,7 +96,8 @@ it("streams and stages all four real Git filename lanes above 32 MiB through the bytes += Buffer.byteLength(relative) + 1; } expect(count).toBe(40_000); - expect(bytes).toBe(43_428_890); + expect(bytes).toBe(34_988_890); + expect(bytes).toBeGreaterThan(32 * 1024 * 1024); }; // Standalone callers must use the same streaming semantics. setExpensiveWorkspaceGitExecutor(null); @@ -147,4 +152,4 @@ it("streams and stages all four real Git filename lanes above 32 MiB through the expect(remaining).toBe(0); expect(commands.every((command) => command.length < 16 * 1024)).toBe(true); expect(scheduler.snapshot()).toMatchObject({ activeCount: 0, queuedCount: 0, inFlightCount: 0, cacheBytes: 0 }); -}, 180_000); +}, realGitTimeoutMs); diff --git a/tests/runner-e2e/FIXTURES.md b/tests/runner-e2e/FIXTURES.md index 6aaf3b6f19..bd0696b76b 100644 --- a/tests/runner-e2e/FIXTURES.md +++ b/tests/runner-e2e/FIXTURES.md @@ -234,6 +234,14 @@ must be an automatic, causally bound repair that records completion. They use public task comments/status APIs and run-detail evidence; no private runtime hooks or database mutations are used by the fixture. +The explicit-only `extended-harnesses` suite uses five bounded journeys for each +pending ACP candidate on local and Daytona. Candidate profile metadata includes +the exact authenticated discovery choice without promoting it to a product +default. Its file case anchors the task to a public project workspace, validates +the model's claimed result by reading the actual final bytes, and also exercises +remote copy-back. Keep candidate admission scoped to the selected model and the +isolated operator environment; ordinary agent configuration must not enable it. + ## Persistent agent files The `instruction_persistence` flow uses production managed storage and public file diff --git a/tests/runner-e2e/README.md b/tests/runner-e2e/README.md index c1f73bb303..7b99f319b2 100644 --- a/tests/runner-e2e/README.md +++ b/tests/runner-e2e/README.md @@ -104,6 +104,8 @@ Shell variables take precedence over the local file. The recognized names are: - `XAI_API_KEY` (local Grok API-key profile) - `GROK_AUTH_JSON` (local native Grok subscription profile) - `DAYTONA_API_KEY` +- `CURSOR_AUTH_TOKEN` (extended Cursor candidate) +- `COPILOT_GITHUB_TOKEN` (extended Copilot candidate) - `PAPERCLIP_E2E_DAYTONA_IMAGE` (Daytona only) The image must be an immutable `image@sha256:...` reference. The launcher @@ -1195,3 +1197,46 @@ missing events or a narrative completion cannot pass. Existing run, copyback, screenshot, billing and environment cleanup checks apply. Use `--id api-response-reading.runner-codex.daytona.saved-text-pages` with an immutable Daytona image; no private hooks or fixture database writes are used. + +## Extended ACP harnesses (explicit only) + +`--suite extended-harnesses` declares 30 Product E2E cells: Cursor, Copilot, +and Pi on local and Daytona, each exercising authenticated completion, +question/answer continuation, revision-bound semantic plan approval, restart +with pending input, and file edit plus independent byte validation. The file +case uses a public project workspace so Daytona copy-back is graded too. +These are candidate definitions, not a claim of provider qualification. Native +provider-specific questions, plan decisions, restrictive permissions and steering +need their separate conformance/qualification evidence. + +```sh +pnpm test:e2e:runner -- --list --suite extended-harnesses +pnpm test:e2e:runner -- --id extended-harnesses.runner-acpx-pi.local.hello-complete +``` + +The suite is excluded from `--all`, and candidate cells never automatically +retry. Select one cell first, reserve its spend and reconcile provider billing +before another attempt. Existing subscriptions/credits and incremental cash +charges are separate; unavailable receipts do not mean zero cost. The September +28 qualification budget is $100 total including retries and Daytona resources: +$25 per provider and $25 coordinated infrastructure/diagnosis. + +The launcher binds only the selected candidate and exact discovered model in +`PAPERCLIP_RUNNER_ACPX_QUALIFICATION`, a JSON array of `{agent,model}` pairs. +The server reads this operator environment at its normal runnerd construction +boundary; agent config/environment cannot enable qualification. Normal hosts +have no admission override. It does not bypass profile, executable, credential, +company, tool or permission checks. Keep this variable confined to isolated +qualification instances. Model catalog discovery alone does not prove inference +entitlement; all three profiles remain pending until the required live evidence +passes. Cursor and Copilot models are the explicit September 28 authenticated +discovery choices; Pi imports its production profile's fixed OpenRouter model. + +Run each candidate from its provider branch, with its verified candidate assets +materialized under the runner package, and build the TypeScript sidecar before +local execution. Daytona additionally requires that branch's immutable Linux +candidate image and the matching controller-owned provider pack described in +[`docker/daytona-runner/README.md`](../../docker/daytona-runner/README.md). +The separate Runner Evals `extended-harnesses` campaign lives in the private +`paperclip-evals` repository and grades semantic protocol behavior against the +mock control plane. Neither suite substitutes for the other. diff --git a/tests/runner-e2e/SECURITY.md b/tests/runner-e2e/SECURITY.md index b3089742a9..080eb8b534 100644 --- a/tests/runner-e2e/SECURITY.md +++ b/tests/runner-e2e/SECURITY.md @@ -278,3 +278,17 @@ specific process signal, never a name-based or machine-wide process kill. The bounded fixture command is released even on failure; normal instance cleanup still owns all disposable processes and files. No credentials enter the prompt, fault metadata, or structured grading fixtures. + +### Candidate ACP qualification + +The explicit-only `extended-harnesses` launcher removes ambient candidate +admission, then authorizes only selected candidate/model pairs in its isolated +server process. The server never reads admission from agent configuration, +resolved credential bindings, or provider environment. Invalid, duplicate and +mismatched authorization fails closed. Verified packaging and normal company +and tool governance still apply. `CURSOR_AUTH_TOKEN` and +`COPILOT_GITHUB_TOKEN` enter via encrypted company secret references, and all +Cursor, Copilot, GitHub and GH environment variables are stripped from the +server environment. GitHub PAT shapes are included in retained-evidence scans. +Candidates have no automatic infrastructure retries; spending must be reconciled +before a deliberate repeat. diff --git a/tests/runner-e2e/catalog.test.ts b/tests/runner-e2e/catalog.test.ts index 749207e66c..575c9f4322 100644 --- a/tests/runner-e2e/catalog.test.ts +++ b/tests/runner-e2e/catalog.test.ts @@ -91,10 +91,10 @@ describe("runner E2E catalog", () => { expect(localIntegrityTasks).toHaveLength(2); expect(openRouterBreadthTasks).toHaveLength(3); expect(runnerSuites.map((suite) => suite.expectedMatrixSize)).toEqual([ - 3, 16, 16, 2, 8, 46, 23, 47, 20, 52, 28, 18, 6, 6, 4, 48, 16, 10, 2, + 30, 3, 16, 16, 2, 8, 46, 23, 47, 20, 52, 28, 18, 6, 6, 4, 48, 16, 10, 2, ]); - expect(validateRunnerCatalog()).toHaveLength(371); - expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(371); + expect(validateRunnerCatalog()).toHaveLength(401); + expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(401); expect( runnerMatrix.filter((entry) => entry.suite.id === "core-compatibility"), ).toHaveLength(48); diff --git a/tests/runner-e2e/catalog.ts b/tests/runner-e2e/catalog.ts index 662bc66175..6837d2399b 100644 --- a/tests/runner-e2e/catalog.ts +++ b/tests/runner-e2e/catalog.ts @@ -151,7 +151,8 @@ function nativeProfile(input: { provider: "codex" | "opencode" | "acpx"; model: string; credential: RunnerProfileFixture["credential"]; - acpxAgent?: "claude" | "codex" | "grok"; + acpxAgent?: "claude" | "codex" | "grok" | "cursor" | "copilot" | "pi"; + qualificationCandidate?: RunnerProfileFixture["qualificationCandidate"]; supportedEnvironments?: readonly (typeof ENVIRONMENT_IDS)[number][]; modelQualification?: RunnerProfileFixture["modelQualification"]; ranking?: RunnerProfileFixture["ranking"]; @@ -191,6 +192,7 @@ function nativeProfile(input: { return commonAgent(buildInput, input.id, "paperclip_runner", { provider: input.provider, model: input.model, + ...(input.qualificationCandidate ? { timeoutSec: 120 } : {}), lifecycleMode: "per_turn", idleTimeoutMs: 300_000, ...permissionConfig, @@ -310,6 +312,27 @@ export const runnerProfiles: readonly RunnerProfileFixture[] = [ }), ] as const; +// Explicit qualification choices from authenticated model discovery, not shipped +// defaults or claims that these candidates have passed inference qualification. +export const extendedHarnessProfiles: readonly RunnerProfileFixture[] = [ + nativeProfile({ + id: "runner-acpx-cursor", label: "Runner Cursor (candidate)", provider: "acpx", acpxAgent: "cursor", + qualificationCandidate: "cursor", credential: "CURSOR_AUTH_TOKEN", + model: "gpt-5.6-luna[context=272k,reasoning=medium,fast=false]", + modelQualification: { source: "candidate_runner_profile", qualificationId: "cursor:2026.09.26-dd393fe:discovery-2026-09-28" }, + }), + nativeProfile({ + id: "runner-acpx-copilot", label: "Runner Copilot (candidate)", provider: "acpx", acpxAgent: "copilot", + qualificationCandidate: "copilot", credential: "COPILOT_GITHUB_TOKEN", model: "gpt-5.6-luna", + modelQualification: { source: "candidate_runner_profile", qualificationId: "copilot:1.0.88:discovery-2026-09-28" }, + }), + nativeProfile({ + id: "runner-acpx-pi", label: "Runner Pi (candidate)", provider: "acpx", acpxAgent: "pi", + qualificationCandidate: "pi", credential: "OPENROUTER_API_KEY", model: QUALIFIED_ACPX_PROFILES.pi.qualificationModel, + modelQualification: { source: "candidate_runner_profile", qualificationId: "pi:0.0.33:0.84.2:openrouter" }, + }), +]; + /** Narrow legacy ACP lanes used only by the explicit context-integrity matrix. */ export const legacyAcpxProfiles: readonly RunnerProfileFixture[] = [ legacyProfile({ @@ -985,7 +1008,34 @@ const everydayProfiles = [ nativeProfile({ id: "runner-codex-mini", label: "Runner Codex Mini", provider: "codex", model: "gpt-5.4-mini", modelQualification: {source:"qualified_runner_profile",qualificationId:"everyday-codex-mini-pilot"}, credential: "OPENAI_API_KEY", supportedEnvironments: ["local"] }), ].map(productionStoryProfile); +export const extendedHarnessFileTask: RunnerTaskFixture = { + id: "file-edit-validate", label: "Edit a file and validate its contents", groups: [], + workMode: "standard", flow: "single_turn", expectedRunCount: 1, + attemptTimeoutMs: { local: 180_000, daytona: 300_000 }, + expectedTerminalState: { issue: "done", run: "succeeded" }, + buildTitle: nonce => `Extended harness file validation ${nonce}`, + buildVisibleMarker: nonce => `EXTENDED-FILE-${nonce}`, + buildPrompt: nonce => [ + `Create extended-${nonce}.txt in the current execution workspace with exactly ready-${nonce} followed by a newline.`, + `Edit that file to contain exactly verified-${nonce} followed by a newline, then run a command that reads it and fails unless those exact bytes match.`, + `After successful validation, call paperclip_finish with reportedWorkDisposition done, summary EXTENDED-FILE-${nonce}, the current completion contract revision and satisfied objective criterion, no remaining work, and the actual validation command and result.`, + `Wait for paperclip_finish to succeed, then emit exactly EXTENDED-FILE-${nonce} as your final response. Do not create unrelated files or work.`, + ].join("\n"), + buildMatchers: (nonce, execution) => [ + ...terminalMatchers(`EXTENDED-FILE-${nonce}`, execution), + { kind: "file_exact", path: `extended-${nonce}.txt`, expected: `verified-${nonce}\n` }, + ], +}; + export const runnerSuites: readonly RunnerSuiteFixture[] = [ + { + id: "extended-harnesses", label: "Extended ACP harnesses", manualOnly: true, + description: "Explicit candidate qualification through real Paperclip tools, browser interactions, file edits and restart recovery.", + groups: ["native"], profiles: extendedHarnessProfiles, environments: runnerEnvironments, + tasks: [...openRouterBreadthTasks, localIntegrityTasks[1]!, extendedHarnessFileTask], + expectedMatrixSize: 30, + definitionMetadata: { version: 1, qualification: "pending", scheduling: "explicit-only", admission: "host-exact-candidate-and-model", authenticatedDiscoveryDate: "2026-09-28" }, + }, { id: "instruction-persistence", label: "Instruction Persistence", description: "Agent-owned text and binary files round trip through the editor, survive a server restart and fresh task, and synchronize concurrent edits per file with last-sync-wins.", @@ -1312,8 +1362,9 @@ function assertNoRawSecretValues(value: unknown, label: string) { } export function validateRunnerCatalog(): MatrixExecution[] { - const allProfiles = [...runnerProfiles, ...legacyAcpxProfiles, ...pendingContextIntegrityProfiles, ...openRouterBreadthProfiles, ...everydayProfiles.filter(p => !runnerProfiles.some(existing => existing.id === p.id))]; + const allProfiles = [...extendedHarnessProfiles, ...runnerProfiles, ...legacyAcpxProfiles, ...pendingContextIntegrityProfiles, ...openRouterBreadthProfiles, ...everydayProfiles.filter(p => !runnerProfiles.some(existing => existing.id === p.id))]; const allTasks = [ + extendedHarnessFileTask, ...contextIntegrityTasks, ...accountingTasks, ...lifecycleLiveTasks, diff --git a/tests/runner-e2e/daytona-image-content.ts b/tests/runner-e2e/daytona-image-content.ts index bcf682284d..92ffbe1b70 100644 --- a/tests/runner-e2e/daytona-image-content.ts +++ b/tests/runner-e2e/daytona-image-content.ts @@ -35,6 +35,7 @@ export const DAYTONA_IMAGE_INPUT_PATHS = [ "packages/paperclip-runner/runner/crates", "packages/paperclip-runner/scripts/acpx-sidecar-contract.mjs", "packages/paperclip-runner/scripts/build-provider-pack.mjs", + "packages/paperclip-runner/scripts/candidate-provider-pack.mjs", "packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs", "packages/paperclip-runner/scripts/generate-acpx-sidecar-contract.mjs", "packages/paperclip-runner/scripts/generate-protocol-schema-module.mjs", @@ -66,6 +67,17 @@ export interface DaytonaImageContentOptions { platform?: string; baseImages?: readonly string[]; frontendDigest?: string; + /** Must match the Docker PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS build argument. */ + candidateProviders?: readonly string[]; +} + +export function normalizedDaytonaCandidateProviders(values: readonly string[]): string[] { + const selected = [...values].sort(); + if (selected.some(value => !["cursor", "copilot", "pi"].includes(value)) + || new Set(selected).size !== selected.length) { + throw new Error("Daytona candidate providers must be distinct known ACP profiles"); + } + return selected; } function compareNames(left: string, right: string): number { @@ -263,6 +275,9 @@ export async function computeDaytonaImageContentId( for (const baseImage of await resolveBaseImages(root, options.baseImages)) { updateRecord(hash, "base-image", baseImage); } + for (const candidate of normalizedDaytonaCandidateProviders(options.candidateProviders ?? [])) { + updateRecord(hash, "candidate-provider", candidate); + } for (const inputPath of inputPaths) { await hashEntry(hash, root, inputPath); } @@ -271,7 +286,13 @@ export async function computeDaytonaImageContentId( const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : null; if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) { - computeDaytonaImageContentId() + const arguments_ = process.argv.slice(2); + const candidateFlag = arguments_[0]; + if (arguments_.length > 1 || (candidateFlag !== undefined && !candidateFlag.startsWith("--candidate-providers="))) { + throw new Error("Expected only --candidate-providers=cursor,copilot,pi"); + } + const candidateProviders = candidateFlag?.slice("--candidate-providers=".length).split(",").filter(Boolean) ?? []; + computeDaytonaImageContentId({ candidateProviders }) .then((contentId) => process.stdout.write(`${contentId}\n`)) .catch((error: unknown) => { process.stderr.write( diff --git a/tests/runner-e2e/daytona-image.test.ts b/tests/runner-e2e/daytona-image.test.ts index 856190f842..3d4a2786b5 100644 --- a/tests/runner-e2e/daytona-image.test.ts +++ b/tests/runner-e2e/daytona-image.test.ts @@ -82,7 +82,7 @@ describe("runner E2E Daytona image contract", () => { ); expect(extractDaytonaBaseImages(dockerfile)).toEqual([ "rust:1.97-bookworm@sha256:408fe88047cef61a2087653b0c5255fa51c0f2d6d94ddedd7a2562a9b91a46f6", - "node:24-bookworm@sha256:9137a20e25879e0b557227b57e3ee4e9af4bde29eb3db66134cd1723e84f830b", + "node:24.21.0-bookworm@sha256:5a750d3be5e5c80275f8c9a5367c3aed99c2875656590c8d0701c7ee687f5f0a", "daytonaio/sandbox:0.8.0@sha256:eadf88e4391072b7ad4bed27d9cadfc9fe9d8ed375d9219d34c2ccb518f213e3", ]); expect(dockerignore).toContain("**/node_modules"); @@ -199,6 +199,7 @@ describe("runner E2E Daytona image contract", () => { "patches", "packages/paperclip-eval-kernel/src", "packages/paperclip-runner/package.json", + "packages/paperclip-runner/scripts/candidate-provider-pack.mjs", "packages/paperclip-runner/runner/crates", "packages/paperclip-runner/src", ]) { @@ -275,6 +276,14 @@ describe("runner E2E Daytona image contract", () => { 'pub const VERSION: &str = "one";\n', ); const baseline = await computeDaytonaImageContentId(options); + const candidate = await computeDaytonaImageContentId({ ...options, candidateProviders: ["pi"] }); + expect(candidate).not.toBe(baseline); + expect(await computeDaytonaImageContentId({ ...options, candidateProviders: ["copilot", "pi"] })) + .toBe(await computeDaytonaImageContentId({ ...options, candidateProviders: ["pi", "copilot"] })); + await expect(computeDaytonaImageContentId({ ...options, candidateProviders: ["pi", "pi"] })) + .rejects.toThrow("distinct known"); + await expect(computeDaytonaImageContentId({ ...options, candidateProviders: ["unknown"] })) + .rejects.toThrow("distinct known"); expect( await computeDaytonaImageContentId({ ...options, diff --git a/tests/runner-e2e/extended-harnesses.test.ts b/tests/runner-e2e/extended-harnesses.test.ts new file mode 100644 index 0000000000..6464263d2a --- /dev/null +++ b/tests/runner-e2e/extended-harnesses.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from "vitest"; +import { runnerMatrix, runnerSuites, extendedHarnessProfiles, extendedHarnessFileTask } from "./catalog.js"; +import { buildRunnerE2EProcessEnvironment, buildPaperclipServerEnvironment } from "./harness-env.js"; +import { parseRunnerSelectors, selectRunnerExecutions } from "./selectors.js"; +import { findSecretLeak, redactText } from "./redaction.js"; + +const selected = runnerMatrix.filter(cell => cell.suite.id === "extended-harnesses"); +describe("extended ACP harness qualification", () => { + it("has exactly five real product journeys per provider in both environments, excluded from all", () => { + expect(selected).toHaveLength(30); + expect(new Set(selected.map(cell => cell.environment.id))).toEqual(new Set(["local", "daytona"])); + expect(new Set(selected.map(cell => cell.task.id))).toEqual(new Set([ + "hello-complete", "question-resume-complete", "plan-approve-complete", "structured-question-restart-resume", "file-edit-validate", + ])); + const all = selectRunnerExecutions(parseRunnerSelectors(["--all"])); + expect(all.some(cell => cell.suite.id === "extended-harnesses")).toBe(false); + expect(runnerSuites.find(suite => suite.id === "extended-harnesses")?.manualOnly).toBe(true); + }); + it("uses exact discovered models, encrypted credential references and pending qualification metadata", () => { + expect(extendedHarnessProfiles.map(profile => profile.model)).toEqual([ + "gpt-5.6-luna[context=272k,reasoning=medium,fast=false]", "gpt-5.6-luna", "openrouter/deepseek/deepseek-v4-flash-0731", + ]); + for (const profile of extendedHarnessProfiles) { + expect(profile.modelQualification.source).toBe("candidate_runner_profile"); + const secretRef = { type: "secret_ref" as const, secretId: "11111111-1111-4111-8111-111111111111", version: "latest" as const }; + const payload = profile.buildAgent({ executionId: "fixture", environmentId: "local", environmentFixtureId: "local", workspacePath: "/tmp/workspace", secretRefs: { [profile.credential]: secretRef } }); + expect(payload.adapterConfig).toMatchObject({ provider: "acpx", model: profile.model, timeoutSec: 120, acpxAgent: profile.qualificationCandidate, env: { [profile.credential]: secretRef } }); + } + }); + it("replaces ambient admission with the selected exact pairs and strips raw provider credentials from server", () => { + const cell = selected[0]!; + const ambient = { PAPERCLIP_RUNNER_ACPX_QUALIFICATION: "ambient", CURSOR_AUTH_TOKEN: "cursor", CURSOR_API_KEY: "cursor-key", COPILOT_GITHUB_TOKEN: "copilot", GH_TOKEN: "github", GITHUB_TOKEN: "github" }; + const env = buildRunnerE2EProcessEnvironment(ambient, [cell]); + expect(JSON.parse(env.PAPERCLIP_RUNNER_ACPX_QUALIFICATION!)).toEqual([{ agent: "cursor", model: cell.profile.model }]); + const server = buildPaperclipServerEnvironment(env); + for (const name of Object.keys(ambient).filter(name => name !== "PAPERCLIP_RUNNER_ACPX_QUALIFICATION")) expect(server[name]).toBeUndefined(); + expect(buildRunnerE2EProcessEnvironment(ambient, []).PAPERCLIP_RUNNER_ACPX_QUALIFICATION).toBeUndefined(); + expect(() => buildRunnerE2EProcessEnvironment({}, [{ ...cell, suite: { ...cell.suite, manualOnly: false } }])).toThrow("explicit"); + }); + it("grades the actual file independently of the model's validation claim", () => { + const cell = selected.find(cell => cell.task.id === "file-edit-validate")!; + expect(extendedHarnessFileTask.buildMatchers("nonce", cell)).toContainEqual({ kind: "file_exact", path: "extended-nonce.txt", expected: "verified-nonce\n" }); + }); + it("redacts and detects GitHub credential shapes even without the bound value", () => { + const token = `github_pat_${"x".repeat(40)}`; + expect(findSecretLeak(token, [])).toBe("secret-shaped value"); + expect(redactText(token, [])).not.toContain(token); + }); +}); diff --git a/tests/runner-e2e/harness-env.ts b/tests/runner-e2e/harness-env.ts index 7b280009dd..33d168def1 100644 --- a/tests/runner-e2e/harness-env.ts +++ b/tests/runner-e2e/harness-env.ts @@ -25,7 +25,7 @@ const AMBIENT_EXTERNAL_STATE_KEYS = [ "PAPERCLIP_STORAGE_S3_PREFIX", "PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE", ] as const; -const PROVIDER_SECRET_KEY = /^(?:OPENAI|ANTHROPIC|OPENROUTER|DAYTONA|XAI|GROK)(?:_|$)/; +const PROVIDER_SECRET_KEY = /^(?:OPENAI|ANTHROPIC|OPENROUTER|DAYTONA|XAI|GROK|CURSOR|COPILOT|GITHUB|GH)(?:_|$)/; export function runnerE2EServerControlPaths(temporaryRoot: string) { const controlDirectory = path.join(temporaryRoot, "control"); @@ -108,6 +108,25 @@ export function buildRunnerE2EProcessEnvironment( // Announcements are unrelated to the scenarios and obscure screenshot evidence. result.PAPERCLIP_ANNOUNCEMENTS_ENABLED = "false"; delete result.OPENCODE_ALLOW_ALL_MODELS; + // Discard ambient admission. Only explicit candidate cells authorize the + // exact model in their isolated server; credentials still use company secrets. + delete result.PAPERCLIP_RUNNER_ACPX_QUALIFICATION; + const candidates = new Map(); + for (const execution of executions) { + const agent = execution.profile.qualificationCandidate; + if (!agent) continue; + if (execution.suite.id !== "extended-harnesses" || !execution.suite.manualOnly) { + throw new Error("Candidate qualification requires the explicit extended-harnesses suite"); + } + const prior = candidates.get(agent); + if (prior !== undefined && prior !== execution.profile.model) throw new Error("Conflicting candidate models"); + candidates.set(agent, execution.profile.model); + } + if (candidates.size > 0) { + result.PAPERCLIP_RUNNER_ACPX_QUALIFICATION = JSON.stringify( + [...candidates].map(([agent, model]) => ({ agent, model })), + ); + } // These stories explicitly require the native API surface. Other suites // retain the server default or any supplied operator restriction. if (executions.some((e) => isManagedHiringCase(e.suite.id, e.task.id) || chatNeedsApiTools(e.suite.id, e.task.id))) { diff --git a/tests/runner-e2e/launch.ts b/tests/runner-e2e/launch.ts index 7e6dd66d3a..ceb5927ec7 100644 --- a/tests/runner-e2e/launch.ts +++ b/tests/runner-e2e/launch.ts @@ -1022,6 +1022,7 @@ async function runExecutionWithRetry(input: { }); if (!firstResult) throw new Error(`No result produced for ${execution.id}`); if ( + execution.profile.qualificationCandidate !== undefined || options.ui || options.debug || firstResult.status !== "failed" || diff --git a/tests/runner-e2e/live-fixtures.test.ts b/tests/runner-e2e/live-fixtures.test.ts index 78cc912d61..17422768c9 100644 --- a/tests/runner-e2e/live-fixtures.test.ts +++ b/tests/runner-e2e/live-fixtures.test.ts @@ -4,6 +4,24 @@ import { runnerMatrix } from "./catalog.js"; import { setupLiveFixtures } from "./live-fixtures.js"; describe("live runner fixtures", () => { + it("anchors extended file validation to a public project workspace for local and remote copy-back", async () => { + const execution = runnerMatrix.find(e => e.id === "extended-harnesses.runner-acpx-pi.local.file-edit-validate")!; + let projectBody: any; + const api = { + async get() { return [{ id: "local", driver: "local" }]; }, + async postSensitive() { return { id: "secret" }; }, + async post(url: string, data: any) { + if (url === "/api/companies") return { id: "company", name: "Test" }; + if (url.endsWith("/agents")) return { id: "agent", ...data }; + if (url.endsWith("/projects")) { projectBody = data; return { id: "project", ...data }; } + throw new Error(`Unexpected POST ${url}`); + }, + } as unknown as RunnerApi; + const fixtures = await setupLiveFixtures({ api, execution, executionNonce: "nonce", workspacePath: "/tmp/fixture-workspace", credentials: { OPENROUTER_API_KEY: "fixture-key" } }); + expect(fixtures.project?.id).toBe("project"); + expect(projectBody).toMatchObject({ executionWorkspacePolicy: { environmentId: "local", workspaceStrategy: { type: "project_primary" } }, workspace: { cwd: "/tmp/fixture-workspace", sourceType: "local_path" } }); + }); + it.each([ ["runner-codex", "everyday-workflows", "hire-reuse"], ["runner-acpx-claude", "everyday-workflows", "hire-reuse"], diff --git a/tests/runner-e2e/live-fixtures.ts b/tests/runner-e2e/live-fixtures.ts index 7346ceb6dd..f4cf45f9ce 100644 --- a/tests/runner-e2e/live-fixtures.ts +++ b/tests/runner-e2e/live-fixtures.ts @@ -306,7 +306,8 @@ export async function setupLiveFixtures(input: { }, }); - if (execution.environment.configurationKey === "warm-reuse-v1") { + if (execution.environment.configurationKey === "warm-reuse-v1" + || (execution.suite.id === "extended-harnesses" && execution.task.id === "file-edit-validate")) { registry.register({ id: "project", dependencies: ["company", "environment"], @@ -316,9 +317,9 @@ export async function setupLiveFixtures(input: { return api.post( `/api/companies/${company.id}/projects`, { - name: `Runner E2E warm project ${input.executionNonce}`, + name: `Runner E2E workspace project ${input.executionNonce}`, description: - "Ephemeral project anchoring a reusable Daytona execution workspace", + "Ephemeral project anchoring the fixture execution workspace and file copy-back", executionWorkspacePolicy: { enabled: true, defaultMode: "shared_workspace", diff --git a/tests/runner-e2e/redaction.ts b/tests/runner-e2e/redaction.ts index ec9df5fd5c..af18974b8c 100644 --- a/tests/runner-e2e/redaction.ts +++ b/tests/runner-e2e/redaction.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { redactDiagnosticText } from "../../packages/adapter-utils/src/command-redaction.js"; const SECRET_SHAPES = [ + /\b(?:github_pat_|ghp_)[A-Za-z0-9_]{16,}\b/g, /\bsk-ant-[A-Za-z0-9_-]{16,}\b/g, /\bsk-(?:proj-)?[A-Za-z0-9_-]{16,}\b/g, /\b(?:openrouter|daytona)[-_]?(?:api)?[-_]?key["'=:\s]+[A-Za-z0-9._-]{12,}\b/gi, diff --git a/tests/runner-e2e/types.ts b/tests/runner-e2e/types.ts index 91b07a80d0..58bc93bda4 100644 --- a/tests/runner-e2e/types.ts +++ b/tests/runner-e2e/types.ts @@ -6,6 +6,8 @@ export const CREDENTIAL_NAMES = [ "XAI_API_KEY", "GROK_AUTH_JSON", "DAYTONA_API_KEY", + "CURSOR_AUTH_TOKEN", + "COPILOT_GITHUB_TOKEN", ] as const; export type CredentialName = (typeof CREDENTIAL_NAMES)[number]; @@ -64,9 +66,11 @@ export interface RunnerProfileFixture { source: | "adapter_constant" | "qualified_runner_profile" + | "candidate_runner_profile" | "openrouter_rankings_snapshot"; qualificationId: string; }; + qualificationCandidate?: "cursor" | "copilot" | "pi"; ranking?: { rank: number; canonicalModelId: string; diff --git a/ui/src/adapters/codex-local/config-fields.test.tsx b/ui/src/adapters/codex-local/config-fields.test.tsx index a665d2b221..261b194aa3 100644 --- a/ui/src/adapters/codex-local/config-fields.test.tsx +++ b/ui/src/adapters/codex-local/config-fields.test.tsx @@ -42,7 +42,7 @@ describe("Paperclip Runner Codex configuration", () => { expect(html).toContain(''); expect(html).toContain("OpenCode 1.18.32"); - expect(html).toContain("ACPX"); + expect(html).toContain(''); expect(html).not.toContain("Permission mode"); expect(html).not.toContain("Ask when requested"); expect(html).not.toContain("Ask for untrusted operations"); @@ -66,15 +66,19 @@ describe("Paperclip Runner Codex configuration", () => { expect(html).not.toContain("Ask for untrusted operations"); }); - it("offers ACPX Claude without a redundant agent selector", () => { + it("offers qualified Claude and keeps candidate ACP agents visibly disabled", () => { const html = renderRunner({ provider: "acpx", acpxAgent: "claude", acpxPermissionMode: "approve-reads", }); - expect(html).toContain(''); - expect(html).not.toContain("ACP agent"); + expect(html).toContain(''); + expect(html).toContain("ACP agent"); + expect(html).toContain(''); + expect(html).toContain(''); + expect(html).toContain(''); + expect(html).toContain(''); expect(html).not.toContain("Codex via ACPX"); expect(html).not.toContain("ACPX Codex"); expect(html).not.toContain("Pi via ACPX"); diff --git a/ui/src/adapters/codex-local/config-fields.tsx b/ui/src/adapters/codex-local/config-fields.tsx index 3282956d85..bb737ee45c 100644 --- a/ui/src/adapters/codex-local/config-fields.tsx +++ b/ui/src/adapters/codex-local/config-fields.tsx @@ -20,6 +20,7 @@ import { PAPERCLIP_RUNNER_IDLE_TIMEOUT_DEFAULT_MS, PAPERCLIP_RUNNER_IDLE_TIMEOUT_MAX_MS, PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES, + PAPERCLIP_RUNNER_ACPX_PROFILES, isPaperclipRunnerProvider, resolvePaperclipRunnerIdleTimeoutMs, resolvePaperclipRunnerPermissionMode, @@ -233,11 +234,28 @@ export function CodexLocalConfigFields({ - + )} + {runnerManaged && runnerProvider === "acpx" && runnerSchemaValue("acpxAgent", "claude") !== "grok" && ( + + + + )} {runnerManaged && runnerProvider === "claude_managed" && ( <> { "runPolicy", config, ); - expect(adapter).toContain("ACPX Claude"); + expect(adapter).toContain(''); expect(adapter).not.toContain("Runner lifecycle"); expect(configuration).not.toContain("Permission mode"); expect(configuration).not.toContain("Runner lifecycle"); expect(policy).toContain("Runner lifecycle"); expect(policy).toContain('value="45000"'); - expect(policy).not.toContain("ACPX Claude"); + expect(policy).not.toContain("ACP agents"); + }); + + it("keeps ACP agent admission choices in the adapter section", () => { + const config = { provider: "acpx", acpxAgent: "claude" }; + const adapter = renderSection(CodexLocalConfigFields, "paperclip_runner", "adapter", config); + const policy = renderSection(CodexLocalConfigFields, "paperclip_runner", "runPolicy", config); + + expect(adapter).toContain(''); + expect(adapter).toContain(''); + expect(adapter).not.toContain("Runner lifecycle"); + expect(policy).toContain("Runner lifecycle"); + expect(policy).not.toContain("ACP agent"); + expect(policy).not.toContain("qualification pending"); }); it.each([ diff --git a/ui/src/adapters/paperclip-runner/index.test.ts b/ui/src/adapters/paperclip-runner/index.test.ts index 4464cc03bf..0f447cb4fe 100644 --- a/ui/src/adapters/paperclip-runner/index.test.ts +++ b/ui/src/adapters/paperclip-runner/index.test.ts @@ -200,6 +200,37 @@ describe("paperclip runner transcript projection", () => { })).toEqual([expect.objectContaining({ kind: "system", text: expect.stringContaining("unsafe") })]); }); + it("preserves complete native plan context and revision identity through replay", () => { + const parse = paperclipRunnerUIAdapter.createStdoutParser!().parseLine; + const description = "# Plan\n" + "x".repeat(99_981) + "\nFINAL-CHECK"; + expect(description.length).toBe(100_000); + const questionId = `plan-${"a".repeat(64)}`; + const event = (eventType: string, payload: Record) => parse(JSON.stringify({ + type: "paperclip.prp.event", event: { eventType, payload }, + }), "2026-09-28T12:00:00.000Z"); + expect(event("runtime_request.created", { request: { + requestId: "native-plan", requestKind: "runtime", type: "input", input: { + schema: "paperclip.question_set.v1", description, + questions: [{ id: questionId, prompt: "Review", required: true, answerMode: "single_select", options: [{ id: "accept", label: "Accept plan" }, { id: "reject", label: "Reject plan" }, { id: "cancel", label: "Cancel plan request" }] }], + }, + } })).toEqual([expect.objectContaining({ questionSet: expect.objectContaining({ description, questions: [expect.objectContaining({ id: questionId })] }) })]); + expect(event("runtime_request.resolved", { requestId: "native-plan", response: { + schema: "paperclip.question_response.v1", answers: { [questionId]: { selectedOptionIds: ["accept"] } }, + } })).toEqual([expect.objectContaining({ questionSet: expect.objectContaining({ description }), response: expect.objectContaining({ answers: { [questionId]: { selectedOptionIds: ["accept"] } } }) })]); + }); + + it("rejects oversized native plan context instead of showing an incomplete decision", () => { + const entries = paperclipRunnerUIAdapter.parseStdoutLine(JSON.stringify({ + type: "paperclip.prp.event", event: { eventType: "runtime_request.created", payload: { request: { + requestId: "oversized-plan", type: "input", input: { + schema: "paperclip.question_set.v1", description: "x".repeat(100_001), + questions: [{ id: "plan-revision", prompt: "Review", answerMode: "single_select", options: [{ id: "accept", label: "Accept" }] }], + }, + } } }, + }), "2026-09-28T12:00:00.000Z"); + expect(entries).toEqual([{ kind: "system", ts: "2026-09-28T12:00:00.000Z", text: expect.stringContaining("No decision can be submitted") }]); + }); + it("coalesces runtime request lifecycle data and emits terminal state", () => { const parse = paperclipRunnerUIAdapter.createStdoutParser!().parseLine; const event = (eventType: string, payload: Record, turnId = "turn-1") => parse(JSON.stringify({ diff --git a/ui/src/adapters/paperclip-runner/index.ts b/ui/src/adapters/paperclip-runner/index.ts index 3ea3f58c0c..6d2ac167b7 100644 --- a/ui/src/adapters/paperclip-runner/index.ts +++ b/ui/src/adapters/paperclip-runner/index.ts @@ -573,7 +573,9 @@ function parseQuestionSet(value: unknown): PaperclipQuestionSet | null { return { schema: "paperclip.question_set.v1", ...(nullableText(input.title) ? { title: text(input.title).slice(0, 1_000) } : {}), - ...(nullableText(input.description) ? { description: text(input.description).slice(0, 4_000) } : {}), + // Native plan decisions bind to the complete canonical question context. + // The event boundary rejects oversized context rather than approving a slice. + ...(nullableText(input.description) ? { description: text(input.description) } : {}), ...(nullableText(input.submitLabel) ? { submitLabel: text(input.submitLabel).slice(0, 200) } : {}), questions, }; @@ -703,6 +705,11 @@ function parsePrpEvent( return entry ? [entry] : [{ kind: "system", ts, text: "Runner: Ignored an unsafe workspace file reference" }]; } if (eventType.startsWith("runtime_request.")) { + const request = record(payload.request ?? payload); + const input = record(request.input); + if (typeof input.description === "string" && input.description.length > 100_000) { + return [{ kind: "system", ts, text: "Runner: Cannot display this input request because its complete context exceeds 100,000 characters. No decision can be submitted from this request." }]; + } const entry = runtimeRequestEntry(eventType, payload, event, ts, state); return entry ? [entry] : []; } diff --git a/ui/src/components/MarkdownBody.test.tsx b/ui/src/components/MarkdownBody.test.tsx index 3615c501a4..0e8572fbdc 100644 --- a/ui/src/components/MarkdownBody.test.tsx +++ b/ui/src/components/MarkdownBody.test.tsx @@ -120,6 +120,33 @@ describe("MarkdownBody", () => { expect(html).toContain('alt="Org chart"'); }); + it("renders decision images as inert references without resolving or preloading them", () => { + const resolveImageSrc = vi.fn((src: string) => `https://resolver.invalid/${src}`); + const html = renderMarkdown( + '# Plan\n\n**Keep this step.**\n\n![Evidence](https://provider.invalid/track.png "Provider image")\n\n![Relative](images/proof.png)\n\n![Protocol relative](//provider.invalid/other.png)', + [], { mediaMode: "reference", resolveImageSrc }, + ); + + expect(html).toContain("

Plan

"); + expect(html).toContain("Keep this step."); + expect(html).toContain("Image: Evidence (https://provider.invalid/track.png)"); + expect(html).toContain('title="Provider image"'); + expect(html).toContain("Image: Relative (images/proof.png)"); + expect(html).toContain("Image: Protocol relative (//provider.invalid/other.png)"); + expect(html).not.toMatch(/<(?:img|link|iframe|video|audio)\b/); + expect(resolveImageSrc).not.toHaveBeenCalled(); + }); + + it("keeps diagram source inert in decision media mode", () => { + const source = 'flowchart LR\n A@{ img: "https://provider.invalid/diagram.png" }'; + const html = renderMarkdown(`\`\`\`mermaid\n${source}\n\`\`\``, [], { mediaMode: "reference" }); + + expect(html).toContain('class="language-mermaid"'); + expect(html).toContain("https://provider.invalid/diagram.png"); + expect(html).not.toContain('class="paperclip-mermaid"'); + expect(html).not.toContain("Rendering Mermaid diagram"); + }); + it("renders user, agent, project, skill, and routine mentions as chips", () => { const html = renderToStaticMarkup( diff --git a/ui/src/components/MarkdownBody.tsx b/ui/src/components/MarkdownBody.tsx index 98c06bf01f..24b9772854 100644 --- a/ui/src/components/MarkdownBody.tsx +++ b/ui/src/components/MarkdownBody.tsx @@ -90,6 +90,8 @@ interface MarkdownBodyProps { resolveImageSrc?: (src: string) => string | null; /** Called when a user clicks an inline image */ onImageClick?: (src: string) => void; + /** Keep untrusted decision media inert: image references and diagram source only. */ + mediaMode?: "render" | "reference"; /** * Resolver that decides which inline-code workspace file paths may be linked * to the issue file viewer. Omitting it (or returning null) leaves every @@ -719,6 +721,7 @@ function MarkdownBodyImpl({ externalReferences, resolveImageSrc, onImageClick, + mediaMode = "render", resolveWorkspaceFileRef, }: MarkdownBodyProps) { const { theme } = useTheme(); @@ -803,7 +806,7 @@ function MarkdownBodyImpl({ ), pre: ({ node: _node, children: preChildren, ...preProps }) => { const mermaidSource = extractMermaidSource(preChildren); - if (mermaidSource) { + if (mermaidSource && mediaMode === "render") { return ; } return {preChildren}; @@ -914,7 +917,13 @@ function MarkdownBodyImpl({ ); }, }; - if (resolveImageSrc || onImageClick) { + if (mediaMode === "reference") { + map.img = ({ src, alt, title }) => ( + + Image: {alt || "Untitled image"}{src ? ` (${src})` : ""} + + ); + } else if (resolveImageSrc || onImageClick) { map.img = ({ node: _node, src, alt, ...imgProps }) => { const resolved = resolveImageSrc && src ? resolveImageSrc(src) : null; const finalSrc = resolved ?? src; @@ -930,7 +939,7 @@ function MarkdownBodyImpl({ }; } return map; - }, [theme, linkIssueReferences, linkCaseReferences, externalReferenceLookup, resolveImageSrc, onImageClick]); + }, [theme, linkIssueReferences, linkCaseReferences, externalReferenceLookup, resolveImageSrc, onImageClick, mediaMode]); return (
{ expect(rows.findIndex((row) => row.contains(preview))).toBeGreaterThan(0); }); + it.each([ + { name: "write_document", anchored: true }, + { name: "mcp__paperclip__write_document", anchored: false }, + ])("anchors a settled ACP Plan only at the normalized display name $name", ({ name, anchored }) => { + const runId = "acpx-plan"; + planState.data = planDocument({ updatedAt: new Date("2026-08-25T18:00:02.000Z") }); + const events = ["started", "completed"].map((phase, index) => ({ + id: index + 1, + companyId: "company-1", + agentId: "agent-1", + stream: "system", + level: "info", + color: null, + message: null, + runId, + seq: index + 1, + eventType: `tool.execution.${phase}`, + createdAt: new Date(`2026-08-25T18:00:0${index + 1}.000Z`), + payload: { + prpEvent: { + schema: "paperclip.prp.event.v1", + schemaVersion: 1, + runId, + eventType: `tool.execution.${phase}`, + sourceEventId: `acpx-write-${index + 1}`, + sourceKind: "runner", + sourceInstanceId: "runner-1", + sourceSeq: index + 1, + normalizedSessionId: "session-1", + emittedAt: `2026-08-25T18:00:0${index + 1}.000Z`, + payload: { + schema: "paperclip.tool.execution.v1", + executionId: "write-plan", + transport: anchored ? "mcp" : "builtin", + namespace: anchored ? "paperclip" : null, + name, + operation: "execute", + readOnly: false, + status: phase === "completed" ? "completed" : "running", + output: null, + outputBytes: 0, + outputTruncated: false, + outputDigest: null, + }, + }, + }, + } satisfies HeartbeatRunEvent)); + // Exercise persisted ACP display events, without native raw arguments or + // a fabricated semantic-tool receipt, through the actual transcript parser. + nativeTranscriptState.transcriptByRun.set(runId, nativeRunEventsToTranscript(events)); + render( {}} issueStatus="blocked" linkedRuns={[{ + runId, + runtimeMode: "native", + status: "succeeded", + agentId: "agent-1", + agentName: "Runner", + adapterType: "paperclip_runner", + createdAt: "2026-08-25T18:00:00.000Z", + startedAt: "2026-08-25T18:00:00.000Z", + finishedAt: "2026-08-25T18:00:03.000Z", + }]} />); + const preview = container.querySelector('[data-testid="task-chat-plan-preview"]'); + const fallback = container.querySelector('[data-testid="task-chat-plan-preview-fallback"]'); + expect(Boolean(preview)).toBe(anchored); + expect(Boolean(fallback)).toBe(!anchored); + expect((preview ?? fallback)?.textContent).toContain("Preview the Plan"); + expect((preview ?? fallback)?.closest('[data-testid="task-chat-turn"][data-settled="true"]')).not.toBeNull(); + }); + it("retains a native Plan as a visible fallback while its write boundary is unavailable", () => { planState.data = planDocument({ updatedAt: new Date("2026-08-25T18:00:02.000Z"), diff --git a/ui/src/components/task-chat/QuestionForm.tsx b/ui/src/components/task-chat/QuestionForm.tsx index 8474299ad2..58b355317c 100644 --- a/ui/src/components/task-chat/QuestionForm.tsx +++ b/ui/src/components/task-chat/QuestionForm.tsx @@ -11,6 +11,7 @@ import type { PaperclipQuestionSet, } from "@paperclipai/adapter-utils"; import type { MentionOption } from "@/components/MarkdownEditor"; +import { MarkdownBody } from "@/components/MarkdownBody"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -515,9 +516,14 @@ export function QuestionForm({ }} > {questionSet.description ? ( -

- {questionSet.description} -

+
+ {questionSet.description} +
) : null} {question.answerMode === "text" ? (
diff --git a/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx b/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx index bb2fb87944..f9e956e2c4 100644 --- a/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx +++ b/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx @@ -178,7 +178,7 @@ describe("TaskChatProtocolActivityRow", () => { expect(row?.querySelector('[data-testid="task-chat-protocol-activity-icon"]')?.querySelectorAll("path")).toHaveLength(3); }); - it("shows a notice as a warning and full-width message without metadata or a disclosure", () => { + it("keeps a notice readable and preserves metadata in a collapsed disclosure", () => { const summary = "Project-local configuration is disabled.\nTrust the repository to load its hooks."; render({ id: "notice", kind: "protocol", surface: "provider_activity", family: "provider_notice", @@ -186,12 +186,26 @@ describe("TaskChatProtocolActivityRow", () => { details: [ { label: "Category", value: "configWarning" }, { label: "Recoverable", value: "Yes" }, + { label: "Provider", value: "pi" }, + { label: "Provenance", value: "provider_native" }, { label: "Summary", value: summary }, ], steps: [], links: [], children: [], }); - expect(container.textContent).toBe(`Warning${summary}`); + expect(container.textContent).toContain(`Warning${summary}`); expect(container.querySelector("p")?.textContent).toBe(summary); - expect(container.querySelector("dl")).toBeNull(); + const disclosure = container.querySelector("details"); + expect(disclosure).not.toBeNull(); + expect(disclosure?.open).toBe(false); + expect(disclosure?.querySelector("summary")?.textContent).toBe("Details"); + expect(Array.from(disclosure?.querySelectorAll("dt") ?? [], (term) => term.textContent)).toEqual([ + "Category", "Recoverable", "Provider", "Provenance", + ]); + expect(Array.from(disclosure?.querySelectorAll("dd") ?? [], (value) => value.textContent)).toEqual([ + "configWarning", "Yes", "pi", "provider_native", + ]); + expect(disclosure?.textContent).not.toContain(summary); + act(() => disclosure?.querySelector("summary")?.click()); + expect(disclosure?.open).toBe(true); expect(container.querySelector("button")).toBeNull(); expect(container.querySelector('[data-testid="task-chat-protocol-activity-icon"]')).not.toBeNull(); }); @@ -214,6 +228,7 @@ describe("TaskChatProtocolActivityRow", () => { const row = container.querySelector('[data-testid="task-chat-protocol-activity-row"]'); expect(row?.querySelector("button")).toBeNull(); + expect(row?.querySelector("details")).toBeNull(); expect(row?.querySelector('[aria-expanded]')).toBeNull(); }); }); diff --git a/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx b/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx index a1f3590a15..5440a4408e 100644 --- a/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx +++ b/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx @@ -288,9 +288,15 @@ export function TaskChatProtocolActivityRow({ item }: { item: TaskChatProtocolIt
- {item.status === "failed" ? "Error" : "Warning"} + {item.status === "failed" ? "Error" : item.details.find(detail => detail.label === "Severity")?.value === "info" ? "Provider update" : "Warning"}

{summary}

+ {item.details.some(detail => detail.label !== "Summary") ? ( +
+ Details + detail.label !== "Summary")} /> +
+ ) : null}
); } diff --git a/ui/src/components/task-chat/TaskChatProtocolCard.test.tsx b/ui/src/components/task-chat/TaskChatProtocolCard.test.tsx index b251462d59..00961fdbaa 100644 --- a/ui/src/components/task-chat/TaskChatProtocolCard.test.tsx +++ b/ui/src/components/task-chat/TaskChatProtocolCard.test.tsx @@ -12,7 +12,9 @@ import type { TaskChatProviderActivityFamily, TaskChatRuntimeRequestDecision, } from "./task-chat-model"; -import type { IssueWorkProduct } from "@paperclipai/shared"; +import type { HeartbeatRunEvent, IssueWorkProduct } from "@paperclipai/shared"; +import { nativeRunEventsToTranscript } from "../transcript/native-run-events"; +import { transcriptToTaskChatItems } from "./transcript-adapter"; import { IssueGalleryContext } from "@/context/IssueGalleryContext"; import { RichWorkProductCard } from "./RichWorkProductCard"; import { stateChipFor } from "./RichWorkProductCard"; @@ -539,6 +541,55 @@ describe("TaskChatProtocolCard", () => { expect(onDecision).toHaveBeenCalledWith({ action: "accept" }); }); + it("renders committed ACP permission choices and preserves its exact resolution binding", async () => { + const event: HeartbeatRunEvent = { + id: 1, seq: 1, companyId: "company-1", runId: "run-1", agentId: "agent-1", + eventType: "runtime_request.created", stream: "system", level: "info", color: null, message: null, + createdAt: new Date("2026-09-28T12:00:00Z"), + payload: { prpEvent: { + schema: "paperclip.prp.event.v1", schemaVersion: 1, sourceEventId: "permission-1", sourceSeq: 1, + sourceKind: "runner", sourceInstanceId: "runner-1", runId: "run-1", normalizedSessionId: "session-1", + turnId: "turn-1", itemId: "item-1", eventType: "runtime_request.created", priority: 0, + emittedAt: "2026-09-28T12:00:00Z", payload: { request: { + schema: "paperclip.runtime_request.v2", requestKind: "permission_approval", type: "permission", + requestId: "permission-1", turnId: "turn-1", itemId: "item-1", status: "pending", + prompt: "Allow editing src/example.ts?", + choices: [{ key: "accept", label: "Allow once" }, { key: "decline", label: "Deny" }], + details: { toolCallId: "tool-1" }, + origin: { adapter: "acpx-runtime-sidecar", provider: "acpx", method: "session/request_permission" }, + } }, + } }, + }; + const item = transcriptToTaskChatItems(nativeRunEventsToTranscript([event]), { + runId: "run-1", agentName: "ACP", running: true, + }).find(candidate => candidate.kind === "protocol" && candidate.surface === "runtime_request"); + if (item?.kind !== "protocol" || item.surface !== "runtime_request") throw new Error("permission card missing"); + expect(item).toMatchObject({ runId: "run-1", requestId: "permission-1", turnId: "turn-1", requestKind: "permission_approval", status: "pending" }); + expect(item.choices.map(choice => choice.key)).toEqual(["accept", "decline"]); + const resolve = vi.fn().mockResolvedValue(undefined); + flushSync(() => root.render( + + )); + expect(container.textContent).toContain("Allow editing src/example.ts?"); + const allow = Array.from(container.querySelectorAll("button")).find(button => button.textContent === "Allow once"); + expect(allow?.disabled).toBe(false); + expect(container.textContent).not.toContain("Allow for session"); + await act(async () => allow?.click()); + expect(resolve).toHaveBeenCalledExactlyOnceWith(item, { action: "accept" }); + const delivered: HeartbeatRunEvent = { ...event, id: 2, seq: 2, eventType: "runtime_request.resolved", payload: { prpEvent: { + ...(event.payload!.prpEvent as Record), sourceEventId: "permission-delivered", sourceSeq: 2, + eventType: "runtime_request.resolved", payload: { requestId: "permission-1", requestKind: "permission_approval", + turnId: "turn-1", itemId: "item-1", status: "delivered", action: "accept" }, + } } }; + const settled = transcriptToTaskChatItems(nativeRunEventsToTranscript([event, delivered]), { + runId: "run-1", agentName: "ACP", running: true, + }).find(candidate => candidate.kind === "protocol" && candidate.surface === "runtime_request"); + if (settled?.kind !== "protocol" || settled.surface !== "runtime_request") throw new Error("receipt missing"); + expect(settled).toMatchObject({ requestId: "permission-1", status: "resolved", resolvedAction: "accept" }); + renderCard(root, settled, resolve); + expect(Array.from(container.querySelectorAll("button")).find(button => button.textContent === "Allow once")).toBeUndefined(); + }); + it("submits structured runtime input through the production card", async () => { const onDecision = vi.fn().mockResolvedValue(undefined); renderCard( @@ -581,6 +632,35 @@ describe("TaskChatProtocolCard", () => { expect(container.textContent).toContain("Submitting…"); }); + it("keeps complete provider plan Markdown while making image references inert", () => { + const prefix = '# Release plan\n\n**Preserve every instruction.**\n\n![Evidence](https://provider.invalid/track.png "Provider image")\n\n![Local image](/api/attachments/untrusted/content)\n\n'; + const suffix = '\n\n```mermaid\nflowchart LR\n A@{ img: "https://provider.invalid/diagram.png" }\n```\n\nFINAL_PLAN_BOUNDARY'; + const fullInstructions = "x".repeat(100_000 - prefix.length - suffix.length); + const description = prefix + fullInstructions + suffix; + expect(description).toHaveLength(100_000); + renderCard(root, { + id: "provider-plan", kind: "protocol", surface: "runtime_request", runId: "run-1", + requestId: "plan-revision-7", requestKind: "runtime", turnId: "turn-1", + requestType: "input", status: "pending", prompt: "Review the complete plan.", choices: [], fields: [], + questionSet: { + schema: "paperclip.question_set.v1", description, + questions: [{ id: "decision", prompt: "Accept this plan?", required: true, answerMode: "single_select", + options: [{ id: "accept", label: "Accept" }, { id: "reject", label: "Reject" }] }], + }, + }); + + const context = container.querySelector('[role="region"][aria-label="Question context"]'); + expect(context?.querySelector("h1")?.textContent).toBe("Release plan"); + expect(context?.querySelector("strong")?.textContent).toBe("Preserve every instruction."); + expect(context?.textContent).toContain(fullInstructions); + expect(context?.textContent).toContain("FINAL_PLAN_BOUNDARY"); + expect(context?.textContent).toContain("Evidence"); + expect(context?.textContent).toContain("https://provider.invalid/track.png"); + expect(context?.querySelector("img, video, audio, iframe, object, embed, image, link")).toBeNull(); + expect(context?.querySelector('.language-mermaid')?.textContent).toContain('https://provider.invalid/diagram.png'); + expect(context?.querySelector('.paperclip-mermaid')).toBeNull(); + }); + it("submits the canonical response from a v2 harness question set", async () => { const onDecision = vi.fn().mockResolvedValue(undefined); renderCard( diff --git a/ui/src/components/task-chat/transcript-adapter.ts b/ui/src/components/task-chat/transcript-adapter.ts index 7f907c97b1..999cf904cb 100644 --- a/ui/src/components/task-chat/transcript-adapter.ts +++ b/ui/src/components/task-chat/transcript-adapter.ts @@ -384,6 +384,15 @@ function providerActivityItem( }); } + if (entry.family === "provider_notice" && Array.isArray(entry.payload.details)) { + for (const raw of entry.payload.details.slice(0, 64)) { + const detail = objectRecord(raw); + if (typeof detail.name === "string" && typeof detail.value === "string") { + details.push({ label: clip(detail.name, 160), value: clip(detail.value, 4000), mono: false }); + } + } + } + const steps = entry.family === "plan" && Array.isArray(entry.payload.steps) ? entry.payload.steps diff --git a/ui/src/components/transcript/RunTranscriptView.tsx b/ui/src/components/transcript/RunTranscriptView.tsx index 90131c495d..af8a05676b 100644 --- a/ui/src/components/transcript/RunTranscriptView.tsx +++ b/ui/src/components/transcript/RunTranscriptView.tsx @@ -879,6 +879,7 @@ function TranscriptProviderActivity({ block, density }: { block: Extract 0 ?
    {children.map((child, index) =>
  • {String(child.role ?? "Child agent")} · {String(child.status ?? "unknown")}
    {String(child.summary ?? "")}
  • )}
: null} {sources.length > 0 ?
    {sources.map((source, index) => { const url = typeof source.url === "string" && /^https?:\/\//.test(source.url) ? source.url : null; return
  • {url ? {String(source.title ?? url)} : String(source.title ?? "Unavailable source")} Provider-reported
  • ; })}
: null} {output ?
{output}
: null} + {block.family === "provider_notice" && Array.isArray(block.payload.details) ?
{block.payload.details.slice(0, 64).map((value, index) => { const detail = asRecord(value); return detail && typeof detail.name === "string" && typeof detail.value === "string" ?
{detail.name}
{detail.value}
: null; })}
: null} {block.family === "model_identity" ?
Requested {String(block.payload.requestedModel ?? "—")} · Effective {String(block.payload.effectiveModel ?? "—")}
: null}
: null}
; diff --git a/ui/src/components/transcript/native-run-events.ts b/ui/src/components/transcript/native-run-events.ts index 2f39dbdbc0..fcc4bb13e9 100644 --- a/ui/src/components/transcript/native-run-events.ts +++ b/ui/src/components/transcript/native-run-events.ts @@ -149,7 +149,12 @@ function runtimeRequestEntry(input: { const requestId = text(request.requestId) ?? text(input.payload.requestId); if (!requestId) return null; const suffix = input.eventType.split(".").at(-1); - const rawStatus = text(request.status) ?? suffix; + // The lifecycle event is authoritative. ACP delivery receipts carry + // status:"delivered" in their payload, which must not reopen a resolved + // request as pending just because it is not a UI lifecycle status. + const rawStatus = suffix === "resolved" || suffix === "expired" || suffix === "cancelled" + ? suffix + : text(request.status) ?? suffix; const resolvedAction = text(request.action) ?? text(input.payload.action) ?? input.previous?.resolvedAction diff --git a/ui/storybook/fixtures/evidence/rich-acp-browser-proof.darwin-arm64.json b/ui/storybook/fixtures/evidence/rich-acp-browser-proof.darwin-arm64.json new file mode 100644 index 0000000000..1e05619cdc --- /dev/null +++ b/ui/storybook/fixtures/evidence/rich-acp-browser-proof.darwin-arm64.json @@ -0,0 +1,147 @@ +{ + "schema": "paperclip.rich-acp-ui-browser-proof.v1", + "sourceRevision": "851c60a1fbc7c68990077204022295ed2c2c11a1", + "recordedAt": "2026-09-28T17:17:49.521920+00:00", + "environment": { + "server": "http://localhost:6007", + "browser": "Chrome via Codex computer-use", + "platform": "macOS ARM64", + "data": "production components and parser, in-memory fixtures", + "credentials": "none" + }, + "providerCalls": 0, + "paidProviderCalls": 0, + "spendUsd": 0, + "observedDefect": { + "path": "ui/src/adapters/paperclip-runner/index.ts", + "beforeDescriptionCharacters": 4000, + "canonicalDescriptionCharacters": 100000, + "fix": "Preserve valid complete input context; reject oversized context with an explicit notice. Render complete Markdown context in a keyboard-focusable scroll region." + }, + "nativeCursor": { + "normalizer": "normalizeCursorPlanRequest({toolCallId: rich-acp-cursor-plan, plan: fullPlan, todos: []})", + "revision": "eedc589b367de4f6366ff76090d93676adb1e2238d4c5af280ac88393e1adef3", + "requestId": "cursor-create-plan-revision-7", + "pipeline": [ + "paperclipRunnerUIAdapter", + "transcriptToTaskChatItems", + "TaskChatProtocolCard", + "QuestionForm" + ], + "exactAppendixDomCharacters": 99724, + "finalSentinelVisible": true, + "manuallyScrolledToEnd": true, + "manualApprovalReceiptVerified": true, + "fixtureSource": "Canonical runtime_request.created fixture matches normalizeCursorPlanRequest output; the browser does not launch Cursor or execute its normalizer.", + "normalizerReference": "packages/paperclip-runner/src/drivers/acpx/cursor-extensions.ts (Cursor provider branch)" + }, + "storybookPlays": [ + { + "storyId": "chat-comments-rich-acp-interactions--typed-questions-verified", + "status": "PASS" + }, + { + "storyId": "chat-comments-rich-acp-interactions--question-cancellation-verified", + "status": "PASS" + }, + { + "storyId": "chat-comments-rich-acp-interactions--complete-plan-accepted-verified", + "status": "PASS" + }, + { + "storyId": "chat-comments-rich-acp-interactions--plan-revision-cancelled-then-rejected-verified", + "status": "PASS" + }, + { + "storyId": "chat-comments-rich-acp-interactions--notice-details-verified", + "status": "PASS" + }, + { + "storyId": "chat-comments-rich-acp-interactions--native-cursor-plan-accepted-verified", + "status": "PASS" + }, + { + "storyId": "chat-comments-rich-acp-interactions--native-cursor-plan-rejected-verified", + "status": "PASS" + }, + { + "storyId": "chat-comments-rich-acp-interactions--native-cursor-plan-cancelled-verified", + "status": "PASS" + } + ], + "automatedChecks": { + "uiTypecheck": "passed", + "targetedVitest": { + "files": 3, + "tests": 46, + "status": "passed" + }, + "tokenGates": "passed" + }, + "limitations": [ + "No live provider process, persistence, control-plane reconnect, or Daytona browser flow was exercised.", + "Receipts prove UI payload identity only; provider and server enforcement are separate deterministic tests.", + "Native Cursor approval currently includes an optional rejection-reason page even for accept/cancel; no conditional question support is claimed.", + "Desktop viewport inspected; mobile was not exercised in this proof." + ], + "screenshots": [ + { + "file": "integer-validation.png", + "sha256": "53971cce4566412b58f895d022d59e2ff31ffe87505e42c9e296081d8b44b865" + }, + { + "file": "native-plan-accepted.png", + "sha256": "afc0c429e03a82e2310923448657c27312f8a2f0e768bfe85e09750d55b8f872" + }, + { + "file": "native-plan-before-truncation.png", + "sha256": "89961213c0465df9a782832dda6a021b4599a42249986ec84cf65f029ed976a9" + }, + { + "file": "native-plan-cancelled.png", + "sha256": "ccd70eb5faf35f723a2b33115e9b9589de492590af2ef1f9d3bb2c310556e620" + }, + { + "file": "native-plan-full-end.png", + "sha256": "598b9234850cff0debfd0520619055d5b0175e69255c26c4e1d2878dce58db40" + }, + { + "file": "native-plan-full-start.png", + "sha256": "8d6fd1885eb01cd2b1370ea9bf548eee082bffa882931bb7d9067364025a4af1" + }, + { + "file": "native-plan-manual-accepted.png", + "sha256": "397aad94f1924f26d2c8820e1726db4ac5fcc5206c87bf14007efaf7f2377750" + }, + { + "file": "native-plan-rejected.png", + "sha256": "22635bf2dce745dfa04ffd6d78f8814faaadc73b22be3856487b5922029fde0d" + }, + { + "file": "notice-details-readable.png", + "sha256": "69f8aa101a22f1bf53ae9a6cd144e064e1f5790f444d98e7ca8d4d7eea6e5758" + }, + { + "file": "notice-details.png", + "sha256": "8e8a6f8ee5a4700c04e996feec8bba2772c6e766e5c018610a7063bc44411de1" + }, + { + "file": "plan-accepted.png", + "sha256": "e50499360d059f78b5651f2b9bc6bcdfd56fa09a51a8fd51351c64d2cef8bba8" + }, + { + "file": "question-cancelled.png", + "sha256": "849035f58d700daf7f670d814ec3ea46e156306e32a48dd2d6f5f13ebd87af03" + }, + { + "file": "typed-questions-verified.png", + "sha256": "564d7aab62e24ee8a5ff6899b8be497b16c417b7a7794a42f655946bc955c899" + } + ], + "reproduce": { + "command": "pnpm --filter @paperclipai/ui storybook", + "storyFile": "ui/storybook/stories/rich-acp-interactions.stories.tsx", + "instructions": "Open each story ID listed below. Storybook play assertions run against production components. Inspect the Native Cursor plan pending story and scroll the Question context region to the final sentinel." + }, + "screenshotsStorage": "Screenshots are deliberately outside Git; file names and digests below identify the local captured evidence, not bundled repository assets." +} diff --git a/ui/storybook/stories/rich-acp-interactions.stories.tsx b/ui/storybook/stories/rich-acp-interactions.stories.tsx new file mode 100644 index 0000000000..5f17004f7d --- /dev/null +++ b/ui/storybook/stories/rich-acp-interactions.stories.tsx @@ -0,0 +1,321 @@ +import { useState, type ReactNode } from "react"; +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { expect, userEvent, waitFor, within } from "storybook/test"; +import type { PaperclipQuestionSet, TranscriptEntry } from "@paperclipai/adapter-utils"; +import { IssueThreadInteractionCard } from "@/components/IssueThreadInteractionCard"; +import { TaskChatCompactInteractionCard } from "@/components/task-chat/TaskChatCompactInteractionCard"; +import { TaskChatProtocolActivityRow } from "@/components/task-chat/TaskChatProtocolActivityRow"; +import { TaskChatProtocolCard } from "@/components/task-chat/TaskChatProtocolCard"; +import type { TaskChatRuntimeRequestDecision } from "@/components/task-chat/task-chat-model"; +import { paperclipRunnerUIAdapter } from "@/adapters/paperclip-runner"; +import { transcriptToTaskChatItems } from "@/components/task-chat/transcript-adapter"; +import { + issueThreadInteractionFixtureMeta, + pendingAskUserQuestionsInteraction, + pendingRequestConfirmationInteraction, +} from "@/fixtures/issueThreadInteractionFixtures"; +import type { + AskUserQuestionsAnswer, + AskUserQuestionsInteraction, + RequestConfirmationInteraction, +} from "@/lib/issue-thread-interactions"; +import { storybookAgentMap } from "../fixtures/paperclipData"; + +// Production components and production projection, with callbacks kept in memory. +// No provider, runner, or control-plane mutation is made by these stories. +function Frame({ children }: { children: ReactNode }) { + return
{children}
; +} + +const questionSet: PaperclipQuestionSet = { + schema: "paperclip.question_set.v1", + title: "Deployment details", + questions: [ + { + id: "regions", prompt: "Which deployment regions should be enabled?", required: true, + answerMode: "multi_select", + options: [ + { id: "provider-region-eu", label: "Production", description: "Europe — Frankfurt" }, + { id: "provider-region-us", label: "Production", description: "United States — Virginia" }, + { id: "provider-region-test", label: "Preview", description: "Temporary test environment" }, + ], + }, + { + id: "replicas", prompt: "How many replicas should run?", required: true, + helpText: "Enter a whole number from 1 to 8.", answerMode: "text", + textValidation: { inputType: "integer", minimum: 1, maximum: 8 }, + }, + { + id: "budget", prompt: "What is the hourly budget?", required: true, + helpText: "Enter a number from 0.5 to 25.", answerMode: "text", + textValidation: { inputType: "number", minimum: 0.5, maximum: 25 }, + }, + { + id: "enabled", prompt: "Enable health checks?", required: true, answerMode: "single_select", + options: [{ id: "true", label: "Yes" }, { id: "false", label: "No" }], + }, + { + id: "instructions", prompt: "What should the operator do during rollout?", required: true, + helpText: "Include both the verification and rollback instructions.", answerMode: "text", + textValidation: { inputType: "text", minLength: 12, maxLength: 500 }, + }, + ], +}; + +function Questions() { + const [interaction, setInteraction] = useState({ + ...pendingAskUserQuestionsInteraction, + id: "rich-acp-questions", title: questionSet.title!, + payload: { ...pendingAskUserQuestionsInteraction.payload, questionSet }, + }); + const [receipt, setReceipt] = useState(null); + return + { + setReceipt(answers); + setInteraction(current => ({ ...current, status: "answered", result: { version: 1, answers } })); + }} + onCancelInteraction={() => { + setReceipt("cancelled"); + setInteraction(current => ({ ...current, status: "cancelled", result: { version: 1, answers: [], cancellationReason: "Cancelled by the operator." } })); + }} + /> + {receipt == null ? "No answer submitted" : JSON.stringify(receipt, null, 2)} + ; +} + +const planStart = "# Roll out revision 7\n\nVerify each region before enabling traffic. Keep the rollback command available throughout deployment.\n\n## Verification appendix\n\n```text\n"; +const planEnd = "\n```\n\n## Final acceptance\n\nPLAN-END-100000: Keep the previous release available until the final health check passes."; +const appendixLine = "Check replica health, verify request counts, confirm recovery metrics, and record the result before continuing.\n"; +const appendixLength = 100_000 - planStart.length - planEnd.length; +const planAppendix = appendixLine.repeat(Math.ceil(appendixLength / appendixLine.length)).slice(0, appendixLength); +const fullPlan = planStart + planAppendix + planEnd; +const revisionId = "77777777-7777-4777-8777-777777777777"; + +// Cursor normalizeCursorPlanRequest({ toolCallId, plan: fullPlan, todos: [] }). +// This is the native runtime-question path, distinct from a synchronized plan document. +const cursorPlanRevision = "eedc589b367de4f6366ff76090d93676adb1e2238d4c5af280ac88393e1adef3"; +const cursorPlanQuestionId = `plan-${cursorPlanRevision}`; +const cursorPlanQuestions: PaperclipQuestionSet = { + schema: "paperclip.question_set.v1", title: "Review Cursor's plan", description: fullPlan, submitLabel: "Send decision", + questions: [ + { id: cursorPlanQuestionId, prompt: "How should Cursor proceed with this plan?", required: true, answerMode: "single_select", options: [ + { id: "accept", label: "Accept plan" }, { id: "reject", label: "Reject plan" }, { id: "cancel", label: "Cancel plan request" }, + ] }, + { id: "reason", prompt: "Reason for rejecting the plan (optional)", required: false, answerMode: "text", textValidation: { maxLength: 4_000 } }, + ], +}; + +function NativeCursorPlan() { + const [receipt, setReceipt] = useState<{ requestId: string; decision: TaskChatRuntimeRequestDecision } | null>(null); + const entries = paperclipRunnerUIAdapter.parseStdoutLine(JSON.stringify({ + type: "paperclip.prp.event", event: { eventType: "runtime_request.created", turnId: "cursor-plan-turn", payload: { + request: { requestId: "cursor-create-plan-revision-7", requestKind: "runtime", type: "input", status: "pending", prompt: "Review Cursor's plan", input: cursorPlanQuestions }, + } }, + }), "2026-09-28T12:00:00.000Z"); + const items = transcriptToTaskChatItems(entries, { runId: "cursor-native-plan", running: true }); + return +

Complete source: {fullPlan.length} characters

+
{items.map(item => item.kind === "protocol" && item.surface === "runtime_request" + ? setReceipt({ requestId: current.requestId, decision })} /> : null)}
+ {receipt == null ? "No decision submitted" : JSON.stringify(receipt, null, 2)} + ; +} + +function PlanReview() { + const [interaction, setInteraction] = useState({ + ...pendingRequestConfirmationInteraction, + id: "rich-acp-plan-r7", title: "Review deployment plan revision 7", + summary: "Review the complete plan before approving this revision.", + payload: { + ...pendingRequestConfirmationInteraction.payload, + detailsMarkdown: fullPlan, + target: { type: "issue_document", issueId: issueThreadInteractionFixtureMeta.issueId, key: "plan", revisionId, revisionNumber: 7 }, + }, + }); + const [receipt, setReceipt] = useState | null>(null); + function resolve(current: RequestConfirmationInteraction, outcome: "accepted" | "rejected", reason?: string) { + setReceipt({ interactionId: current.id, target: current.payload.target, outcome, ...(reason ? { reason } : {}) }); + setInteraction({ ...current, status: outcome, result: { version: 1, outcome, reason: reason ?? null } }); + } + return +

Complete source: {fullPlan.length} characters

+
+ resolve(current as RequestConfirmationInteraction, "accepted")} + onRejectInteraction={(current, reason) => resolve(current as RequestConfirmationInteraction, "rejected", reason)} /> +
+ {receipt == null ? "Pending revision 7" : JSON.stringify(receipt, null, 2)} + ; +} + +const notice: TranscriptEntry = { + kind: "provider_activity", ts: "2026-09-28T12:00:00.000Z", family: "provider_notice", + eventType: "provider.notice.recorded", title: "Provider notice", status: "informational", + summary: "The provider is retrying after a temporary capacity limit.", + payload: { + schema: "paperclip.provider.notice.v1", severity: "warning", category: "retry", scope: "turn", + recoverable: true, userActionable: false, summary: "The provider is retrying after a temporary capacity limit.", + details: [ + { name: "Provider", value: "pi / ACP" }, + { name: "Source event", value: "auto_retry_start" }, + { name: "Provenance", value: "Provider-reported; not a runner-verified result" }, + { name: "Retry attempt", value: "2 of 3" }, + { name: "Message", value: "Capacity is temporarily unavailable.\nRetry after 2 seconds.\nDETAIL-END" }, + ], + }, +}; + +function ProviderNotice() { + const items = transcriptToTaskChatItems([notice], { runId: "rich-acp-notice", running: false }); + return {items.map(item => item.kind === "protocol" && item.surface === "provider_activity" + ? : null)}; +} + +const meta = { + title: "Chat & Comments/Rich ACP interactions", + parameters: { layout: "fullscreen", docs: { description: { component: "Credential-free production UI fixtures. Callback receipts establish UI payload fidelity only; they do not claim provider execution or server persistence." } } }, +} satisfies Meta; +export default meta; +type Story = StoryObj; + +export const QuestionsPending: Story = { render: () => }; +export const TypedQuestionsVerified: Story = { + ...QuestionsPending, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByRole("checkbox", { name: /Europe/ })); + await userEvent.click(canvas.getByRole("checkbox", { name: /United States/ })); + await userEvent.click(canvas.getByRole("button", { name: "Next" })); + let input = within(await canvas.findByRole("group", { name: "How many replicas should run?" })).getByRole("textbox"); + await userEvent.type(input, "2.5"); + await expect(canvas.getByText("Enter a valid integer.")).toBeVisible(); + await expect(canvas.getByRole("button", { name: "Next" })).toBeDisabled(); + await userEvent.clear(input); + await userEvent.type(input, "9"); + await expect(canvas.getByText("Enter a value no greater than 8.")).toBeVisible(); + await userEvent.clear(input); + await userEvent.type(input, "3"); + await userEvent.click(canvas.getByRole("button", { name: "Next" })); + input = within(await canvas.findByRole("group", { name: "What is the hourly budget?" })).getByRole("textbox"); + await userEvent.type(input, "0.25"); + await expect(canvas.getByText("Enter a value of at least 0.5.")).toBeVisible(); + await userEvent.clear(input); + await userEvent.type(input, "2.75"); + await userEvent.click(canvas.getByRole("button", { name: "Next" })); + await userEvent.click(canvas.getByRole("radio", { name: "Yes" })); + await userEvent.click(canvas.getByRole("button", { name: "Next" })); + input = within(await canvas.findByRole("group", { name: "What should the operator do during rollout?" })).getByRole("textbox"); + await userEvent.type(input, "Verify health checks.{shift>}{enter}{/shift}Roll back if latency increases."); + await userEvent.click(canvas.getByRole("button", { name: "Submit answers" })); + await waitFor(() => expect(JSON.parse(canvas.getByTestId("question-receipt").textContent!)).toEqual([ + { questionId: "regions", optionIds: ["provider-region-eu", "provider-region-us"] }, + { questionId: "replicas", optionIds: [], otherText: "3" }, + { questionId: "budget", optionIds: [], otherText: "2.75" }, + { questionId: "enabled", optionIds: ["true"] }, + { questionId: "instructions", optionIds: [], otherText: "Verify health checks.\nRoll back if latency increases." }, + ])); + }, +}; + +export const QuestionCancellationVerified: Story = { + ...QuestionsPending, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByRole("button", { name: "Cancel" })); + await expect(canvas.getByTestId("question-receipt")).toHaveTextContent('"cancelled"'); + await expect(canvas.queryByRole("checkbox")).not.toBeInTheDocument(); + }, +}; + +export const CompletePlanPending: Story = { name: "Semantic plan document pending", render: () => }; +async function assertCompletePlan(canvasElement: HTMLElement) { + const canvas = within(canvasElement); + await expect(canvas.getByTestId("plan-source-length")).toHaveTextContent("100000 characters"); + const appendix = canvas.getByTestId("full-plan-review").querySelector("pre code"); + await expect(appendix).not.toBeNull(); + await expect(appendix!.textContent).toBe(planAppendix + "\n"); + // Markdown may link the task-like sentinel; inspect its complete paragraph. + await expect(canvas.getByText((_, element) => element?.tagName === "P" && element.textContent?.includes("PLAN-END-100000:") === true)).toBeVisible(); +} +export const CompletePlanAcceptedVerified: Story = { + ...CompletePlanPending, + name: "Semantic plan document accepted verified", + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await assertCompletePlan(canvasElement); + await userEvent.click(canvas.getByRole("button", { name: "Approve plan" })); + await waitFor(() => expect(JSON.parse(canvas.getByTestId("plan-receipt").textContent!)).toMatchObject({ + interactionId: "rich-acp-plan-r7", outcome: "accepted", target: { key: "plan", revisionId, revisionNumber: 7 }, + })); + }, +}; +export const PlanRevisionCancelledThenRejectedVerified: Story = { + ...CompletePlanPending, + name: "Semantic plan revision cancelled then rejected verified", + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await assertCompletePlan(canvasElement); + await userEvent.click(canvas.getByRole("button", { name: "Revise…" })); + await userEvent.click(canvas.getByRole("button", { name: "Send revision" })); + await expect(canvas.getByText("Add a note describing the changes you want.")).toBeVisible(); + await userEvent.click(canvas.getByRole("button", { name: "Cancel" })); + await expect(canvas.getByTestId("plan-receipt")).toHaveTextContent("Pending revision 7"); + await userEvent.click(canvas.getByRole("button", { name: "Revise…" })); + await userEvent.type(canvas.getByRole("textbox"), "Add a rollback verification in each region."); + await userEvent.click(canvas.getByRole("button", { name: "Send revision" })); + await waitFor(() => expect(JSON.parse(canvas.getByTestId("plan-receipt").textContent!)).toMatchObject({ + outcome: "rejected", reason: "Add a rollback verification in each region.", target: { key: "plan", revisionId, revisionNumber: 7 }, + })); + }, +}; + +export const NoticeDetails: Story = { render: () => }; +export const NoticeDetailsVerified: Story = { + ...NoticeDetails, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect(canvas.getByText("The provider is retrying after a temporary capacity limit.")).toBeVisible(); + await userEvent.click(canvas.getByText("Details", { exact: true })); + await expect(canvas.getByText("pi / ACP")).toBeVisible(); + await expect(canvas.getByText("auto_retry_start")).toBeVisible(); + await expect(canvas.getByText("Provider-reported; not a runner-verified result")).toBeVisible(); + await expect(canvas.getByText(/DETAIL-END/)).toBeVisible(); + }, +}; + +export const NativeCursorPlanPending: Story = { render: () => }; + +function nativePlanVerification(choice: "accept" | "reject" | "cancel"): Story["play"] { + return async ({ canvasElement }) => { + const canvas = within(canvasElement); + const context = canvas.getByRole("region", { name: "Question context" }); + await expect(context.querySelector("pre code")?.textContent).toBe(planAppendix + "\n"); + await expect(context.textContent).toContain("PLAN-END-100000:"); + const label = { accept: "Accept plan", reject: "Reject plan", cancel: "Cancel plan request" }[choice]; + await userEvent.click(canvas.getByRole("radio", { name: label })); + await userEvent.click(canvas.getByRole("button", { name: "Next" })); + if (choice === "reject") await userEvent.type(canvas.getByRole("textbox"), "Add per-region rollback checks."); + await userEvent.click(canvas.getByRole("button", { name: "Send decision" })); + await waitFor(() => expect(JSON.parse(canvas.getByTestId("native-plan-receipt").textContent!)).toMatchObject({ + requestId: "cursor-create-plan-revision-7", decision: { + action: "submit", response: { schema: "paperclip.question_response.v1", answers: { + [cursorPlanQuestionId]: { selectedOptionIds: [choice] }, + ...(choice === "reject" ? { reason: { text: "Add per-region rollback checks." } } : {}), + } }, + }, + })); + }; +} + +export const NativeCursorPlanAcceptedVerified: Story = { ...NativeCursorPlanPending, play: nativePlanVerification("accept") }; +export const NativeCursorPlanRejectedVerified: Story = { ...NativeCursorPlanPending, play: nativePlanVerification("reject") }; +export const NativeCursorPlanCancelledVerified: Story = { ...NativeCursorPlanPending, play: nativePlanVerification("cancel") };