From 83c6832ccbf1496d5a4802b8f031d0ce6e5dc7af Mon Sep 17 00:00:00 2001 From: Nicky Leach Date: Fri, 6 Nov 2026 07:33:59 -0800 Subject: [PATCH] fix(build): typecheck package, Playwright, and e2e test files in CI (#18839) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Runner, plugin, and Playwright tests verify how those agents run, connect, and appear in the board > - Most package tsconfigs exclude tests, and Vitest strips types, so a type error in those tests fails nothing > - The tests drifted to 803 real type errors, and some of those errors hid assertions that checked nothing > - paperclipai/paperclip#18739 and paperclipai/paperclip#18738 check the server test tree and four test projects, but not these files > - This pull request adds a required check for these test files, fixes every error, and fails CI if a test file is in no program > - The benefit is that a type error in any test file outside `server/src` now fails CI ## Linked Issues or Issue Description Refs #18739 Refs #18738 **What happened?** 517 test files were in no program that required CI checks. A type error in those files did not fail CI. **Expected behavior** CI fails on a type error in any test file. **Steps to reproduce** Add `export const value: number = "text";` to a test file under `packages/paperclip-runner/src` on master. CI stays green, because `tsconfig.json` excludes test files and Vitest strips types. **Measured gap** Each row is a program with its tests included, on one thread. File and error counts are for head `7d4b5e907a`. Memory peaks were measured before the two latest merges of master. | Group | Test files | Real errors | Peak | | --- | ---: | ---: | ---: | | `paperclip-runner` Node source | 290 | 644 | 1.09 GiB | | `paperclip-runner` surfaces | 9 | 12 | 0.42 GiB | | `paperclip-runner` devtools and examples | 14 | 0 | 0.68 GiB | | `sandbox-providers/kubernetes` | 21 | 65 | 0.44 GiB | | 8 other sandbox providers and `bridge-template` | 32 | 4 | 0.34 GiB | | `plugins/sdk` | 22 | 15 | 0.41 GiB | | `plugin-workspace-diff` | 5 | 0 | 0.37 GiB | | `plugin-orchestration-smoke-example` | 1 | 1 | 0.15 GiB | | `kv-demo-mcp-server` | 3 | 0 | 0.13 GiB | | `packages/db` scripts | 1 | 0 | 1.01 GiB | | 8 Playwright suites under `tests/` | 52 | 25 | 1.72 GiB | | `tests/runner-acceptance` | 3 | 0 | 0.58 GiB | | `tests/e2e`, through layer declarations | 63 | 37 | at most 1.59 GiB | | `ui` `AttentionInteractionResolver.test.tsx` | 1 | 0 | n/a | | **Total** | **517** | **803** | | 27 of the 803 errors came from master while this pull request was open. Each merge of master brought new test code that no check had read. Errors that were config errors, not real errors: - One TS2688 for each provider, because the root install does not install provider dependencies. - 9 in the Runner Node program (TS7016 and TS6059). - 2 in `tsconfig.browser.json`. TypeScript 7 removed `baseUrl`, so that config was invalid. - 213 in the Playwright suites (`@/*` aliases and Vite types). - About 60 in `tests/e2e` (import extensions and aliases). `packages/plugins/create-paperclip-plugin` has no gap. Its tsconfig excludes its test, but `cli/tsconfig.json` reaches that test through an import. `server/test-baselines` also has no gap, because `tests/lifecycle-baseline` reads it. ## What Changed - Added `scripts/typecheck-test-programs.mjs` and its tests. The `Typecheck + Release Registry` job runs it. It checks each program in `PROGRAMS` on one thread. - The script fails if a tracked `*.test.ts(x)` or `*.spec.ts(x)` file outside `server/src` is in no program. The compiler file list decides this, because `include` drops `foo.test.tsx` when `foo.test.ts` exists. - Added a `tsconfig.test.json` to each uncovered package, and `tests/tsconfig.playwright.json`. - Added `tests/e2e` to `PROJECTS` in `scripts/typecheck-test-projects.mjs`, with a `tsconfig.json` and a `tsconfig.layers.json`. - Added `scripts/typecheck-sandbox-providers`. It is a small workspace that installs provider SDKs from one lockfile. I did not add a lockfile inside each provider, because `scripts/build-standalone-public-packages.mjs` uses a frozen install when one exists, and that changes what a release resolves. - Corrected `packages/paperclip-runner/tsconfig.browser.json` for TypeScript 7. - Set `rootDir` to `..` in `packages/paperclip-runner/tsconfig.test.json`, because one Runner test imports from `packages/adapter-utils/src`. The program does not emit files, so this changes no build output. - Moved the Kubernetes provider from `vitest ^3.2.4` to `^4.1.8`, the version the other providers use. Vitest 3 requires `tinypool ^1.1.1`, which has two critical advisories (GHSA-85c8-ppgw-ccpr and GHSA-5gmw-xhrv-c9v3). The first patched version is 2.1.2. Its tests have the same result on both versions: 189 passed, and the same 12 `file-sync` tests failed. - Moved the Kubernetes provider from `zod ^3.24.2` to `^4.6.6`, and gave its two `z.record` calls a key schema. This is the only production change, and it stays in this pull request. See "Production change for review". - Renamed `AttentionInteractionResolver.test.ts` to `AttentionInteractionResolver.replace.test.ts`, so TypeScript reads the `.test.tsx` beside it. - Fixed all 803 errors, with changes to test files and test support only. The fix for `contracts/native-execution.test.ts` is the same as the fix in paperclipai/paperclip#18739, so those two changes merge cleanly. - Updated `doc/DEVELOPING.md`. This pull request does not edit `server/scripts/typecheck-layers.mjs`. paperclipai/paperclip#18739 and paperclipai/paperclip#18738 have both merged, and this branch includes them. ### Production change for review The new lockfile made dependency review flag `zod@3.25.76` as a high-severity advisory (GHSA-mrhx-c7xc-m3w5). The first patched version is 4.6.6, so 3.x has no fix. The published Kubernetes plugin uses zod to validate operator config. The zod 3 source does not compile against zod 4, so a typecheck-only override cannot fix this. The existing tests do not prove this change, because they also passed with the invalid one-argument `z.record`. So I parsed 45 inputs with the same schema on zod 3.25.76 and zod 4.6.7: | Result | Inputs | Detail | | --- | ---: | --- | | Identical | 23 | Includes defaults, valid config, and a `__proto__` key | | Same outcome, new error wording | 21 | For example, `Invalid url` is now `Invalid URL`. `validateConfig` shows this text to operators. | | Different outcome | 1 | `jobTtlSecondsAfterFinished: 2 ** 60` was valid and is now rejected. The new limit is `Number.MAX_SAFE_INTEGER`. | I did not test this against a real cluster. The decision is to keep this change in this pull request. Since then, paperclipai/paperclip#17364 has moved the rest of the repository to zod 4.6.x, so the Kubernetes plugin now matches it. ### Behavior change for review `reducedMotion` under `use` is not a Playwright option. It had no effect in 11 configs and 2 `test.use` calls, so those suites ran with `no-preference`. The options are now under `contextOptions`, so reduced motion now takes effect. `app-shell-design.spec.ts` asserts a real transition, so it now asks for `no-preference` itself. The baseline manifest has `snapshotCount: 0`, so this invalidates no published baseline. A frozen trial reference PNG can differ. ### Findings These are product questions. I did not change them, and I did not hide them with casts: 1. **`PrpEvent` and its schema disagree.** The TypeScript type in `src/protocol/replay-contract.ts` requires `normalizedSessionId`. The `required` list in `protocol/schemas/event.schema.json` does not include it. `validatePrpEvent` accepts events that the type forbids. 2. **`requestCheckboxConfirmation` is stricter than its runtime.** It uses the output type, so plugin authors must pass `continuationPolicy` and `allowDeclineReason`, although the schema gives them defaults. `z.input` would fix this. 3. **`RetainedRunnerdMaintenanceInput.backend.kind` is a free string.** The quarantine domain comes from `descriptor.kind`, but the release uses `proof.backend.kind`. If they differ, `completeRetainedNativeSessionCleanup` returns 0 and the quarantine stays. The production caller passes `"runner"` today, so this is not a live bug. 4. **`CodexHarnessSession.close(input?)` is optional, but the contract requires `{ reason }`.** These are tests that verified nothing. I corrected them, and they pass: - `backends/native-instruction-measurement.test.ts` filtered on `placement === "core"`, which is not a valid placement. It measured an empty tool list. It now measures 14 tools. - `drivers/acpx/codex-acpx-driver.test.ts` checked for `usage.updated`, which is not an event type. - The `hostFixture()` helper in `drivers/acpx/runtime-host.test.ts` ignored two overrides, so `expect(prepareSandbox).not.toHaveBeenCalled()` could not fail. - `live/live-session.test.ts` set `agent: "pi"` before a spread that overwrote it. - `native-distribution-integrity.test.ts` passed an AbortSignal where `openCommand` expects `{ signal }`. These are gaps that this pull request does not close: - **The root lockfile still has some old zod versions.** paperclipai/paperclip#17364 moved the direct zod dependencies to 4.6.x. `pnpm-lock.yaml` still resolves `zod@3.25.76`, `4.1.11`, and `4.4.3`, which are below the patched 4.6.6. `packages/paperclip-runner/scripts/pi-distribution/package.json` pins `zod` to `3.25.76`, and some agent SDKs resolve `4.4.3`. Dependency review does not flag them, because this pull request does not add them. - **Dead GitHub coverage.** `chat-adapters-ui-providers.spec.ts` returned early for GitHub, so four later `provider === "github"` branches never ran. I removed them. They included the private key file and paste match, and the reconnect flow. Nothing asserts those behaviors in that spec now. - **Provider tests do not run in CI.** The root `vitest.config.ts` lists only Daytona. The other 8 providers, `bridge-template` included, are typechecked now, but CI does not run their tests. - **One `packages/db` test never runs.** `scripts/clean-poisoned-claude-sessions.test.ts` is outside the `include` of `packages/db/vitest.config.ts`. - **`plugin-orchestration-smoke-example` does not collect.** `@paperclipai/shared` is not linked in its `node_modules`. It passes 3 of 3 with an alias. ## Verification Run these commands: ```sh node scripts/typecheck-test-programs.mjs --low-memory pnpm --filter @paperclipai/server typecheck node scripts/typecheck-test-projects.mjs --low-memory --reuse-declarations node --test scripts/typecheck-test-programs.test.mjs ``` Results on macOS (arm64), Node 26.12. CI uses Node 24, and its `Typecheck + Release Registry` job also passes. At head `7d4b5e907a`, the test programs, `tests/e2e`, and the script tests pass again with 0 errors. The times and peaks below come from the run before the two latest merges of master: | Check | Result | | --- | --- | | Test programs, `--low-memory` | Exit 0, 0 errors, 42 seconds, peak 1.78 GiB | | Server layers, low memory | Exit 0, 0 errors, 38 seconds, peak 3.41 GiB | | Test projects, including `tests/e2e` | Exit 0, 0 errors, 13 seconds, peak 1.59 GiB | | Script tests | 12 passed | | Injected type error in each of 23 places | Each one failed its program with TS2322 | | Coverage check | 0 unchecked files, 10 seconds | I did not run this in a 4 GiB container. The peaks above are `maximum resident set size` for the largest single process. **Runtime check of the fixes.** Types do not prove that a test still works, so I ran the suites before the two latest merges of master: | Suite | Result | | --- | --- | | Runner, full suite | 4,885 passed, 3 failed, 13 skipped | | Sandbox providers, 10 packages | 939 passed, 12 failed, 36 skipped | | Plugin SDK | 200 passed | | `kv-demo-mcp-server` | 12 passed | | Renamed UI test and its sibling | 4 passed | All 15 failures exist without these fixes or come from the local machine: - **3 Cursor Cloud timeouts.** `artifact-delivery.test.ts` has 1 and `cursor-cloud-driver.test.ts` has 2. They time out in a full local run, and all 133 tests in those files with `codex-security-config.test.ts` pass when run alone. They fail in the same way with those two files at their unedited versions. - **12 Kubernetes `file-sync.test.ts` failures.** They fail in the same way before and after the fixes. The first failure is that `/proc/self/fd` does not exist on macOS. After each of the two latest merges of master, I ran only the Runner test files that I changed: | Merge | Result | | --- | --- | | `885c249cb9`, 8 files | 534 passed | | `7d4b5e907a`, 7 files | 672 passed, 16 failed | The 16 failures are in the two `source-reconciliation` test files. They fail in the same way with master's unedited versions of those files. 15 of them fail with `ACPX sidecar exited at initialize`, so I think my local build of the Rust and sidecar binaries is out of date. I did not rebuild them to check. CI builds them from scratch, and all Runner jobs pass in CI at `7d4b5e907a`. **Not run:** - `tests/e2e` and the 8 Playwright suites need a server, so most of them have only compile checks. `playwright test --list` loads 308 tests from the main e2e config. - `packages/plugins/sandbox-providers/kubernetes/test/integration` needs a cluster, so it has only compile checks. - I did not run the full `pnpm test:run` or root `pnpm build`. Required CI runs them. **Reduced motion.** I ran 6 motion-sensitive Storybook specs in system Chrome with a temporary config, once with `reduce` and once with `no-preference`. Both runs had 45 passed and the same 5 failed. So reduced motion did not change those results. The 5 failures do not depend on motion. At least one comes from the browser, because system Chrome prints `oklch(55.6% 0 0)` where the test expects `oklch(0.556 0 0)`. I did not run the other Storybook specs. The `storybook-visual` label runs that workflow. ## Risks - **A new lockfile to maintain.** A change to a provider `package.json` now fails the typecheck job until someone refreshes `scripts/typecheck-sandbox-providers/pnpm-lock.yaml`. The failure message gives the command. - **The job has a new install.** The step downloads provider SDKs, so it depends on the npm registry. It uses `--frozen-lockfile` and `--ignore-scripts`. - **The local install replaces provider `node_modules`.** A contributor with a standalone provider install must reinstall that provider after running the script. - **Reduced motion can change Storybook results.** See "Behavior change for review". That workflow is not part of required CI. - **Kubernetes plugin validation changed.** A release of this plugin ships zod 4. Error wording changes, and very large integers are now rejected. See "Production change for review". - **Master moves fast.** Each merge of master brought new test code with type errors, 27 so far. After this merges, the check reports those errors in the pull request that adds them. ## Model Used - Provider: Anthropic Claude, through Claude Code - Model: Claude Opus 5.5 (`claude-opus-5-5`) - Capabilities: tool use, code execution, and subagents for the type fixes in separate files ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details. The app generated the branch name. - [ ] I have run tests locally and they pass. See the failures explained in Verification. - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- .github/workflows/pr-trusted.yml | 3 + doc/DEVELOPING.md | 34 + package.json | 2 +- packages/db/tsconfig.test.json | 8 + .../kv-demo-mcp-server/tsconfig.test.json | 7 + .../backends/harness-driver-backend.test.ts | 23 +- .../backends/native-backend-factory.test.ts | 27 +- .../native-instruction-measurement.test.ts | 4 +- .../src/browser/client.test.ts | 10 +- .../src/browser/transcript-model.test.ts | 8 +- .../catalog/protocol-action-contracts.test.ts | 7 +- .../catalog/semantic-action-catalog.test.ts | 2 +- .../src/cli/acpx-runtime-sidecar.test.ts | 21 +- .../src/cli/acpx-sidecar-input.test.ts | 12 +- .../src/cli/eval-session-contract.test.ts | 8 +- .../src/cli/opencode-proxy-input.test.ts | 12 +- .../local-provider-fault-matrix.test.ts | 6 +- .../src/contracts/completion-result.test.ts | 2 +- .../provider-trace-file-sink.test.ts | 2 +- .../src/contracts/question-set.test.ts | 2 +- .../cursor-cloud-runner-launch.test.ts | 12 +- .../durable-prp-control-plane.test.ts | 30 +- .../src/devtools/index.test.ts | 23 +- .../acpx/antigravity-acpx-runtime.test.ts | 1 + .../acpx/antigravity-admission.test.ts | 2 +- .../drivers/acpx/codex-acpx-driver.test.ts | 82 +- .../acpx/codex-runtime-adapter.test.ts | 71 +- .../drivers/acpx/copilot-runtime-host.test.ts | 2 +- .../drivers/acpx/cursor-extensions.test.ts | 5 +- .../acpx/cursor-permission-identity.test.ts | 3 +- .../acpx/cursor-sdk-interactions.test.ts | 1 + .../drivers/acpx/cursor-tool-evidence.test.ts | 6 +- .../acpx/grok-native-session-options.test.ts | 7 +- .../native-distribution-integrity.test.ts | 2 +- .../drivers/acpx/pi-runtime-extension.test.ts | 8 +- .../acpx/profile-compatibility.test.ts | 2 +- .../drivers/acpx/response-delivery.test.ts | 1 + .../src/drivers/acpx/runtime-host.test.ts | 40 +- .../codex/app-server-transport.test.ts | 5 +- ...-app-server-driver.context-control.test.ts | 2 +- .../codex-app-server-driver.events.test.ts | 4 +- .../codex-app-server-driver.lifecycle.test.ts | 7 +- ...app-server-driver.runtime-requests.test.ts | 8 +- ...app-server-driver.semantic-results.test.ts | 8 +- .../codex/codex-protocol-integrity.test.ts | 5 +- .../codex/codex-question-adapter.test.ts | 2 +- .../drivers/codex/codex-resume-usage.test.ts | 28 +- .../src/drivers/codex/codex-turn-diff.test.ts | 12 +- .../codex/prepared-context-transport.test.ts | 2 +- .../cursor-cloud/app-server-proxy.test.ts | 4 +- .../drivers/cursor-cloud/cloud-client.test.ts | 2 +- .../cursor-cloud/cursor-cloud-driver.test.ts | 106 +- .../drivers/dot/dot-harness-driver.test.ts | 2 +- .../drivers/dot/runnerd-dot-driver.test.ts | 8 +- .../drivers/muse/runnerd-muse-driver.test.ts | 18 +- .../opencode/mcp-bridge-diagnostics.test.ts | 8 +- .../opencode/opencode-server-driver.test.ts | 14 +- .../drivers/opencode/reasoning-mode.test.ts | 2 +- .../src/drivers/runner-tool-bridge.test.ts | 12 +- .../src/eval/workflow-evals.test.ts | 2 +- .../src/evals/native-execution.test.ts | 2 +- .../src/live/acpx-request-recovery.test.ts | 4 +- .../antigravity-candidate-startup.test.ts | 4 +- .../src/live/clean-room-server.test.ts | 28 +- .../src/live/live-session.test.ts | 33 +- .../runner-prepared-launch-workspace.test.ts | 4 +- .../src/live/runnerd-acpx-questions.test.ts | 2 +- ...runnerd-acpx-source-reconciliation.test.ts | 6 +- .../runnerd-codex-transport-adoption.test.ts | 8 +- ...odex-transport-attachment-recovery.test.ts | 18 +- .../runnerd-codex-transport-deadlines.test.ts | 2 +- ...runnerd-codex-transport-settlement.test.ts | 10 +- ...runnerd-codex-transport-suspension.test.ts | 5 +- ...rd-codex-transport-warm-attachment.test.ts | 12 +- .../src/live/runnerd-codex-transport.test.ts | 31 +- .../live/runnerd-detach-durability.test.ts | 6 +- .../runnerd-opencode-streaming-tools.test.ts | 4 +- .../live/runnerd-restart-continuation.test.ts | 4 +- .../src/live/runnerd-runtime-activity.test.ts | 4 +- .../live/runnerd-saved-continuation.test.ts | 2 +- .../runnerd-source-reconciliation.test.ts | 4 +- .../src/live/runnerd-staged-handoff.test.ts | 2 +- .../live/runnerd-trace-frame-index.test.ts | 11 +- .../live/runnerd-welcome.integration.test.ts | 2 +- .../live/scenario-chat-demo-server.test.ts | 2 +- .../src/live/turn-stream.test.ts | 20 +- ...ability-mock-control-plane-adapter.test.ts | 1 + .../live-console-demo-server.test.ts | 2 +- .../live-console-scripted-driver.test.ts | 1 + .../src/mock-core/local-runner.test.ts | 17 +- .../src/native-session-runtime.test.ts | 139 +- .../src/protocol-actions/case-data.test.ts | 2 +- .../collaboration-contracts.test.ts | 2 +- .../protocol-actions/core-contracts.test.ts | 2 +- .../src/protocol/permission-request.test.ts | 2 +- .../src/protocol/provider-descriptor.test.ts | 4 +- .../prp-v1-expressiveness-crosswalk.test.ts | 2 +- .../src/protocol/replay-contract.test.ts | 6 +- .../protocol/workspace-diff-events.test.ts | 4 +- .../src/react/public-contract.test.tsx | 4 +- .../tools/capability-semantic-tools.test.ts | 35 +- .../test/support/runnerd-transport.ts | 9 + .../paperclip-runner/tsconfig.browser.json | 11 +- .../tsconfig.browser.test.json | 15 + .../tsconfig.surfaces.test.json | 9 + packages/paperclip-runner/tsconfig.test.json | 17 + .../tests/plugin.spec.ts | 3 + .../tsconfig.test.json | 12 + .../sandbox-providers/boxd/tsconfig.test.json | 8 + .../bridge-template/tsconfig.test.json | 4 + .../cloudflare/tsconfig.test.json | 8 + .../createos/src/file-sync.test.ts | 4 +- .../createos/tsconfig.test.json | 8 + .../daytona/tsconfig.test.json | 8 + .../sandbox-providers/e2b/src/plugin.test.ts | 4 +- .../sandbox-providers/e2b/tsconfig.test.json | 8 + .../exe-dev/tsconfig.test.json | 8 + .../sandbox-providers/kubernetes/package.json | 4 +- .../kubernetes/src/adapter-registry.ts | 2 +- .../sandbox-providers/kubernetes/src/types.ts | 2 +- .../test/integration/end-to-end-run.test.ts | 6 +- .../test/unit/adapter-defaults.test.ts | 5 +- .../test/unit/cilium-network-policy.test.ts | 10 +- .../kubernetes/test/unit/manifest-views.ts | 98 + .../test/unit/network-policy.test.ts | 14 +- .../test/unit/pod-spec-builder.test.ts | 6 +- .../test/unit/sandbox-cr-builder.test.ts | 6 +- .../kubernetes/tsconfig.test.json | 9 + .../modal/tsconfig.test.json | 8 + .../novita/src/plugin.test.ts | 2 +- .../novita/tsconfig.test.json | 8 + .../plugins/sdk/tests/testing-actions.test.ts | 7 +- .../sdk/tests/testing-lifecycle.test.ts | 2 +- .../plugins/sdk/tests/worker-rpc-host.test.ts | 23 +- packages/plugins/sdk/tsconfig.test.json | 8 + .../typecheck-sandbox-providers/package.json | 8 + .../pnpm-lock.yaml | 4206 +++++++++++++++++ .../pnpm-workspace.yaml | 7 + scripts/typecheck-test-programs.mjs | 220 + scripts/typecheck-test-programs.test.mjs | 170 + scripts/typecheck-test-projects.mjs | 1 + tests/ai-connections-app/playwright.config.ts | 2 +- .../playwright.config.ts | 2 +- tests/e2e/acp-stop-continuation.spec.ts | 4 +- tests/e2e/agent-configuration.spec.ts | 8 +- tests/e2e/board-attachment-receipts.spec.ts | 5 +- tests/e2e/chat-adapters-ui-providers.spec.ts | 117 +- tests/e2e/chat-adapters-ui.shared.ts | 5 +- tests/e2e/company-context-refresh.spec.ts | 6 +- tests/e2e/connection-intents.spec.ts | 2 +- tests/e2e/execution-recovery/recovery.spec.ts | 2 +- .../in-feed-native/completion-stream.spec.ts | 2 +- tests/e2e/in-feed-native/connections.spec.ts | 2 +- tests/e2e/playwright-composer-stop.config.ts | 2 +- .../playwright-transcript-delivery.config.ts | 2 +- tests/e2e/tsconfig.json | 20 + tests/e2e/tsconfig.layers.json | 8 + .../issue-detail/issue-detail.perf.spec.ts | 16 +- .../perf/issue-detail/rich-loading-fixture.ts | 2 +- tests/perf/issue-detail/window-globals.d.ts | 9 + .../storybook-visual/agent-personas.config.ts | 2 +- tests/storybook-visual/agent-personas.spec.ts | 2 +- .../storybook-visual/app-shell-design.spec.ts | 1 + .../feature-gap-stories.config.ts | 2 +- tests/storybook-visual/fireflies-pr.config.ts | 2 +- .../imessage-photon.config.ts | 2 +- .../in-feed-connections.config.ts | 2 +- tests/storybook-visual/playwright.config.ts | 2 +- .../remote-mcp-connections.config.ts | 2 +- .../repository-skill-packages.config.ts | 2 +- .../task-list-reproduction-trial.config.ts | 2 +- .../task-sidebar-loading.spec.ts | 2 +- .../task-list-reproduction-trial.spec.ts | 2 +- .../typography-foundations.spec.ts | 2 +- tests/tsconfig.playwright.json | 28 + ...entionInteractionResolver.replace.test.ts} | 0 176 files changed, 5771 insertions(+), 680 deletions(-) create mode 100644 packages/db/tsconfig.test.json create mode 100644 packages/kv-demo-mcp-server/tsconfig.test.json create mode 100644 packages/paperclip-runner/tsconfig.browser.test.json create mode 100644 packages/paperclip-runner/tsconfig.surfaces.test.json create mode 100644 packages/paperclip-runner/tsconfig.test.json create mode 100644 packages/plugins/examples/plugin-orchestration-smoke-example/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/boxd/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/cloudflare/bridge-template/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/cloudflare/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/createos/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/daytona/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/e2b/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/exe-dev/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/kubernetes/test/unit/manifest-views.ts create mode 100644 packages/plugins/sandbox-providers/kubernetes/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/modal/tsconfig.test.json create mode 100644 packages/plugins/sandbox-providers/novita/tsconfig.test.json create mode 100644 packages/plugins/sdk/tsconfig.test.json create mode 100644 scripts/typecheck-sandbox-providers/package.json create mode 100644 scripts/typecheck-sandbox-providers/pnpm-lock.yaml create mode 100644 scripts/typecheck-sandbox-providers/pnpm-workspace.yaml create mode 100644 scripts/typecheck-test-programs.mjs create mode 100644 scripts/typecheck-test-programs.test.mjs create mode 100644 tests/e2e/tsconfig.json create mode 100644 tests/e2e/tsconfig.layers.json create mode 100644 tests/perf/issue-detail/window-globals.d.ts create mode 100644 tests/tsconfig.playwright.json rename ui/src/components/{AttentionInteractionResolver.test.ts => AttentionInteractionResolver.replace.test.ts} (100%) diff --git a/.github/workflows/pr-trusted.yml b/.github/workflows/pr-trusted.yml index 1231280351f..18246ba0de7 100644 --- a/.github/workflows/pr-trusted.yml +++ b/.github/workflows/pr-trusted.yml @@ -446,6 +446,9 @@ jobs: - name: Typecheck workspaces whose build scripts skip TypeScript run: pnpm run typecheck:build-gaps + - name: Typecheck test files that no build reads + run: node scripts/typecheck-test-programs.mjs --low-memory + - name: Typecheck the server in low-memory layers env: PAPERCLIP_TYPECHECK_LOW_MEMORY: "1" diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 054b8317510..5704355c9fc 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -1319,6 +1319,40 @@ that file, and it fails when a referenced config does not exist yet. The program. That program peaks at about 15 GiB at default settings and about 8 GiB on one thread. +#### Typecheck test files outside the build + +Most package tsconfigs exclude tests, and Vitest strips types, so a type error in +one of those tests fails nothing. `scripts/typecheck-test-programs.mjs` reads them +through separate programs, usually a `tsconfig.test.json` beside the package's +`tsconfig.json`: + +```sh +node scripts/typecheck-test-programs.mjs --low-memory +node scripts/typecheck-test-programs.mjs packages/paperclip-runner/tsconfig.test.json +``` + +The first form runs in the required `Typecheck + Release Registry` job. It checks +one program at a time and the largest peaks below 2 GiB. It then fails if a tracked +`*.test.ts(x)` or `*.spec.ts(x)` file is in no program. The compiler's file list +decides that, because `include` silently drops `foo.test.tsx` when `foo.test.ts` +exists. + +To add a test directory, add it to the `include` of the package's +`tsconfig.test.json`. To add a package, add a program to `PROGRAMS` in the script. + +Sandbox providers are outside the root workspace, so the root install has none of +their SDKs. `scripts/typecheck-sandbox-providers` is a small workspace that lists +them and holds one lockfile. Its install replaces any standalone `node_modules` in +those providers. After changing a provider's `package.json`, refresh that lockfile: + +```sh +pnpm -C scripts/typecheck-sandbox-providers install --ignore-scripts --lockfile-only +``` + +Do not commit a lockfile inside a provider. +`scripts/build-standalone-public-packages.mjs` uses a frozen install when one +exists, which changes what a release resolves. + #### Reused Runner build in the server build The server build vendors `packages/paperclip-runner/dist`, so its diff --git a/package.json b/package.json index d05ed21d7ad..8ea83e18e59 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "smoke:notion-generic-live": "node scripts/smoke/notion-generic-live.mjs", "smoke:posthog-live": "node scripts/smoke/posthog-live.mjs", "smoke:terminal-bench-loop-skill": "node scripts/smoke/terminal-bench-loop-skill-smoke.mjs", - "test:release-registry": "node scripts/ci-profile/node-test.mjs scripts/ci-profile/reporters.test.mjs scripts/__tests__/canary-startup-playwright.test.mjs scripts/__tests__/cursor-public-install-sandbox.test.mjs scripts/__tests__/grok-public-install-sandbox.test.mjs scripts/__tests__/public-install-closure.test.mjs scripts/__tests__/public-install-registry.test.mjs scripts/__tests__/prefetch-child.test.mjs scripts/__tests__/stage-bundled-packages.test.mjs scripts/__tests__/pack-staged-packages.test.mjs scripts/__tests__/retain-runner-qualification-packages.test.mjs scripts/__tests__/runner-release-crate-cache.test.mjs scripts/__tests__/runner-debug-crate-cache.test.mjs scripts/__tests__/vitest-worker-isolation.test.mjs scripts/verify-release-registry-state.test.mjs scripts/release-package-map.test.mjs scripts/check-release-package-bootstrap.test.mjs scripts/check-no-git-push.test.mjs scripts/release-lib.test.mjs scripts/release-registry-versions.test.mjs scripts/link-plugin-dev-sdk.test.js scripts/acpx-patch-packaging.test.mjs scripts/service-onboard-smoke.test.mjs scripts/docker-onboard-smoke.test.mjs scripts/preview-artifacts.test.mjs scripts/cloud-migrator-artifacts.test.mjs scripts/__tests__/local-verify.test.mjs", + "test:release-registry": "node scripts/ci-profile/node-test.mjs scripts/ci-profile/reporters.test.mjs scripts/__tests__/canary-startup-playwright.test.mjs scripts/__tests__/cursor-public-install-sandbox.test.mjs scripts/__tests__/grok-public-install-sandbox.test.mjs scripts/__tests__/public-install-closure.test.mjs scripts/__tests__/public-install-registry.test.mjs scripts/__tests__/prefetch-child.test.mjs scripts/__tests__/stage-bundled-packages.test.mjs scripts/__tests__/pack-staged-packages.test.mjs scripts/__tests__/retain-runner-qualification-packages.test.mjs scripts/__tests__/runner-release-crate-cache.test.mjs scripts/__tests__/runner-debug-crate-cache.test.mjs scripts/__tests__/vitest-worker-isolation.test.mjs scripts/verify-release-registry-state.test.mjs scripts/release-package-map.test.mjs scripts/check-release-package-bootstrap.test.mjs scripts/check-no-git-push.test.mjs scripts/release-lib.test.mjs scripts/release-registry-versions.test.mjs scripts/link-plugin-dev-sdk.test.js scripts/acpx-patch-packaging.test.mjs scripts/service-onboard-smoke.test.mjs scripts/docker-onboard-smoke.test.mjs scripts/preview-artifacts.test.mjs scripts/cloud-migrator-artifacts.test.mjs scripts/typecheck-test-programs.test.mjs scripts/__tests__/local-verify.test.mjs", "storybook-visual:baseline": "node scripts/storybook-visual-baseline.mjs", "test:storybook-visual": "node scripts/storybook-visual-baseline.mjs download && node scripts/storybook-visual-baseline.mjs verify && pnpm build-storybook && npx playwright test --config tests/storybook-visual/playwright.config.ts", "test:storybook-visual:update": "node scripts/storybook-visual-baseline.mjs download && pnpm build-storybook && npx playwright test --config tests/storybook-visual/playwright.config.ts --update-snapshots && node scripts/storybook-visual-baseline.mjs pack", diff --git a/packages/db/tsconfig.test.json b/packages/db/tsconfig.test.json new file mode 100644 index 00000000000..0308a876ded --- /dev/null +++ b/packages/db/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "." + }, + "include": ["scripts/**/*.test.ts"] +} diff --git a/packages/kv-demo-mcp-server/tsconfig.test.json b/packages/kv-demo-mcp-server/tsconfig.test.json new file mode 100644 index 00000000000..1e36832b11d --- /dev/null +++ b/packages/kv-demo-mcp-server/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "exclude": [] +} 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 6791372425a..bc1193167e7 100644 --- a/packages/paperclip-runner/src/backends/harness-driver-backend.test.ts +++ b/packages/paperclip-runner/src/backends/harness-driver-backend.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from "vitest"; import type { HarnessDriver, HarnessSession, PersistedHarnessSession } from "../contracts/harness-driver.js"; import { PROVIDER_STATE_INCOMPATIBLE, runnerRecoveryFailureClass } from "../contracts/harness-driver.js"; import type { PrpEvent, PrpStructuredRunResult, PrpTerminalState } from "../protocol/replay-contract.js"; -import { NativeSessionProtocolIntegrityError } from "../contracts/native-session-backend.js"; +import { NativeSessionProtocolIntegrityError, type NativeSession } from "../contracts/native-session-backend.js"; import { HarnessDriverBackend } from "./harness-driver-backend.js"; const result: PrpStructuredRunResult = { @@ -55,8 +55,13 @@ function prpEvent(sourceSeq: number, eventType: PrpEvent["eventType"], payload: const runtimeResolutions: unknown[] = []; +function cancelSession(session: NativeSession, input: Parameters>[0]) { + if (!session.cancel) throw new Error("backend session does not support cancellation"); + return session.cancel(input); +} + class FakeHarnessSession implements HarnessSession { - ids() { return { driverSessionId: "driver-1", providerSessionId: "provider-1" }; } + ids(): ReturnType { return { driverSessionId: "driver-1", providerSessionId: "provider-1" }; } async *events() { yield prpEvent(1, "run.result.proposed", result); yield prpEvent(2, "turn.completed", { status: "completed" }); @@ -942,7 +947,7 @@ describe("HarnessDriverBackend", () => { ); class TerminalThenIntegrityFailure extends FakeHarnessSession { goal = vi.fn(async () => null); - steer = vi.fn(async () => ({ correlationId: "blocked-steer" })); + steer = vi.fn(async () => {}); override async *events() { yield* super.events(); throw fault; @@ -1068,8 +1073,8 @@ describe("HarnessDriverBackend", () => { const session = await backend.openSession({ identity: { runId: "run-1", sessionId: "session-1", companyId: "company-1", issueId: "issue-1", agentId: "agent-1" }, workingDirectory: "/workspace", }); - if (journalOnly) session.revokeTurnPublication(); - else await session.cancel({ reason: "durable governed wait", signal: new AbortController().signal }).cleanup; + if (journalOnly) session.revokeTurnPublication!(); + else await cancelSession(session, { reason: "durable governed wait", signal: new AbortController().signal }).cleanup; const events = []; for await (const event of session.events()) events.push(event); expect(events.filter(event => event.payload.kind === "agentMessage")).toHaveLength(0); @@ -1085,7 +1090,7 @@ describe("HarnessDriverBackend", () => { identity: { runId: "run-1", sessionId: "session-1", companyId: "company-1", issueId: "issue-1", agentId: "agent-1" }, workingDirectory: "/workspace", }); - await session.cancel({ reason: "user stop during startup", signal: new AbortController().signal }).cleanup; + await cancelSession(session, { reason: "user stop during startup", signal: new AbortController().signal }).cleanup; await expect(session.startTurn({ message: { role: "user", text: "cancelled work" } })) .rejects.toThrow("native_session_cancelled"); expect(start).not.toHaveBeenCalled(); @@ -1108,7 +1113,7 @@ describe("HarnessDriverBackend", () => { }); const iterator = session.events()[Symbol.asyncIterator](); await iterator.next(); - await session.cancel({ reason: "operator stop", signal: new AbortController().signal }).cleanup; + await cancelSession(session, { reason: "operator stop", signal: new AbortController().signal }).cleanup; await expect(iterator.next()).resolves.toMatchObject({ value: { eventType: "runtime_request.cancelled", sourceSeq: 3 } }); await expect(iterator.next()).resolves.toMatchObject({ value: { eventType: "turn.failed", sourceSeq: 4 } }); await expect(iterator.next()).resolves.toMatchObject({ done: true }); @@ -1133,7 +1138,7 @@ describe("HarnessDriverBackend", () => { }); const iterator = session.events()[Symbol.asyncIterator](); await iterator.next(); - await expect(session.cancel({ reason: "operator stop", signal: new AbortController().signal }).cleanup).rejects.toThrow("no active provider turn"); + await expect(cancelSession(session, { reason: "operator stop", signal: new AbortController().signal }).cleanup).rejects.toThrow("no active provider turn"); await expect(iterator.next()).resolves.toMatchObject({ value: { eventType: "turn.cancelled", sourceSeq: 2, payload: { status: "cancelled", providerTerminalState: "completed", reason: "cancelled_after_provider_completed" } } }); await expect(iterator.next()).resolves.toMatchObject({ value: { eventType: "run.terminal", sourceSeq: 3, @@ -1166,7 +1171,7 @@ describe("HarnessDriverBackend", () => { }); const iterator = session.events()[Symbol.asyncIterator](); await expect(iterator.next()).resolves.toMatchObject({ value: { eventType: "runtime_request.created" } }); - await session.cancel({ + await cancelSession(session, { reason: "operator cancelled the run", signal: new AbortController().signal, }).cleanup; 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 1b551e20ccf..a0cf6f0e9b5 100644 --- a/packages/paperclip-runner/src/backends/native-backend-factory.test.ts +++ b/packages/paperclip-runner/src/backends/native-backend-factory.test.ts @@ -5,7 +5,7 @@ import { join } from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import * as codexSecurity from "../drivers/codex/codex-security-config.js"; -import { buildNativeModelEnvelope, parseNativeExecutionInput, type NativeExecutionInput } from "../contracts/native-execution.js"; +import { buildNativeModelEnvelope, parseNativeExecutionInput, type NativeExecutionInput, type NativeExecutionInputV1, type NativeExecutionInputV2 } from "../contracts/native-execution.js"; import { NATIVE_RUNTIME_ASSET_SCHEMA, PAPERCLIP_EXECUTION_PROMPT, PAPERCLIP_EXECUTION_PROMPT_REVISION, nativeRuntimePromptDigest, canonicalNativeRuntimeContextDigest, composeNativeSystemInstructions } from "../contracts/runtime-context.js"; const contextRoots: string[] = []; @@ -36,12 +36,12 @@ import { createCodexNativeSessionBackend, createRunnerdNativeSessionBackend } fr import { createOpenCodeNativeSessionBackend } from "./opencode-native-backend.js"; function execution( - provider: NativeExecutionInput["provider"] = { + provider: NativeExecutionInputV1["provider"] = { kind: "codex", model: null, approvalPolicy: "never", }, -): NativeExecutionInput { +): NativeExecutionInputV1 { return { schema: "paperclip.native-execution-input.v1", binding: { @@ -88,7 +88,7 @@ function execution( function acpxExecution( agent: "codex" | "pi" | "claude" = "codex", -): NativeExecutionInput { +): NativeExecutionInputV1 { return { ...execution(), session: { @@ -107,7 +107,7 @@ function acpxExecution( }; } -function opencodeExecution(): NativeExecutionInput { +function opencodeExecution(): NativeExecutionInputV1 { return { ...execution(), session: { @@ -124,7 +124,7 @@ function opencodeExecution(): NativeExecutionInput { }; } -function planningExecution(input: NativeExecutionInput): NativeExecutionInput { +function planningExecution(input: NativeExecutionInputV1): NativeExecutionInputV2 { return { ...input, schema: "paperclip.native-execution-input.v2", @@ -143,7 +143,7 @@ function planningExecution(input: NativeExecutionInput): NativeExecutionInput { function managedExecution( kind: "claude_managed" | "aws_agentcore", -): NativeExecutionInput { +): NativeExecutionInputV1 { if (kind === "claude_managed") { return { ...execution(), @@ -223,7 +223,7 @@ describe("native backend factory", () => { }); try { const backend = createNativeSessionBackend(input, { environment: {} }); - await expect(backend.openSession({ identity: { runId: "run", sessionId: "session", companyId: "company", agentId: "agent" }, + await expect(backend.openSession({ identity: { runId: "run", sessionId: "session", companyId: "company", issueId: "issue", agentId: "agent" }, workingDirectory: WORKSPACE })).rejects.toThrow("offline-stop-before-process-spawn"); expect(generated).toBeDefined(); if (webSearch) expect(generated?.slice(-3)).toEqual(["-c", 'web_search="disabled"', "app-server"]); @@ -404,7 +404,7 @@ describe("native backend factory", () => { expect(recovered).toMatchObject({ recovered: true }); expect(factory).toHaveBeenCalledWith(expect.objectContaining({ baseInstructions: retainedSystemInstructions })); expect(transport.calls.some(call => call.method === "thread/start" || call.method === "turn/start")).toBe(false); - if (recovered.recovered) await recovered.session.close({ reason: "test complete" }); + if (recovered.recovered) await recovered.session!.close({ reason: "test complete" }); }); describe("runner-prepared launch working directory", () => { @@ -420,7 +420,7 @@ describe("native backend factory", () => { return response; } } - const identity = { runId: "run", sessionId: "session", companyId: "company", agentId: "agent" }; + const identity = { runId: "run", sessionId: "session", companyId: "company", issueId: "issue", agentId: "agent" }; const providers = [ ["Codex", execution()], ["OpenCode", opencodeExecution()], @@ -452,8 +452,8 @@ describe("native backend factory", () => { }).recoverSession!({ ...snapshot, activeTurnId: null }, { signal: new AbortController().signal }); expect(recovered).toMatchObject({ recovered: true }); if (recovered.recovered) { - expect((await recovered.session.snapshot()).workingDirectory).toBe(cwd); - await recovered.session.close({ reason: "test complete" }); + expect((await recovered.session!.snapshot()).workingDirectory).toBe(cwd); + await recovered.session!.close({ reason: "test complete" }); } }); @@ -769,6 +769,7 @@ describe("native backend factory", () => { await backend.openSession({ identity: { companyId: "company", + issueId: "issue", agentId: "agent", runId: "run", sessionId: "session", @@ -807,7 +808,7 @@ describe("native backend factory", () => { environment: { PAPERCLIP_WORKSPACE_CWD: WORKSPACE }, }); const session = await backend.openSession({ - identity: { companyId: "company", agentId: "agent", runId: "run", sessionId: "session" }, + identity: { companyId: "company", issueId: "issue", agentId: "agent", runId: "run", sessionId: "session" }, workingDirectory: WORKSPACE, }); const first = JSON.stringify(buildNativeModelEnvelope(input)); diff --git a/packages/paperclip-runner/src/backends/native-instruction-measurement.test.ts b/packages/paperclip-runner/src/backends/native-instruction-measurement.test.ts index 2c5592af47e..ec1f00ec4bb 100644 --- a/packages/paperclip-runner/src/backends/native-instruction-measurement.test.ts +++ b/packages/paperclip-runner/src/backends/native-instruction-measurement.test.ts @@ -21,7 +21,7 @@ import { inspectNativeCompletionSourceMetadata } from "../../../../tests/runner- // It measures complete Paperclip-supplied instruction/tool/message projections, // not provider-owned stock prompts, tokenization, billing or model behavior. const tools = PAPERCLIP_SEMANTIC_ACTION_CATALOG - .filter(tool => tool.placement === "core") + .filter(tool => tool.placement === "always") .map(tool => ({ name: tool.operationId, description: tool.description, inputSchema: tool.inputSchema })); const identity = { companyId: "company", agentId: "agent", issueId: "issue", runId: "run", sessionId: "session" }; const { qualificationModel: _qualificationModel, reportedModelId: _reportedModelId, permissionPolicy: _permissionPolicy, ...profile } = resolveQualifiedAcpxProfile("claude", "claude-sonnet-5"); @@ -182,7 +182,7 @@ describe("direct OpenCode HTTP instruction boundary", () => { try { const checkpoint = await session.snapshot(); const send = async (current: typeof session, phase: string, value: NativeExecutionInput) => { - const envelope = buildNativeModelEnvelope(value, { resumedSession: phase === "continuation" }); + const envelope = phase === "continuation" ? buildNativeModelEnvelope(value, { resumedSession: true }) : buildNativeModelEnvelope(value); if ("requestedSkills" in envelope && backend.preparedTaskConstraints) envelope.constraints = [...backend.preparedTaskConstraints]; await current.startTurn({ message: { role: "user", text: JSON.stringify(envelope) } }); for await (const event of current.events()) { if (event.eventType === "turn.completed") break; if (["turn.failed", "turn.cancelled"].includes(event.eventType)) throw new Error(`Fixture turn failed: ${event.eventType}`); } diff --git a/packages/paperclip-runner/src/browser/client.test.ts b/packages/paperclip-runner/src/browser/client.test.ts index 27795f4dd78..99fdef4fead 100644 --- a/packages/paperclip-runner/src/browser/client.test.ts +++ b/packages/paperclip-runner/src/browser/client.test.ts @@ -54,16 +54,20 @@ describe("public runner browser client", () => { fetchImpl: vi.fn() as unknown as typeof fetch, eventSourceFactory: (nextUrl) => { url = nextUrl; return source; }, }); - const event = { + const event: PrpEvent = { schema: "paperclip.prp.event.v1", sourceEventId: "event-9", - sourceKind: "harness", + sourceKind: "runner", sourceInstanceId: "driver", sourceSeq: 9, + runId: "run-1", + normalizedSessionId: "session/id", + schemaVersion: 1, + priority: 2, emittedAt: "2026-08-08T00:00:00.000Z", eventType: "item.delta", payload: { text: "exact" }, - } as PrpEvent; + }; const onEvent = vi.fn(); const onError = vi.fn(); const handle = client.openEventStream("session/id", 8, { onEvent, onError }); diff --git a/packages/paperclip-runner/src/browser/transcript-model.test.ts b/packages/paperclip-runner/src/browser/transcript-model.test.ts index 2de5010907c..b2fd25af8a2 100644 --- a/packages/paperclip-runner/src/browser/transcript-model.test.ts +++ b/packages/paperclip-runner/src/browser/transcript-model.test.ts @@ -1,17 +1,17 @@ import { describe, expect, it } from "vitest"; -import { LiveConsoleScriptedDriver } from "../mock-core/live-console-scripted-driver"; +import { LiveConsoleScriptedDriver } from "../mock-core/live-console-scripted-driver.js"; import { parseHarnessRuntimeRequestResolution, type HarnessSession, -} from "../contracts/harness-driver"; -import type { PrpEvent } from "../protocol/replay-contract"; +} from "../contracts/harness-driver.js"; +import type { PrpEvent } from "../protocol/replay-contract.js"; import { applyPrpEvent, createSessionSnapshotFromMetadata, reduceSessionEvents, type SessionSnapshot, -} from "../reducer/session-reducer"; +} from "../reducer/session-reducer.js"; import type { RunnerCapabilities } from "./protocol.js"; import { buildTranscript, diff --git a/packages/paperclip-runner/src/catalog/protocol-action-contracts.test.ts b/packages/paperclip-runner/src/catalog/protocol-action-contracts.test.ts index 2ae21e152d5..c399e302439 100644 --- a/packages/paperclip-runner/src/catalog/protocol-action-contracts.test.ts +++ b/packages/paperclip-runner/src/catalog/protocol-action-contracts.test.ts @@ -1,4 +1,4 @@ -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { PAPERCLIP_PROTOCOL_ACTIONS } from "../protocol-actions/index.js"; @@ -50,8 +50,9 @@ describe("canonical Paperclip protocol action contracts", () => { ).toEqual([]); if (action.scenario.descriptor.idempotency === "required") { expect(action.examples).toHaveProperty("scenarioCall"); - expect(scenarioCall.idempotencyKey).toEqual(expect.any(String)); - expect(scenarioCall.idempotencyKey).not.toHaveLength(0); + const { idempotencyKey } = scenarioCall as { idempotencyKey?: string }; + expect(idempotencyKey).toEqual(expect.any(String)); + expect(idempotencyKey).not.toHaveLength(0); } else { expect(scenarioCall).not.toHaveProperty("idempotencyKey"); } diff --git a/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts b/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts index 992cf522183..afec800cdba 100644 --- a/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts +++ b/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts @@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { createTaskAction } from "../protocol-actions/create-task.js"; import { createProjectAction } from "../protocol-actions/create-project.js"; 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 90d49bb600c..676dabac7cb 100644 --- a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts +++ b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts @@ -449,7 +449,7 @@ describe("qualified ACPX runtime sidecar", () => { // The receipt hashes the actual emitted input, not a second normalization. await loadActualToolCommands(tools).resolve({ callId: "3", turnId: "test-turn", result: { accepted: true }, error: null }); } - const body = await (await response).json(); + const body = await (await response).json() as ToolCallResponseBody; const receipt = readNativeSemanticReceipt({ contents: body.result.content }); expect(receipt).toMatchObject({ schema: "paperclip.semantic_tool_receipt.v2", inputSha256: semanticInputSha256(raw), normalizedInputSha256: mode === "forwarded" ? semanticInputSha256(emitted[0].input) : null, @@ -490,7 +490,7 @@ describe("qualified ACPX runtime sidecar", () => { expect(validatePrpStructuredRunResult(emitted[1].input).ok).toBe(true); await commands.resolve({ callId: "3", turnId: "test-turn", result: { accepted: true }, error: null }); } - const body = await (await response).json(); + const body = await (await response).json() as ToolCallResponseBody; const receipt = readNativeSemanticReceipt({ contents: body.result.content }); expect(receipt).toMatchObject({ inputSha256: semanticInputSha256(raw), normalizedInputSha256: eligible ? semanticInputSha256(emitted[1].input) : null, outcome: eligible ? "returned" : "error" }); @@ -517,7 +517,7 @@ describe("qualified ACPX runtime sidecar", () => { await vi.waitFor(() => expect(emitted).toHaveLength(1)); await expect(handler(call)).rejects.toThrow("duplicated"); } finally { controller.abort(); } - expect((await result).message).toContain("cancelled"); + expect((await result as Error).message).toContain("cancelled"); expect(capture).not.toHaveBeenCalled(); expect(tools.size).toBe(0); expect(emitted[0].operationId).toBe("get_task_context"); @@ -547,7 +547,7 @@ describe("qualified ACPX runtime sidecar", () => { const bridge = await startRunnerToolBridge({ handler, captureSemanticReceipt: () => () => {} }); try { const body = await (await fetch(bridge.url, { method: "POST", headers: { Authorization: `Bearer ${bridge.secret}`, "Content-Type": "application/json" }, - body: JSON.stringify(request) })).json(); + body: JSON.stringify(request) })).json() as ToolCallResponseBody; expect(body.result.isError).toBe(true); expect(body.result.content[0].text).toBe("ACPX semantic tool call exceeds the sidecar frame limit"); expect(readNativeSemanticReceipt({ contents: body.result.content })).toMatchObject({ outcome: "error", normalizedInputSha256: null }); @@ -561,7 +561,7 @@ describe("qualified ACPX runtime sidecar", () => { } finally { await bridge.close(); } }); - it.each(["backpressure", "throws"])("uses actual writer acceptance, preserving %s semantics", async mode => { + it.each(["backpressure", "throws"] as const)("uses actual writer acceptance, preserving %s semantics", async mode => { const wire = loadActualSidecarWriter(mode); const tools = new Map(), emitted: any[] = [], commit = vi.fn(), capture = vi.fn((_input: unknown) => commit); const raw = { reportedWorkDisposition: "done", summary: "Complete", evidence: [], verification: [], @@ -727,7 +727,7 @@ describe("qualified ACPX runtime sidecar", () => { expect(Buffer.byteLength(JSON.stringify(JSON.parse(wire.writes[0]!).payload))).toBeGreaterThan(256 * 1024); expect(Buffer.byteLength(wire.writes[0]!)).toBeLessThan(ACPX_SIDECAR_MAX_FRAME_BYTES); } - const body = await (await response).json(); + const body = await (await response).json() as ToolCallResponseBody; if (mode === "error") { expect(JSON.stringify(body)).not.toContain(credentialCanary); expect(JSON.stringify(body)).toContain("[REDACTED]"); @@ -820,8 +820,9 @@ describe("qualified ACPX runtime sidecar", () => { const sanitize = new Function("boundedOptionalText", "stableProviderIdentity", `return event => {${source.slice(start, end)} return null;}`)( (value: string) => value, (value: string) => `stable-${value}`, ); - const projection = createPiMessageProjection<{ type: string; text: string; stream: string; messageId: string; meta: Record }>(); - const event = (kind: string) => ({ type: "text_delta", text: "", stream: "output", messageId: `pi-message-${"a".repeat(64)}`, + type PiTextFrame = { type: string; text: string; stream: string; messageId: string; meta: Record }; + const projection = createPiMessageProjection(); + const event = (kind: string): PiTextFrame => ({ type: "text_delta", text: "", stream: "output", messageId: `pi-message-${"a".repeat(64)}`, meta: { origin: "pi-native-assistant", source: "pi-rpc-message-v1", kind, secret: "DROP_ME" } }); const startFrame = sanitize(projection.normalize(event("start"))); const endFrame = sanitize(projection.normalize(event("end:toolUse"))); @@ -830,7 +831,7 @@ describe("qualified ACPX runtime sidecar", () => { expect(startFrame.messageId).toBe(endFrame.messageId); expect(JSON.stringify([startFrame, endFrame])).not.toContain("DROP_ME"); projection.settle(); - const loaded = createPiMessageProjection>(); + const loaded = createPiMessageProjection(); const history = sanitize(loaded.normalize({ ...event("history"), messageId: `pi-history-message-${"b".repeat(64)}`, text: "old reply", meta: { origin: "pi-history-assistant", source: "pi-session-history-v1", kind: "history" } })); expect(history).toMatchObject({ text: "old reply", piMessageHistory: true }); loaded.settle(); @@ -1744,6 +1745,8 @@ class SidecarProcess { } } +type ToolCallResponseBody = { result: { content: Array<{ text: string }>; isError?: boolean } }; + function loadWaitForTool(input: { tools: Map; emitted: unknown[]; diff --git a/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts b/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts index 6ebd3259074..8ad2681f63c 100644 --- a/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts +++ b/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts @@ -54,7 +54,9 @@ describe("ACPX sidecar input sequencing", () => { async () => { events.push(command); }, - () => events.push(`${command}:error`), + () => { + events.push(`${command}:error`); + }, ); } const shutdown = pending.then(() => events.push("shutdown")); @@ -87,7 +89,9 @@ describe("ACPX sidecar input sequencing", () => { async () => { events.push("recovered"); }, - () => events.push("unexpected"), + () => { + events.push("unexpected"); + }, ); await recovered; @@ -113,7 +117,9 @@ describe("ACPX sidecar input sequencing", () => { async () => { events.push("recovered"); }, - () => events.push("unexpected"), + () => { + events.push("unexpected"); + }, ); await expect( 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 5888e1b9328..0f07e2173ac 100644 --- a/packages/paperclip-runner/src/cli/eval-session-contract.test.ts +++ b/packages/paperclip-runner/src/cli/eval-session-contract.test.ts @@ -123,7 +123,7 @@ describe("eval-session request contract", () => { 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(value, { candidateProfile: "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"); @@ -525,10 +525,10 @@ describe("eval-session usage", () => { providerReported: { nanodollars: null, recordedAttempts: 0 }, estimated: { nanodollars: 6_200_000, recordedAttempts: 1 }, }); - expect(snapshot.usageLedger[0]?.costNanodollars).toBe(legacyCost); + expect(snapshot.usageLedger![0]?.costNanodollars).toBe(legacyCost); const limitedRequest = parseEvalSessionRequest(request()); limitedRequest.limits.maxEstimatedCostNanodollars = 10_000_000; - const costlySnapshot = { ...snapshot, usageLedger: [{ ...snapshot.usageLedger[0]!, costNanodollars: 10_000_001 }] }; + const costlySnapshot = { ...snapshot, usageLedger: [{ ...snapshot.usageLedger![0]!, costNanodollars: 10_000_001 }] }; expect(() => boundedEvalSessionUsage(limitedRequest, { turnId: "codex-turn", status: "completed", assistantText: "Completed", snapshot: costlySnapshot, })).toThrow("legacy ledger cost limit exceeded"); @@ -609,7 +609,7 @@ describe("eval-session budget settlement", () => { }); const input = request({ provider: "acpx", acpxAgent: agent, model, runnerd: { path: binary, sha256: createHash("sha256").update("unused fake runner").digest("hex") }, - session: { workingDirectory: workspace, ...(agent === "pi" ? { piThinkingLevel: "low" } : {}) }, limits: { turnTimeoutMs: 1000, maxAgentTurns: 2, maxEstimatedCostNanodollars: 100_000_000 }, + session: { workingDirectory: workspace }, limits: { turnTimeoutMs: 1000, maxAgentTurns: 2, maxEstimatedCostNanodollars: 100_000_000 }, }); const requestPath = join(workspace, "request.json"); const outputPath = join(workspace, "output.json"); diff --git a/packages/paperclip-runner/src/cli/opencode-proxy-input.test.ts b/packages/paperclip-runner/src/cli/opencode-proxy-input.test.ts index 47170c7dbf8..7b3a0a27a7b 100644 --- a/packages/paperclip-runner/src/cli/opencode-proxy-input.test.ts +++ b/packages/paperclip-runner/src/cli/opencode-proxy-input.test.ts @@ -17,14 +17,18 @@ describe("OpenCode proxy input sequencing", () => { await firstBlocked; events.push("initialize:complete"); }, - () => events.push("initialize:error"), + () => { + events.push("initialize:error"); + }, ); pending = enqueueOpenCodeProxyInput( pending, async () => { events.push("thread:start"); }, - () => events.push("thread:error"), + () => { + events.push("thread:error"); + }, ); const shutdown = pending.then(() => { events.push("shutdown"); @@ -50,7 +54,9 @@ describe("OpenCode proxy input sequencing", () => { async () => { throw new Error("initialize failed"); }, - (error) => events.push((error as Error).message), + (error) => { + events.push((error as Error).message); + }, ); await pending; diff --git a/packages/paperclip-runner/src/conformance/local-provider-fault-matrix.test.ts b/packages/paperclip-runner/src/conformance/local-provider-fault-matrix.test.ts index 74419455b09..f69d58d8acb 100644 --- a/packages/paperclip-runner/src/conformance/local-provider-fault-matrix.test.ts +++ b/packages/paperclip-runner/src/conformance/local-provider-fault-matrix.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import type { NativeExecutionInput } from "../contracts/native-execution.js"; +import type { NativeExecutionInput, NativeExecutionInputV1 } from "../contracts/native-execution.js"; import type { NativeRunIdentity, NativeSessionCapabilities, @@ -29,8 +29,8 @@ type ProfileKey = "codex" | "opencode" | "acpx-claude" | "acpx-codex"; interface LocalNativeProfile { key: ProfileKey; - driverKind: NativeExecutionInput["session"]["driverKind"]; - provider: NativeExecutionInput["provider"]; + driverKind: NativeExecutionInputV1["session"]["driverKind"]; + provider: NativeExecutionInputV1["provider"]; } /** diff --git a/packages/paperclip-runner/src/contracts/completion-result.test.ts b/packages/paperclip-runner/src/contracts/completion-result.test.ts index d230ef4ef21..fb166ef36e0 100644 --- a/packages/paperclip-runner/src/contracts/completion-result.test.ts +++ b/packages/paperclip-runner/src/contracts/completion-result.test.ts @@ -1,5 +1,5 @@ import { normalizeLegacyPrpStructuredRunResult } from "../protocol/result-normalization.js"; -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { PRP_BLOCK_RESULT_OUTPUT_SCHEMA, diff --git a/packages/paperclip-runner/src/contracts/provider-trace-file-sink.test.ts b/packages/paperclip-runner/src/contracts/provider-trace-file-sink.test.ts index fc30e120364..f104bc303a3 100644 --- a/packages/paperclip-runner/src/contracts/provider-trace-file-sink.test.ts +++ b/packages/paperclip-runner/src/contracts/provider-trace-file-sink.test.ts @@ -76,5 +76,5 @@ describe("ProviderTraceFileSink", () => { }); function statMode(value: Awaited>): number { - return value.mode & 0o777; + return Number(value.mode) & 0o777; } diff --git a/packages/paperclip-runner/src/contracts/question-set.test.ts b/packages/paperclip-runner/src/contracts/question-set.test.ts index c7860c8bf24..16190c9685d 100644 --- a/packages/paperclip-runner/src/contracts/question-set.test.ts +++ b/packages/paperclip-runner/src/contracts/question-set.test.ts @@ -1,4 +1,4 @@ -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { questionSetSchema } from "../protocol/generated/schema-bundle.js"; import { describe, expect, it } from "vitest"; diff --git a/packages/paperclip-runner/src/control-plane/cursor-cloud-runner-launch.test.ts b/packages/paperclip-runner/src/control-plane/cursor-cloud-runner-launch.test.ts index 14b87373c9c..b38b5099f13 100644 --- a/packages/paperclip-runner/src/control-plane/cursor-cloud-runner-launch.test.ts +++ b/packages/paperclip-runner/src/control-plane/cursor-cloud-runner-launch.test.ts @@ -101,7 +101,7 @@ it("settles a create rejected during real-facade Stop before cold recovery and t PAPERCLIP_CURSOR_CLOUD_RELAY_EXPIRES_AT: String(Date.now() + 60_000) }; const options = { provider: "cursor_cloud" as const, cursorCloud, stateDirectory, runnerBinary: defaultCapabilityRunnerdBinary(), providerNodeCommand, cursorCloudProxyPath: proxyPath, cursorCloudProxySha256: `sha256:${createHash("sha256").update(await readFile(proxyPath)).digest("hex")}`, - lifecyclePolicy: { mode: "per_turn" as const }, environment }; + lifecyclePolicy: { mode: "per_turn" as const, idleTimeoutMs: null }, environment }; const first = createRunnerdCodexTransport({ ...options, prpIdentity: identity }), bundles = [first]; let stop: Promise | undefined; try { @@ -169,7 +169,7 @@ it("opens and closes the real Cloud facade without a provider prompt", async () runnerBinary: defaultCapabilityRunnerdBinary(), providerNodeCommand, cursorCloudProxyPath: proxyPath, cursorCloudProxySha256: `sha256:${createHash("sha256").update(await readFile(proxyPath)).digest("hex")}`, - lifecyclePolicy: { mode: "per_turn" }, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, prpIdentity: { runnerInstanceId: "runner-cloud-launch", environmentLeaseId: "lease-cloud-launch", runId: "00000000-0000-4000-8000-000000000761", normalizedSessionId, turnId: "turn-cloud-launch", itemId: "item-cloud-launch" }, environment: { @@ -249,7 +249,7 @@ it("preserves native Cloud tool identity and redacted output through the real Ru provider: "cursor_cloud", cursorCloud, stateDirectory: join(root, "runner-session"), runnerBinary: defaultCapabilityRunnerdBinary(), providerNodeCommand, cursorCloudProxyPath: proxyPath, cursorCloudProxySha256: `sha256:${createHash("sha256").update(await readFile(proxyPath)).digest("hex")}`, - lifecyclePolicy: { mode: "per_turn" }, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, prpIdentity: { runnerInstanceId: "runner-cloud-tool", environmentLeaseId: "lease-cloud-tool", runId: "00000000-0000-4000-8000-000000000762", normalizedSessionId, turnId: "turn-cloud-tool", itemId: "item-cloud-tool" }, environment: { @@ -343,7 +343,7 @@ async function cloudQuestionHandoff(terminalDelayMs: number, governed: boolean, provider: "cursor_cloud", cursorCloud, stateDirectory: join(root, "runner-session"), runnerBinary: defaultCapabilityRunnerdBinary(), providerNodeCommand, cursorCloudProxyPath: proxyPath, cursorCloudProxySha256: `sha256:${createHash("sha256").update(await readFile(proxyPath)).digest("hex")}`, - lifecyclePolicy: { mode: "per_turn" }, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, prpIdentity: { runnerInstanceId: "runner-cloud-question", environmentLeaseId: "lease-cloud-question", runId: "00000000-0000-4000-8000-000000000763", normalizedSessionId, turnId: "turn-cloud-question", itemId: "item-cloud-question" }, environment: { @@ -405,7 +405,7 @@ async function cloudQuestionHandoff(terminalDelayMs: number, governed: boolean, const execution = executeNativeSession({ input, backend, runnerInstanceId: "runner-cloud-question", controlPlaneInstanceId: "control-cloud-question", timeoutMs: 120_000, controlPlane: { async openRun() {}, async checkpointSession() {}, completeRun, - async appendEvent(event) { history.push(event); return { cursor: history.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, + async appendEvent(event: PrpEvent) { history.push(event); return { cursor: history.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents(replay) { return { events: history.filter(event => event.sourceInstanceId === replay.sourceInstanceId && event.sourceSeq > replay.afterSourceSeq), highestContiguousSourceSeq: 0 }; }, }, resolveGovernedWait: ({ event }) => event.eventType === "item.completed" && event.payload.kind === "dynamicToolCall" ? yielded : null, @@ -520,7 +520,7 @@ async function cloudColdAuthorityRotation(instructions: string, validShutdown = PAPERCLIP_CURSOR_CLOUD_RELAY_URL: `https://relay.example.test/mcp/native-runs/${runId}`, PAPERCLIP_CURSOR_CLOUD_RELAY_EXPIRES_AT: String(Date.now() + 60_000) }); const options = { provider: "cursor_cloud" as const, cursorCloud, stateDirectory, runnerBinary: defaultCapabilityRunnerdBinary(), providerNodeCommand, cursorCloudProxyPath: proxyPath, cursorCloudProxySha256: `sha256:${createHash("sha256").update(await readFile(proxyPath)).digest("hex")}`, - lifecyclePolicy: { mode: "per_turn" as const } }; + lifecyclePolicy: { mode: "per_turn" as const, idleTimeoutMs: null } }; const mcp = async (token: string, body: Record, sessionId?: string, priorScopeHeader?: string): Promise<{ status: number; sessionId?: string; value: any }> => { const scopeHeader = priorScopeHeader ?? await currentCloudScopeHeader(runtime, normalizedSessionId); return new Promise((accept, reject) => { 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 13aa10b34d1..0dd2161ca0b 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 @@ -39,6 +39,8 @@ import { inspectRetainedRunInstructions, inspectStagedRunAttachmentHeader, type DurablePrpCheckpoint, + type DurablePrpControlPlaneStore, + type RunnerProcessHandle, durableRecoveryInternals, SemanticToolNotDispatchedError, inspectWarmRunTransition, @@ -48,6 +50,16 @@ import { } from "./durable-prp-control-plane.js"; import type { DurableRecoveryIdentity } from "./prp-transport-types.js"; +type WritableCoreStore = DurablePrpControlPlaneStore & { + save(): void; + commit(candidate: DurablePrpControlPlaneStore["state"]): void; +}; + +function restartRunner(handle: RunnerProcessHandle, ticket: string): void { + if (!handle.restart) throw new Error("Runner handle does not support restart"); + handle.restart(ticket); +} + const identity: DurableRecoveryIdentity = { runnerInstanceId: "runner-test-1", environmentLeaseId: "environment-test-1", @@ -229,7 +241,7 @@ it("recovers only the matching run's admitted instructions from a valid checkpoi expect(inspectRetainedRunInstructions(core.store.state, { ...targetExpected, runtimeContextDigest: digest })).toBeNull(); expect(inspectRetainedRunInstructions(core.store.state, expected)).toBe(template.provider.instructions); const corrupted = structuredClone(core.store.state); - corrupted.stagedRunAttachment!.templateDigest = `sha256:${"f".repeat(64)}`; + corrupted.stagedRunAttachment = { ...corrupted.stagedRunAttachment!, templateDigest: `sha256:${"f".repeat(64)}` }; expect(inspectRetainedRunInstructions(corrupted, targetExpected)).toBeNull(); } finally { await core.stop(); rmSync(root, { recursive: true, force: true }); } }); @@ -545,7 +557,7 @@ it("excludes streamed output from checkpoints even when the live window is full" anchor.eventType = "turn.started"; (anchor.envelope.payload as Record).eventType = "turn.started"; core.store.state.ackedSourceSeq = 4096; - core.store.save(); + (core.store as WritableCoreStore).save(); const snapshots: DurablePrpCheckpoint[] = []; core.setCheckpointPersistence(async saved => { snapshots.push(saved); }); try { @@ -918,7 +930,7 @@ it.each([false, true])("dispatches a fresh semantic input once when recovery ove if (JSON.parse(saved.records.get("cursor")!).ackedSourceSeq > 0) { writeHeld = true; await writeBarrier; } }); const pending: Promise[] = []; - let client: Awaited>; + let client: AuthenticatedClient | null = null; try { await core.start(); client = (await authenticate(core, core.issueBootstrapTicket()))!; @@ -1149,7 +1161,7 @@ it.each(["event", "progress", "lifecycle", "lifecycle_priority_one", "usage", "c expect(() => core.queueCommand("session.snapshot")).toThrow("indeterminate"); } if (mode === "authority_conflict") { - core.store.commit(structuredClone(core.store.state)); + (core.store as WritableCoreStore).commit(structuredClone(core.store.state)); core.queueCommand("session.snapshot", {}, "command_after_authority"); expect(JSON.parse(readFileSync(core.store.path, "utf8")).ackedSourceSeq).toBe(0); expect(core.store.state.ackedSourceSeq).toBe(0); @@ -1817,7 +1829,7 @@ it("pins the ACPX launch profile in runner startup arguments and restarts", () = }, }); - handle.restart("replacement-ticket"); + restartRunner(handle, "replacement-ticket"); expect(launches).toHaveLength(2); for (const launch of launches) { expect(launch.args).toContain("--acpx-launch-authority-digest"); @@ -1869,7 +1881,7 @@ it("pins the OpenCode launch profile in runner startup arguments and restarts", }, }); - handle.restart("replacement-ticket"); + restartRunner(handle, "replacement-ticket"); expect(launches).toHaveLength(2); for (const launch of launches) { expect(launch.args).toEqual( @@ -2057,7 +2069,7 @@ it("preserves only bounded GitHub credential projection at the runner spawn boun it("passes structured inputs to an external owner and preserves its replacement veto", () => { const process = { child: { exitCode: null, signalCode: null, kill: () => true }, completion: Promise.resolve({ code: 0, signal: null, stdout: "", stderr: "" }), restart: null }; - const launcher = vi.fn(() => process); + const launcher = vi.fn((_spec: RunnerProcessLaunchSpec) => process); const options = { connection: { mode: "external" as const }, stateDirectory: "/unused", identity, ticket: "synthetic-ticket", maxOutboxBytes: 262144, p0ReserveBytes: 65536, runnerVersion: expectedRunnerVersion, runnerDigest: expectedRunnerDigest, @@ -2065,7 +2077,7 @@ it("passes structured inputs to an external owner and preserves its replacement expect(() => spawnRunner(options)).toThrow("requires a process owner"); expect(spawnRunner({ ...options, processLauncher: launcher })).toBe(process); expect(launcher).toHaveBeenCalledWith(expect.objectContaining({ nativeRunner: expect.objectContaining(options) })); - expect(launcher.mock.calls[0][0].args).not.toContain("--connect-url"); + expect(launcher.mock.calls[0]![0].args).not.toContain("--connect-url"); }); it("preserves the controller-selected ACPX provider package root", () => { @@ -2147,7 +2159,7 @@ it.each([ completion: Promise.resolve({ code: 0, signal: null, stdout: "", stderr: "" }) }; }, }); - handle.restart("replacement-ticket"); + restartRunner(handle, "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]; diff --git a/packages/paperclip-runner/src/devtools/index.test.ts b/packages/paperclip-runner/src/devtools/index.test.ts index 80c591de7cb..f6e118ceb34 100644 --- a/packages/paperclip-runner/src/devtools/index.test.ts +++ b/packages/paperclip-runner/src/devtools/index.test.ts @@ -19,37 +19,41 @@ function snapshot(): CapabilityLiveSessionSnapshot { current.lifecycle = "running"; current.artifacts.push({ id: "artifact-1", - companyId: "company-1", taskId: "task-1", - runId: "run-1", - kind: "text", - name: "result.txt", + filename: "result.txt", + contentType: "text/plain", + byteSize: 6, + sha256: "a".repeat(64), contentRef: "file:///private/result.txt", - metadata: {}, createdAt: "2026-08-09T00:00:01.000Z", }); return { - id: "session-1", + schema: "paperclip.capability.live-session.v1", + sessionId: "session-1", revision: 1, status: "running", activeTurnId: null, providerThreadId: "thread-1", providerSessionId: "provider-session-1", - providerModel: "gpt-test", + providerModel: { id: "gpt-test", provider: "openai" }, config: { seedState: JSON.stringify(seed), workingDirectory: "/private/worktree", - scenario: "hb-baseline", + scenario: { id: "hb-baseline" }, capabilities: ["task:read"], explicitClaims: [], turnTimeoutMs: 1_000, }, authority: { + active: true, companyId: "company-1", actorId: "actor-1", taskId: "task-1", runId: "run-1", sessionId: "session-1", + scenarioId: "hb-baseline", + capabilities: ["task:read"], + explicitClaims: [], }, transcript: [], evidence: [{ @@ -64,8 +68,9 @@ function snapshot(): CapabilityLiveSessionSnapshot { }, }], mockState: JSON.stringify(current), + authorizationRecords: [], process: null, - networkEvidence: [], + networkEvidence: { realPaperclipRequests: 0, childPaperclipEnvironmentKeys: [] }, createdAt: "2026-08-09T00:00:00.000Z", updatedAt: "2026-08-09T00:00:01.000Z", stateHistory: [{ diff --git a/packages/paperclip-runner/src/drivers/acpx/antigravity-acpx-runtime.test.ts b/packages/paperclip-runner/src/drivers/acpx/antigravity-acpx-runtime.test.ts index 93ce3d63825..c8aef50aaa0 100644 --- a/packages/paperclip-runner/src/drivers/acpx/antigravity-acpx-runtime.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/antigravity-acpx-runtime.test.ts @@ -168,5 +168,6 @@ it("rejects an unacknowledged thinking-level change after a native family model const f = await fixture("family-change"), owner = await f.open(); const result = await f.turn(owner); expect(result.status).toBe("failed"); + if (result.status !== "failed") throw new Error("turn did not fail"); expect(result.error?.message).toMatch(/admission/i); }); diff --git a/packages/paperclip-runner/src/drivers/acpx/antigravity-admission.test.ts b/packages/paperclip-runner/src/drivers/acpx/antigravity-admission.test.ts index 728b1fd2a32..343317d0f29 100644 --- a/packages/paperclip-runner/src/drivers/acpx/antigravity-admission.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/antigravity-admission.test.ts @@ -22,7 +22,7 @@ function response(message: ReturnType, changes: Record }, } }; } const prompt = () => ({ id: "turn", method: "session/prompt", params: { sessionId: "native-session", prompt: [{ type: "text", text: "Do the task" }] } }); 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 f49e42294b7..3fbcc41260e 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 @@ -2,7 +2,7 @@ import { appendSemanticToolReceipt } from "../semantic-tool-receipt.js"; import { readFileSync } from "node:fs"; import { createHash } from "node:crypto"; import { cursorToolIdentity } from "./cursor-plan-tool-identity.js"; -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi, type Mock } from "vitest"; import { AcpxProviderProcessLostError } from "./codex-runtime-adapter.js"; import { QUALIFIED_ACPX_PROFILES } from "./qualified-profiles.js"; @@ -31,7 +31,7 @@ describe("Codex ACPX harness driver", () => { it("captures the direct driver's normalized input only when the exact proposal is retained", async () => { const fixture = driverFixture({ agent: "copilot", model: "explicit-test-model", providerPolicy: { readOnly: true } }, { runtimeEvents: [] }); const session = await fixture.driver.openSession({ runId: "run-normalized-receipt", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); - await session.startTurn({ message: { text: "Complete" } }); + await session.startTurn({ message: { role: "user", text: "Complete" } }); const { schema: _, attentionRequests: __, artifacts: ___, ...raw } = completedResult(); const commit = vi.fn(), capture = vi.fn((value: unknown) => { expect(commit).not.toHaveBeenCalled(); return commit; }); const handler = fixture.hostOptions!.semanticTools!.handler; @@ -58,7 +58,7 @@ describe("Codex ACPX harness driver", () => { runtimeEvents: Array.from({ length: 8 }, (_, n) => ({ type: "text_delta" as const, stream: "output" as const, text: `bounded-${n}` })), }); const session = await fixture.driver.openSession({ runId: "run-normalized-pressure", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); - await session.startTurn({ message: { text: "Complete" } }); + await session.startTurn({ message: { role: "user", text: "Complete" } }); await vi.waitFor(async () => expect((await session.transcript!()).eventCount).toBeGreaterThanOrEqual(4)); const commit = vi.fn(), capture = vi.fn(() => commit); await expect(fixture.hostOptions!.semanticTools!.handler({ tool: PRP_COMPLETION_TOOL_NAME, callId: "finish", arguments: completedResult(), @@ -107,23 +107,23 @@ describe("Codex ACPX harness driver", () => { }, } }; fixture.host.status.mockResolvedValueOnce(before as never).mockResolvedValue(after as never); - await session.startTurn({ message: { text: "fixture" } }); + await session.startTurn({ message: { role: "user", text: "fixture" } }); fixture.finishTurn({ status: "completed", stopReason: "end_turn" }); const events = await collectUntil(session.events(), "turn.completed"); const notices = events.filter(e => e.eventType === "provider.notice.recorded" && e.payload.category === "cursor_native_usage_observed"); expect(notices).toHaveLength(agent === "cursor" ? 1 : 0); - expect(events.filter(e => e.eventType === "usage.updated")).toEqual([]); + expect(events.filter(e => e.eventType === "usage.reported")).toEqual([]); expect(events.at(-1)?.eventType).toBe("turn.completed"); await session.close({ reason: "partial diagnostics verified" }); }); it.each(["tool-1", "tool with spaces", "🧭/plan", "x".repeat(300)])("binds a native Cursor plan request to its actual projected tool identity: %s", async toolCallId => { const fixture = driverFixture({ agent: "cursor", model: "explicit-test-model", mode: "plan", providerPolicy: { readOnly: false } }, { - runtimeEvents: [{ type: "tool_call", tag: "tool_call", toolCallId, title: "arbitrary tool display", kind: "execute", status: "pending" }], + runtimeEvents: [{ type: "tool_call", tag: "tool_call", toolCallId, title: "arbitrary tool display", kind: "execute", status: "pending", text: "arbitrary tool display" }], }); const session = await fixture.driver.openSession({ runId: "run-plan-identity", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); const toolEvents = collectUntil(session.events(), "tool.execution.started"); - const { turnId } = await session.startTurn({ message: { text: "Plan" } }); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Plan" } }); const tool = (await toolEvents).find(e => e.eventType === "tool.execution.started")!; const created = collectUntil(session.events(), "runtime_request.created"); const callback = fixture.host.startTurn.mock.calls[0]![0].onExtensionRequest!; @@ -145,7 +145,7 @@ describe("Codex ACPX harness driver", () => { ]; const fixture = driverFixture({ agent, model: "explicit-model", providerPolicy: { readOnly: true } }, { runtimeEvents }); const session = await fixture.driver.openSession({ runId: "other-provider-identity", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); - const { turnId } = await session.startTurn({ message: { text: "fixture" } }); + const { turnId } = await session.startTurn({ message: { role: "user", text: "fixture" } }); fixture.finishTurn({ status: "completed" }); const events = await collectUntil(session.events(), "turn.completed"); const messages = events.filter(event => event.payload.kind === "agentMessage"); @@ -162,7 +162,7 @@ describe("Codex ACPX harness driver", () => { runtimeEvents.push(chunk(undefined, "Info: native session activity")); const fixture = driverFixture({ agent: "copilot", model: "explicit-model", providerPolicy: { readOnly: true } }, { runtimeEvents }); const session = await fixture.driver.openSession({ runId: "run-copilot-boundaries", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); - await session.startTurn({ message: { text: "Answer exactly" } }); + await session.startTurn({ message: { role: "user", text: "Answer exactly" } }); fixture.finishTurn({ status: scenario === "cancelled" ? "cancelled" : "completed" }); const events = await collectUntil(session.events(), scenario === "cancelled" ? "turn.interrupted" : "turn.completed"); const interim = events.find(event => event.eventType === "item.delta" && event.payload.text === "EXACT_MARKER"); @@ -179,7 +179,7 @@ describe("Codex ACPX harness driver", () => { it("keeps Pi retry-only activity as a rich notice without inventing a final answer", async () => { const fixture = driverFixture({ agent: "pi", model: "openrouter/deepseek/deepseek-v4-flash-0731", providerPolicy: { readOnly: true } }, { runtimeEvents: [] }); const session = await fixture.driver.openSession({ runId: "run-pi-notice", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); - await session.startTurn({ message: { text: "Work" } }); + await session.startTurn({ message: { role: "user", text: "Work" } }); const input = fixture.host.startTurn.mock.calls[0]![0]; await input.onExtensionNotification!("paperclip/pi_notice", { sessionId: "backend-1", category: "auto_retry_end", summary: "Retry did not recover.", severity: "warning", details: { attempt: 2, success: false } }); fixture.finishTurn({ status: "completed" }); @@ -201,7 +201,7 @@ describe("Codex ACPX harness driver", () => { } const fixture = driverFixture({ agent: "pi", model: "openrouter/deepseek/deepseek-v4-flash-0731", providerPolicy: { readOnly: true } }, { runtimeEvents }); const session = await fixture.driver.openSession({ runId: "run-pi-boundaries", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); - await session.startTurn({ message: { text: "Answer exactly" } }); + await session.startTurn({ message: { role: "user", text: "Answer exactly" } }); fixture.finishTurn({ status: scenario === "cancelled" ? "cancelled" : "completed" }); const events = await collectUntil(session.events(), scenario === "missing-end" ? "turn.failed" : scenario === "cancelled" ? "turn.interrupted" : "turn.completed"); expect(events.some(event => event.eventType === "item.delta" && event.payload.text === "Calling finish.")).toBe(true); @@ -233,7 +233,7 @@ describe("Codex ACPX harness driver", () => { } const fixture = driverFixture({ agent: "kimi", model: "kimi-for-coding", providerPolicy: { readOnly: true } }, { runtimeEvents }); const session = await fixture.driver.openSession({ runId: "run-kimi-boundaries", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); - const { turnId } = await session.startTurn({ message: { text: "Answer exactly" } }); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Answer exactly" } }); fixture.finishTurn({ status: scenario === "cancelled" ? "cancelled" : "completed" }); const events = await collectUntil(session.events(), scenario === "missing-end" ? "turn.failed" : scenario === "cancelled" ? "turn.interrupted" : "turn.completed"); const progress = events.find(event => event.eventType === "item.delta" && event.payload.text === "Calling finish.")!; @@ -256,34 +256,34 @@ describe("Codex ACPX harness driver", () => { 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 { turnId } = await session.startTurn({ message: { role: "user", 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 session.steer!({ turnId, correlationId: "control-1", message: { text: "Change focus" } }); - await session.steer!({ turnId, correlationId: "control-2", mode: "follow_up", message: { text: "Then validate" } }); + await session.steer!({ turnId, correlationId: "control-1", message: { role: "user", text: "Change focus" } }); + await session.steer!({ turnId, correlationId: "control-2", mode: "follow_up", message: { role: "user", 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(); + await expect(session.steer!({ turnId, correlationId: "control-2", message: { role: "user", text: "Duplicate" } })).rejects.toThrow(/already attempted/); + await expect(session.steer!({ turnId: "wrong-turn", message: { role: "user", 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 expect(session.steer!({ turnId, message: { role: "user", 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 } }, { runtimeEvents: [] }); 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/); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Work" } }); + await expect(session.steer!({ turnId, message: { role: "user", 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" } }; + const input = { turnId, correlationId: "ambiguous", message: { role: "user" as const, 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(); @@ -453,7 +453,7 @@ describe("Codex ACPX harness driver", () => { ], }); const session = await fixture.driver.openSession({ runId: "run-claude-banners", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); - await session.startTurn({ message: { text: "Fix it" } }); + await session.startTurn({ message: { role: "user", text: "Fix it" } }); fixture.finishTurn({ status: "completed" }); const events = await collectUntil(session.events(), "turn.completed"); for (const event of events) validatePrpEvent(event); @@ -713,11 +713,11 @@ describe("Codex ACPX harness driver", () => { signal: new AbortController().signal, }), ).rejects.toThrow("does not match"); - await session.interrupt({ turnId, reason: "user cancelled" }); + await session.interrupt!({ turnId, reason: "user cancelled" }); expect(fixture.host.interruptActiveTurn).toHaveBeenCalledWith( "user cancelled", ); - await expect(session.interrupt({ turnId: "stale-turn" })).rejects.toThrow( + await expect(session.interrupt!({ turnId: "stale-turn" })).rejects.toThrow( "is not the active turn", ); await session.close({ reason: "cancelled" }); @@ -1291,13 +1291,13 @@ describe("Codex ACPX harness driver", () => { const fixture = driverFixture({}, { closeSettlementTimeoutMs: 1 }); const hostA = { ...fixture.host, - close: vi.fn(async () => { + close: vi.fn(async (_input: { reason: string }): Promise => { throw new Error("host A cleanup failed"); }), }; const hostB = { ...fixture.host, - close: vi.fn(async () => { + close: vi.fn(async (_input: { reason: string }): Promise => { throw new Error("host B cleanup failed"); }), }; @@ -1606,7 +1606,7 @@ describe("Codex ACPX harness driver", () => { ).rejects.toThrow("awaiting canonical terminal-event retention"); expect(dynamicToolHandler).not.toHaveBeenCalled(); await expect( - session.interrupt({ turnId, reason: "too late" }), + session.interrupt!({ turnId, reason: "too late" }), ).rejects.toThrow("awaiting canonical terminal-event retention"); expect(fixture.host.interruptActiveTurn).not.toHaveBeenCalled(); await expect(session.snapshot()).resolves.toMatchObject({ @@ -1761,7 +1761,7 @@ describe("Codex ACPX harness driver", () => { const fixture = driverFixture({ agent: "copilot", model: "gpt-5.6-luna", providerPolicy: { readOnly: false } }, { eventStreamFailure: failure.promise }); const session = await fixture.driver.openSession({ runId: "run-provider-death", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); const created = collectUntil(session.events(), "runtime_request.created"); - const { turnId } = await session.startTurn({ message: { text: "Edit the file" } }); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Edit the file" } }); const callback = fixture.host.startTurn.mock.calls[0]![0].onPermissionRequest!; const controller = new AbortController(); const response = callback({ inferredKind: "edit", raw: { @@ -1895,11 +1895,11 @@ describe("Codex ACPX harness driver", () => { it.each(["cursor-tool", "native\u0000tool", "native\u007ftool"].flatMap(toolCallId => ["written", "failed"].map(outcome => ({ toolCallId, outcome }))))("binds Cursor denial evidence to its original tool and response write: $outcome/$toolCallId", async ({ outcome, toolCallId }) => { const command = "printf 'sensitive-value' > /workspace/denied.txt"; const fixture = driverFixture({ agent: "cursor", model: "explicit-test-model", providerPolicy: { readOnly: false } }, { - runtimeEvents: [{ type: "tool_call", tag: "tool_call", toolCallId, title: "Run command", kind: "execute", status: "pending", rawInput: { command } }], + runtimeEvents: [{ type: "tool_call", tag: "tool_call", toolCallId, title: "Run command", kind: "execute", status: "pending", text: "Run command", rawInput: { command } }], }); const session = await fixture.driver.openSession({ runId: "run-cursor-receipt", normalizedSessionId: "session-1", workingDirectory: "/workspace" }); const origin = collectUntil(session.events(), "tool.execution.started"); - const { turnId } = await session.startTurn({ message: { text: "Request the command." } }); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Request the command." } }); const originEvents = await origin; const created = collectUntil(session.events(), "runtime_request.created"); const callback = fixture.host.startTurn.mock.calls[0]![0].onPermissionRequest!; @@ -1942,7 +1942,7 @@ describe("Codex ACPX harness driver", () => { const fixture = driverFixture({ agent, providerPolicy: { readOnly: false } }, { runtimeEvents }); const session = await fixture.driver.openSession({ runId: "run-attached", normalizedSessionId: "session-1", workingDirectory: "/fixture/workspace" }); const completed = collectUntil(session.events(), "turn.completed"); - const { turnId } = await session.startTurn({ message: { text: "Start attached work." } }); + const { turnId } = await session.startTurn({ message: { role: "user", text: "Start attached work." } }); fixture.finishTurn({ status: "completed", stopReason: "end_turn" }); const events = await completed; const evidence = events.filter(event => event.eventType === "provider.notice.recorded" && event.payload.category === "copilot_tool_evidence_v1"); @@ -1976,6 +1976,7 @@ describe("Codex ACPX harness driver", () => { const providerResponse = onElicitation( { mode: "form", + sessionId: "backend-1", message: "Choose deployment settings.", requestedSchema: { type: "object", @@ -2065,6 +2066,8 @@ describe("Codex ACPX harness driver", () => { onElicitation( { mode: "form", + sessionId: "backend-1", + message: "Provide a value.", requestedSchema: { type: "object", properties: { value: { type: "string" } }, @@ -2101,6 +2104,8 @@ describe("Codex ACPX harness driver", () => { const providerResponse = onElicitation( { mode: "form", + sessionId: "backend-1", + message: "Provide a value.", requestedSchema: { type: "object", properties: { value: { type: "string" } }, @@ -2148,6 +2153,8 @@ describe("Codex ACPX harness driver", () => { const providerResponse = onElicitation( { mode: "form", + sessionId: "backend-1", + message: "Provide a value.", requestedSchema: { type: "object", properties: { value: { type: "string" } }, @@ -2194,6 +2201,8 @@ describe("Codex ACPX harness driver", () => { const providerResponse = onElicitation( { mode: "form", + sessionId: "backend-1", + message: "Provide a value.", requestedSchema: { type: "object", properties: { value: { type: "string" } }, @@ -2262,6 +2271,8 @@ describe("Codex ACPX harness driver", () => { const providerResponse = onElicitation( { mode: "form", + sessionId: "backend-1", + message: "Provide a value.", requestedSchema: { type: "object", properties: { value: { type: "string" } }, @@ -3177,12 +3188,12 @@ function driverFixture( function recoveryWorkspaceLease( path = "/workspace", ): AcpxRecoveryWorkspaceLease & { - assertHeld: ReturnType; - close: ReturnType; + assertHeld: Mock<() => void>; + close: Mock<() => Promise>; } { return { path, - assertHeld: vi.fn(), + assertHeld: vi.fn<() => void>(), close: vi.fn(async () => undefined), }; } @@ -3207,6 +3218,7 @@ function fakeHost(createTurn: () => AcpxRuntimeTurn, onClose: () => void) { workspacePath: "/workspace", workspaceDigest: `sha256:${"b".repeat(64)}`, runtimeRoot: "/runtime/acpx/session-1", + commandDigest: `sha256:${"c".repeat(64)}`, profileDigest: `sha256:${"a".repeat(64)}`, requestedModel: "gpt-5.6-sol", effectiveModel: "gpt-5.6-sol", @@ -3225,7 +3237,7 @@ function fakeHost(createTurn: () => AcpxRuntimeTurn, onClose: () => void) { 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 () => { + close: vi.fn(async (_input: { reason: string }) => { onClose(); }), }; 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 11c16adc8a5..467f2d11ee9 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 @@ -7,6 +7,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import type { + AcpPermissionRequest, AcpAgentRegistry, AcpRuntime, AcpRuntimeHandle, @@ -114,7 +115,7 @@ describe("Codex ACPX runtime adapter", () => { if (!restoring) spawnProvider(); return HANDLE; }); - vi.mocked(runtime.setConfigOption).mockImplementation(async input => { + vi.mocked(runtime.setConfigOption!).mockImplementation(async input => { if (restoring) spawnProvider(); const guard = created.protocolGuardFactory!(); guard("outbound", { id: 0, method: "session/load", params: { sessionId: "backend-1" } }); @@ -546,7 +547,7 @@ describe("Codex ACPX runtime adapter", () => { const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "cursor" }; options.refreshConsumedCommand = vi.fn(async () => undefined); let created!: AcpRuntimeOptions; - vi.mocked(runtime.setConfigOption).mockImplementation(async () => { + vi.mocked(runtime.setConfigOption!).mockImplementation(async () => { const guard = created.protocolGuardFactory!(); const binding = cursorInstructionBinding(options.systemInstructions); guard("outbound", { id: 0, method: "session/load", params: { sessionId: "backend-1" } }); @@ -574,7 +575,7 @@ describe("Codex ACPX runtime adapter", () => { let created!: AcpRuntimeOptions; const binding = cursorInstructionBinding(options.systemInstructions); const configs = (mode: string) => [{ id: "mode", currentValue: mode }]; - vi.mocked(runtime.setConfigOption).mockImplementation(async input => { + vi.mocked(runtime.setConfigOption!).mockImplementation(async input => { const guard = created.protocolGuardFactory!(); guard("outbound", { id: 0, method: "session/load", params: { sessionId: "backend-1" } }); guard("inbound", { id: 0, result: { modes: { currentModeId: "agent" }, configOptions: configs("agent"), _meta: { paperclipCursorInstructions: { @@ -586,7 +587,7 @@ describe("Codex ACPX runtime adapter", () => { const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: value => { created = value; return runtime; }, }); - expect(vi.mocked(runtime.setConfigOption).mock.calls.map(([call]) => [call.key, call.value])).toEqual([["model", options.profile.reportedModelId], ["mode", selected]]); + expect(vi.mocked(runtime.setConfigOption!).mock.calls.map(([call]) => [call.key, call.value])).toEqual([["model", options.profile.reportedModelId], ["mode", selected]]); expect(options.refreshConsumedCommand).toHaveBeenCalledTimes(2); expect(await port.identity()).toMatchObject({ mode: selected }); expect(runtime.startTurn).not.toHaveBeenCalled(); @@ -652,13 +653,13 @@ describe("Codex ACPX runtime adapter", () => { 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 })) }); + const runtime = Object.assign(fakeRuntime(), { requestExtension: vi.fn(async (_input: unknown) => ({ 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" }; options.piThinkingLevel = "low"; - vi.mocked(runtime.setConfigOption).mockImplementation(async input => { + vi.mocked(runtime.setConfigOption!).mockImplementation(async input => { const guard = created.protocolGuardFactory!(); guard("outbound", { id: 0, method: "session/load", params: { sessionId: "backend-1" } }); guard("inbound", { id: 0, result: { modes: { currentModeId: "high" }, configOptions: [{ id: "thought_level", currentValue: "high" }] } }); @@ -734,9 +735,7 @@ describe("Codex ACPX runtime adapter", () => { expect(runtimeOptions!.permissionPolicy).toMatchObject({ defaultAction: "escalate" }); const turn = port.startTurn({ text: "Attempt a write.", requestId: "permission-test" }); try { - await runtimeOptions!.onPermissionRequest!({ - sessionId: "backend-1", inferredKind: "write", raw: {}, - }, { signal: new AbortController().signal }); + await runtimeOptions!.onPermissionRequest!(permissionRequest("backend-1", "edit"), { signal: new AbortController().signal }); expect(signal?.aborted).toBe(true); await expect(turn.result).rejects.toThrow("Approval required"); await expect((async () => { for await (const _event of turn.events) { /* drain */ } })()) @@ -2361,21 +2360,13 @@ describe("Codex ACPX runtime adapter", () => { ]); await expect( runtimeOptions?.onPermissionRequest?.( - { - sessionId: "session-1", - inferredKind: "execute", - raw: { _meta: { is_mcp_tool_approval: true } }, - }, + permissionRequest("session-1", "execute", { _meta: { is_mcp_tool_approval: true } }), { signal: new AbortController().signal }, ), ).resolves.toEqual({ outcome: "reject_once" }); await expect( runtimeOptions?.onPermissionRequest?.( - { - sessionId: "session-1", - inferredKind: "execute", - raw: {}, - }, + permissionRequest("session-1", "execute"), { signal: new AbortController().signal }, ), ).resolves.toEqual({ outcome: "reject_once" }); @@ -2399,11 +2390,7 @@ describe("Codex ACPX runtime adapter", () => { await expect( runtimeOptions?.onPermissionRequest?.( - { - sessionId: "session-1", - inferredKind: "write", - raw: {}, - }, + permissionRequest("session-1", "edit"), { signal: new AbortController().signal }, ), ).resolves.toEqual({ outcome: "reject_once" }); @@ -2438,11 +2425,7 @@ describe("Codex ACPX runtime adapter", () => { expect(runtimeOptions?.nonInteractivePermissions).toBe("fail"); await expect( runtimeOptions?.onPermissionRequest?.( - { - sessionId: "backend-1", - inferredKind: "write", - raw: {}, - }, + permissionRequest("backend-1", "edit"), { signal: new AbortController().signal }, ), ).resolves.toEqual(expected); @@ -3618,8 +3601,18 @@ function fakeCommand(): VerifiedAcpxCommandLease { return { spawn: vi.fn(), close: vi.fn() }; } -function fakeChild(): ChildProcess { - const child = new EventEmitter() as ChildProcess; +type FakeChildProcess = ChildProcess & { exitCode: number | null; signalCode: NodeJS.Signals | null }; + +function permissionRequest( + sessionId: string, + inferredKind: AcpPermissionRequest["inferredKind"], + raw: Record = {}, +): AcpPermissionRequest { + return { sessionId, inferredKind, raw: raw as unknown as AcpPermissionRequest["raw"] }; +} + +function fakeChild(): FakeChildProcess { + const child = new EventEmitter() as FakeChildProcess; Object.defineProperties(child, { exitCode: { value: null, writable: true }, signalCode: { value: null, writable: true }, @@ -3633,10 +3626,10 @@ function fakeChild(): ChildProcess { } function failingSignalChild(): { - child: ChildProcess; + child: FakeChildProcess; errors: [Error, Error]; } { - const child = new EventEmitter() as ChildProcess; + const child = new EventEmitter() as FakeChildProcess; const errors: [Error, Error] = [ new Error("SIGTERM delivery failed"), new Error("SIGKILL delivery failed"), @@ -3654,8 +3647,8 @@ function failingSignalChild(): { return { child, errors }; } -function childThatExitsAfterSignalCalls(exitAfter: number): ChildProcess { - const child = new EventEmitter() as ChildProcess; +function childThatExitsAfterSignalCalls(exitAfter: number): FakeChildProcess { + const child = new EventEmitter() as FakeChildProcess; Object.defineProperties(child, { exitCode: { value: null, writable: true }, signalCode: { value: null, writable: true }, @@ -3676,8 +3669,8 @@ function childThatExitsAfterSignalCalls(exitAfter: number): ChildProcess { return child; } -function stubbornChild(): ChildProcess { - const child = new EventEmitter() as ChildProcess; +function stubbornChild(): FakeChildProcess { + const child = new EventEmitter() as FakeChildProcess; Object.defineProperties(child, { pid: { value: 71_001 }, exitCode: { value: null, writable: true }, @@ -3691,8 +3684,8 @@ function stubbornChild(): ChildProcess { return child; } -function childThatExitsOnKill(): ChildProcess { - const child = new EventEmitter() as ChildProcess; +function childThatExitsOnKill(): FakeChildProcess { + const child = new EventEmitter() as FakeChildProcess; Object.defineProperties(child, { pid: { value: 71_002 }, exitCode: { value: null, writable: true }, diff --git a/packages/paperclip-runner/src/drivers/acpx/copilot-runtime-host.test.ts b/packages/paperclip-runner/src/drivers/acpx/copilot-runtime-host.test.ts index 536220811fa..cb2cf018190 100644 --- a/packages/paperclip-runner/src/drivers/acpx/copilot-runtime-host.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/copilot-runtime-host.test.ts @@ -20,7 +20,7 @@ it("reports missing Copilot authentication before spawn and releases admission o agentServerPackageJsonPath: join(root, "provider/package.json"), agentRuntimePackageJsonPath: null, openCommand }), openRuntime, reportRetainedCleanupFailure: () => {}, }; - const options = { agent: "copilot" as const, model: "explicit-model", normalizedSessionId: "copilot-auth-test", + const options = { agent: "copilot" as const, model: "explicit-model", permissionMode: "approve-reads" as const, normalizedSessionId: "copilot-auth-test", runtimeDirectory, workingDirectory, providerPolicy: { readOnly: false } }; try { await expect(AcpxRuntimeHost.open({ ...options, environment: { GH_TOKEN: "ambient-forbidden", GITHUB_TOKEN: "ambient-forbidden" } }, dependencies)) diff --git a/packages/paperclip-runner/src/drivers/acpx/cursor-extensions.test.ts b/packages/paperclip-runner/src/drivers/acpx/cursor-extensions.test.ts index 1e77f46018f..a921c5cc396 100644 --- a/packages/paperclip-runner/src/drivers/acpx/cursor-extensions.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/cursor-extensions.test.ts @@ -1,15 +1,16 @@ +/// import { mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { validateAcpxRichEvent } from "./profile-extensions.js"; -import { PAPERCLIP_QUESTION_RESPONSE_SCHEMA } from "../../contracts/question-set.js"; +import { PAPERCLIP_QUESTION_RESPONSE_SCHEMA, type PaperclipQuestionAnswer } from "../../contracts/question-set.js"; import { createCursorNotificationNormalizer, createCursorProfileExtensionAdapter, createCursorSubagentNormalizer, cursorWorkspaceArtifactReference, normalizeCursorPlanRequest, normalizeCursorQuestionRequest, normalizeCursorSubagentUpdate, } from "./cursor-extensions.js"; -const response = (answers: Record) => ({ schema: PAPERCLIP_QUESTION_RESPONSE_SCHEMA, answers }); +const response = (answers: Record) => ({ schema: PAPERCLIP_QUESTION_RESPONSE_SCHEMA, answers }); const roots: string[] = []; afterEach(async () => { await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); }); async function workspace() { const root = await mkdtemp(join(tmpdir(), "cursor-extension-")); roots.push(root); return root; } diff --git a/packages/paperclip-runner/src/drivers/acpx/cursor-permission-identity.test.ts b/packages/paperclip-runner/src/drivers/acpx/cursor-permission-identity.test.ts index 57462493056..4c95ad21263 100644 --- a/packages/paperclip-runner/src/drivers/acpx/cursor-permission-identity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/cursor-permission-identity.test.ts @@ -1,3 +1,4 @@ +/// import { spawn, type ChildProcess } from "node:child_process"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; @@ -43,7 +44,7 @@ it.each(["tool-first", "permission-first"])("real ACPX streams Cursor tool origi const evidence = createCursorToolEvidence({ sessionId: "native", turnId: "turn", workingDirectory: cwd, active: () => true, emit: event => { validateAcpxRichEvent(event); fields.push(Object.fromEntries((event.payload.details as Array<{ name: string; value: string }>).map(field => [field.name, field.value]))); }, }); - const runtime = createAcpRuntime({ cwd, agentRegistry: createAgentRegistry({ overrides: { cursor: "fixture" } }), sessionStore: createRuntimeStore({ stateDir: join(cwd, "state") }), permissionMode: "approve-reads", authPolicy: "skip", + const runtime = createAcpRuntime({ cwd, agentRegistry: createAgentRegistry({ overrides: { cursor: "fixture" } }), sessionStore: createRuntimeStore({ stateDir: join(cwd, "state") }), permissionMode: "approve-reads", spawnAgent: () => { const child = spawn(process.execPath, ["-e", peer], { cwd, env: { ORDER: order }, stdio: ["pipe", "pipe", "pipe"] }); children.push(child); return child; }, onAcpMessage: (direction, value) => { const message = value as { id?: number; result?: { outcome?: unknown } }; diff --git a/packages/paperclip-runner/src/drivers/acpx/cursor-sdk-interactions.test.ts b/packages/paperclip-runner/src/drivers/acpx/cursor-sdk-interactions.test.ts index 1566e01cd7d..8bf956f9af7 100644 --- a/packages/paperclip-runner/src/drivers/acpx/cursor-sdk-interactions.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/cursor-sdk-interactions.test.ts @@ -1,3 +1,4 @@ +/// import { spawn } from "node:child_process"; import { createRequire } from "node:module"; import { mkdtemp, rm } from "node:fs/promises"; diff --git a/packages/paperclip-runner/src/drivers/acpx/cursor-tool-evidence.test.ts b/packages/paperclip-runner/src/drivers/acpx/cursor-tool-evidence.test.ts index 0c208001bc6..5410fced811 100644 --- a/packages/paperclip-runner/src/drivers/acpx/cursor-tool-evidence.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/cursor-tool-evidence.test.ts @@ -34,7 +34,7 @@ it.each(["tool-first", "permission-first"])("correlates opaque C0/C1/DEL native expect(expectedIds.at(-1)).toBe("safe-tool-1"); for (const [index, toolCallId] of rawIds.entries()) { const native = { sessionId: "session", inferredKind: "execute" as const, raw: { - sessionId: "session", toolCall: { toolCallId, kind: "execute" }, + sessionId: "session", toolCall: { toolCallId, kind: "execute" as const }, options: [{ kind: "reject_once" as const, optionId: `native-denial-${index}`, name: "Deny" }], } }; const before = structuredClone(native); @@ -99,7 +99,7 @@ it("ignores inactive turns and never lets observation errors undo a delivered de }); it("bounds retained tool origins", () => { const s = setup(); for (let i = 0; i < 257; i++) s.p.tool({ ...initial, toolCallId: `tool-${i}` }); - expect(s.fields().at(-1).stage).toBe("evidence_incomplete"); expect(s.events).toHaveLength(257); + expect(s.fields().at(-1)!.stage).toBe("evidence_incomplete"); expect(s.events).toHaveLength(257); }); it("rejects commands missing from their original frame and bounded oversized input", () => { @@ -159,5 +159,5 @@ it("preserves execute denial evidence after valid edit and read permission input it.each(["edit", "read", "unknown-native-kind"])("still rejects a contradictory permission kind %s", kind => { const s = setup(); s.p.tool(initial); s.p.permission({ raw: { sessionId: "session", toolCall: { toolCallId: "tool", kind, rawInput: { path: "/fixture/file" } } } }, "request", ["decline"]); - expect(s.fields().at(-1).stage).toBe("evidence_incomplete"); expect(s.unavailable()).toBe(true); + expect(s.fields().at(-1)!.stage).toBe("evidence_incomplete"); expect(s.unavailable()).toBe(true); }); diff --git a/packages/paperclip-runner/src/drivers/acpx/grok-native-session-options.test.ts b/packages/paperclip-runner/src/drivers/acpx/grok-native-session-options.test.ts index 2b2e8798eec..317feaf02cd 100644 --- a/packages/paperclip-runner/src/drivers/acpx/grok-native-session-options.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/grok-native-session-options.test.ts @@ -7,6 +7,11 @@ import { expect, it } from "vitest"; import { createGrokEffortAdmission } from "./grok-effort.js"; import { acpxSidecarErrorCode } from "../../cli/acpx-sidecar-input.js"; +// The patched acpx runtime implements this method without declaring it. +interface ExtensionRuntime { + requestExtension(input: { handle: AcpRuntimeHandle; method: string; params: Record; sessionMode?: "persistent" | "oneshot" }): Promise>; +} + it("retains a native Grok acknowledgement failure through the real ACPX connection", async () => { const root = await mkdtemp(join(tmpdir(), "grok-rejected-admission-")); const fixture = join(root, "grok.cjs"); @@ -94,7 +99,7 @@ it.each(["low", "high"] as const)("transports and restores native Grok %s effort handle = await runtime.ensureSession(input); expect(handle.backendSessionId).toBe(originalSession); expect(admission.isReady()).toBe(false); - await runtime.requestExtension({ handle, method: "_x.ai/session/info", params: { sessionId: handle.backendSessionId }, sessionMode: "persistent" }); + await (runtime as typeof runtime & ExtensionRuntime).requestExtension({ handle, method: "_x.ai/session/info", params: { sessionId: handle.backendSessionId }, sessionMode: "persistent" }); admission.assertReady(); const turn = runtime.startTurn({ handle, text: "Verify the restored fixture", mode: "prompt", requestId: "grok-cold-turn", timeoutMs: 5_000 }); const output: string[] = []; 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 index 397cd56a238..61cd382955b 100644 --- a/packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.test.ts @@ -686,7 +686,7 @@ describe("native ACPX execution closure", () => { const fences = await Promise.all([listen(), listen()]); const fds = fences.map(server => (server as Server & { _handle?: { fd?: number } })._handle!.fd!); const installation = await verifyNativeAcpxInstallation(declaration); - const owner = createAcpxCommandLeaseOwner(await installation.openCommand(), installation.openCommand); + const owner = createAcpxCommandLeaseOwner(await installation.openCommand(), signal => installation.openCommand({ signal })); const pidFile = join(declaration.distributionRoot, "native-pid"); let port: Awaited> | undefined; let deadline: ReturnType | undefined; diff --git a/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.test.ts b/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.test.ts index c11999afac8..d5e4ccca82c 100644 --- a/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.test.ts @@ -4,7 +4,7 @@ import { mkdtemp, mkdir, realpath, rename, rm, symlink, writeFile } from "node:f import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; -import Ajv from "ajv"; +import { Ajv } from "ajv"; import { checkPiNativeTool, installPiRuntimeExtension, piMcpRequest, readPiRuntimeConfiguration, PI_NATIVE_QUESTION_TOOL, PI_PERMISSION_TITLE_PREFIX, type PiExtensionApi, type PiRuntimeConfiguration, type PiToolDefinition, @@ -12,7 +12,7 @@ import { const directories: string[] = []; afterEach(async () => { for (const path of directories.splice(0)) await rm(path, { recursive: true, force: true }); }); -async function workspace() { +async function workspace(): Promise<{ root: string; config: PiRuntimeConfiguration }> { const root = await mkdtemp(join(tmpdir(), "paperclip-pi-extension-")); directories.push(root); await mkdir(join(root, "workspace")); await mkdir(join(root, "skills")); await mkdir(join(root, "private")); return { root, config: { @@ -20,7 +20,7 @@ async function workspace() { workspace: join(root, "workspace"), readOnly: false, readRoots: [join(root, "skills")], protectedRoots: [join(root, "private")], instructions: "Use the assigned Paperclip tools", servers: [], - } satisfies PiRuntimeConfiguration }; + } }; } function harness() { const handlers = new Map any>(); @@ -241,7 +241,7 @@ describe("owned Pi runtime extension", () => { it("revalidates paths after a human wait and never treats cancellation as approval", async () => { const { root, config } = await workspace(); const h = harness(); await installPiRuntimeExtension(h.api, config); h.handlers.get("turn_start")!(); - const context = { cwd: config.workspace, ui: { select: vi.fn(async () => { + const context = { cwd: config.workspace, ui: { select: vi.fn(async (_title: string) => { await symlink(join(root, "private"), join(config.workspace, "target")); return "Allow once"; }) } }; diff --git a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts index b2edd4c6367..cb38eec8b12 100644 --- a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts @@ -68,7 +68,7 @@ describe("historical ACPX profile decoding", () => { ["copilot", "copilot-profile-v85-cursor-cloud-qualification-historical-identity.json", "977d68b327f3964e8337ef8ba00230eb1b1697017db21f8010f664fee3d3c70a", "private Cloud qualification"], ["grok", "grok-profile-v14-identity.json", "08294ec6bdcbdbe46c8c8b711d8b368558a05f0d84d25c9f5fd0c3b67b79cd17", "published ed8"], ["grok", "grok-profile-v14-cursor-cloud-qualification-historical-identity.json", "fa34ba395ff51affed7e7195dc44f4e33a3c6718ada1c80e8e36852cf7af40ac", "private Cloud qualification"], - ] as const)("keeps %s identity %s immutable and decode-only", (agent, filename, checksum) => { + ] as const)("keeps %s identity %s immutable and decode-only", (agent, filename, checksum, _source) => { const bytes = readFileSync(new URL(`../../../test/fixtures/${filename}`, import.meta.url)); expect(createHash("sha256").update(bytes).digest("hex")).toBe(checksum); const identity = JSON.parse(bytes.toString("utf8")); diff --git a/packages/paperclip-runner/src/drivers/acpx/response-delivery.test.ts b/packages/paperclip-runner/src/drivers/acpx/response-delivery.test.ts index 8e2073ec064..06f721541a7 100644 --- a/packages/paperclip-runner/src/drivers/acpx/response-delivery.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/response-delivery.test.ts @@ -1,3 +1,4 @@ +/// import { describe, expect, it, vi } from "vitest"; import { deliverAcpxResponse, requireAcpxResponseDelivery } from "./response-delivery.js"; 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 3c663b40447..793aadce056 100644 --- a/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts @@ -272,7 +272,7 @@ describe("ACPX runtime host", () => { it("rejects preconfigured Codex before admission resources or runtime launch", async () => { const fixture = await hostFixture(); const openRuntime = vi.fn(), prepareSandbox = vi.fn(); - await expect(AcpxRuntimeHost.open({ ...fixture.options, agent: "codex", permissionMode: "approve-all", + await expect(AcpxRuntimeHost.open({ ...fixture.options, agent: "codex", model: "gpt-5.6-sol", permissionMode: "approve-all", environment: { HOME: fixture.root, PAPERCLIP_RUNNER_HOME_MODE: "preconfigured" }, }, fixture.dependencies({ openRuntime, prepareSandbox }))).rejects.toThrow("cannot protect Runner state"); expect(prepareSandbox).not.toHaveBeenCalled(); @@ -282,7 +282,7 @@ describe("ACPX runtime host", () => { it.each(["claude", "codex"] as const)("rejects preconfigured read-only %s before provider admission", async agent => { const fixture = await hostFixture(); const openRuntime = vi.fn(); - await expect(AcpxRuntimeHost.open({ ...fixture.options, agent, permissionMode: "approve-all", + await expect(AcpxRuntimeHost.open({ ...fixture.options, agent, model: "explicit-test-model", permissionMode: "approve-all", providerPolicy: { readOnly: true }, environment: { HOME: fixture.root, PAPERCLIP_RUNNER_HOME_MODE: "preconfigured" }, }, fixture.dependencies({ openRuntime }))).rejects.toThrow("preconfigured ACPX read-only execution is not supported"); expect(openRuntime).not.toHaveBeenCalled(); @@ -291,7 +291,7 @@ describe("ACPX runtime host", () => { it.each(["approve-reads", "approve-paperclip", "deny-all"] as const)("rejects unqualified preconfigured permission mode %s before admission", async permissionMode => { const fixture = await hostFixture(); const openRuntime = vi.fn(); - await expect(AcpxRuntimeHost.open({ ...fixture.options, permissionMode, + await expect(AcpxRuntimeHost.open({ ...fixture.options, agent: "claude", model: "claude-sonnet-5", permissionMode, environment: { HOME: fixture.root, PAPERCLIP_RUNNER_HOME_MODE: "preconfigured" }, }, fixture.dependencies({ openRuntime }))).rejects.toThrow("requires ACPX approve-all"); expect(openRuntime).not.toHaveBeenCalled(); @@ -379,7 +379,7 @@ describe("ACPX runtime host", () => { ])("rejects invalid assigned connection bindings before runtime launch", async (environment) => { const fixture = await hostFixture(); const openRuntime = vi.fn(); - await expect(AcpxRuntimeHost.open({ ...fixture.options, environment }, fixture.dependencies({ openRuntime }))).rejects.toThrow(/assigned native MCP/); + await expect(AcpxRuntimeHost.open({ ...fixture.options, agent: "claude", model: "claude-sonnet-5", permissionMode: "approve-reads", environment }, fixture.dependencies({ openRuntime }))).rejects.toThrow(/assigned native MCP/); expect(openRuntime).not.toHaveBeenCalled(); }); @@ -491,7 +491,8 @@ describe("ACPX runtime host", () => { const host = await AcpxRuntimeHost.open({ ...fixture.options, agent: "claude", model: "claude-sonnet-5", - environment: { ...fixture.options.environment, + permissionMode: "approve-reads", + environment: { PAPERCLIP_NATIVE_MCP_NAME: "paperclip-assigned", PAPERCLIP_NATIVE_MCP_URL: "http://127.0.0.1:3211/mcp/gateway", PAPERCLIP_NATIVE_MCP_TOKEN: "fixture-gateway-token-".repeat(3), @@ -518,7 +519,7 @@ describe("ACPX runtime host", () => { let servers: AcpxRuntimePortOpenOptions["mcpServers"] = []; const host = await AcpxRuntimeHost.open({ ...fixture.options, agent: "claude", model: "claude-sonnet-5", permissionMode: "approve-reads", providerPolicy: { readOnly }, - environment: { ...fixture.options.environment, PAPERCLIP_NATIVE_MCP_STDIO: JSON.stringify([binding]), PAPERCLIP_NATIVE_MCP_PROXY: "/verified/runnerd" }, + environment: { PAPERCLIP_NATIVE_MCP_STDIO: JSON.stringify([binding]), PAPERCLIP_NATIVE_MCP_PROXY: "/verified/runnerd" }, }, fixture.dependencies({ openRuntime: async options => { servers = options.mcpServers; return runtimePort({ getStatus: async () => ({ models: { currentModelId: "claude-sonnet-5" } }) }); @@ -1042,12 +1043,12 @@ describe("ACPX runtime host", () => { const events: CanonicalProviderEvent[] = []; const evidence = createCopilotToolEvidence({ sessionId: "backend-1", turnId: "turn-1", workingDirectory: fixture.options.workingDirectory, active: () => true, emit: event => { validateAcpxRichEvent(event); events.push(JSON.parse(JSON.stringify(event))); } }); - let bridge: AcpxRuntimePortOpenOptions["mcpServers"][number] | undefined; + let bridge: Extract | undefined; const host = await AcpxRuntimeHost.open({ ...fixture.options, agent: "copilot", model: "gpt-5.6-sol", permissionMode: "deny-all", environment: { COPILOT_GITHUB_TOKEN: "fixture-not-a-real-credential" }, semanticTools: { tools: [{ name: "get_task_context", inputSchema: { type: "object", properties: {}, additionalProperties: false } }], handler: async () => ({ accepted: false }), captureSemanticReceipt: () => evidence.captureSemanticReceipt() }, - }, fixture.dependencies({ openRuntime: async options => { bridge = options.mcpServers[0]; return runtimePort(); } })); + }, fixture.dependencies({ openRuntime: async options => { bridge = urlBridge(options.mcpServers[0]); return runtimePort(); } })); try { const response = await fetch(bridge!.url, { method: "POST", headers: { Authorization: `Bearer ${bridge!.bearerToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 3, method: "tools/call", params: { name: "get_task_context", arguments: {} } }) }); @@ -1092,7 +1093,7 @@ describe("ACPX runtime host", () => { }, fixture.dependencies({ openRuntime: async (options) => { - bridge = options.mcpServers[0]; + bridge = urlBridge(options.mcpServers[0]); return runtimePort(); }, }), @@ -1990,10 +1991,10 @@ describe("ACPX runtime host", () => { it("retains a settled turn when shutdown cleanup must be retried", async () => { const fixture = await hostFixture(); const credential = credentialLeaseFixture(fixture.root); - let settleTurn!: (value: { stopReason: string }) => void; + let settleTurn!: (value: Awaited) => void; const turn = { ...runtimeTurn(), - result: new Promise<{ stopReason: string }>((resolve) => { + result: new Promise>((resolve) => { settleTurn = resolve; }), }; @@ -2019,7 +2020,7 @@ describe("ACPX runtime host", () => { host.startTurn({ text: "Complete the task.", requestId: "turn-1" }); const firstClose = host.close({ reason: "first shutdown" }); - settleTurn({ stopReason: "end_turn" }); + settleTurn({ status: "completed", stopReason: "end_turn" }); await expect(firstClose).rejects.toThrow(/cleanup failed/); expect(turn.cancel).toHaveBeenCalledOnce(); expect(credential.close).not.toHaveBeenCalled(); @@ -2332,6 +2333,11 @@ function credentialLeaseFixture(root: string) { }; } +function urlBridge(server: AcpxRuntimePortOpenOptions["mcpServers"][number] | undefined) { + if (!server || !("url" in server)) throw new Error("missing tool bridge"); + return server; +} + function runtimePort( input: { identity?: AcpxRuntimePort["identity"]; @@ -2402,7 +2408,7 @@ async function hostFixture() { dependencies( input: Pick & Partial< - Pick + Pick > & { // Opt-in only. When true, `prepareSandbox` runs the real // preparation once, then returns that same sandbox for every @@ -2413,19 +2419,19 @@ async function hostFixture() { ): AcpxRuntimeHostDependencies { let reusedSandbox: AcpxRuntimeSandbox | null = null; return { - verifyInstallation: async (profile) => + verifyInstallation: input.verifyInstallation ?? (async (profile) => ({ commandDigest: profile.commandDigest, agentServerPackageJsonPath: join(root, "package.json"), agentRuntimePackageJsonPath: null, openCommand: async () => command, - }) satisfies VerifiedAcpxInstallation, + }) satisfies VerifiedAcpxInstallation), openRuntime: input.openRuntime, // Record the skills home the instant the sandbox exists, ahead of // the materialize call that seals it. A test that overrides // `prepareSandbox` for a non-Claude agent replaces this wrapper, but // those agents never materialize skills, so nothing is lost. - prepareSandbox: async (sandboxInput) => { + prepareSandbox: input.prepareSandbox ?? (async (sandboxInput) => { if (input.reuseSandbox && reusedSandbox) return reusedSandbox; const sandbox = await prepareAcpxRuntimeSandbox(sandboxInput); if (sandboxInput.agent === "claude") { @@ -2433,7 +2439,7 @@ async function hostFixture() { } if (input.reuseSandbox) reusedSandbox = sandbox; return sandbox; - }, + }), retainAdmissionCleanup: trackAdmissionCleanup, reportRetainedCleanupFailure: input.reportRetainedCleanupFailure ?? vi.fn(), diff --git a/packages/paperclip-runner/src/drivers/codex/app-server-transport.test.ts b/packages/paperclip-runner/src/drivers/codex/app-server-transport.test.ts index ddeef5fc806..b42c13323d0 100644 --- a/packages/paperclip-runner/src/drivers/codex/app-server-transport.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/app-server-transport.test.ts @@ -128,10 +128,11 @@ describe("Codex app-server transport limits", () => { }); it("installs cleanup handlers before synchronously reporting process ownership", async () => { - const originalSpawn = ChildProcess.prototype.spawn; + const childProcessPrototype = ChildProcess.prototype as ChildProcess & { spawn(...args: unknown[]): unknown }; + const originalSpawn = childProcessPrototype.spawn; let spawnedProcess: ChildProcess | undefined; const spawnSpy = vi - .spyOn(ChildProcess.prototype, "spawn") + .spyOn(childProcessPrototype, "spawn") .mockImplementation(function (this: ChildProcess, ...args) { spawnedProcess = this; return originalSpawn.apply(this, args); diff --git a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.context-control.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.context-control.test.ts index 719c7035661..13e83500aba 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.context-control.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.context-control.test.ts @@ -660,7 +660,7 @@ describe("Codex app-server Codex driver", () => { ).rejects.toBeInstanceOf(HarnessCapabilityUnavailableError); for (const action of ["set", "pause", "resume", "clear"] as const) { await expect( - unsupported.goal?.({ action, ...(action === "set" ? { objective: "Must not reach the provider" } : {}) }), + unsupported.goal?.(action === "set" ? { action, objective: "Must not reach the provider" } : { action }), ).rejects.toBeInstanceOf(HarnessCapabilityUnavailableError); } expect(unsupportedTransport.calls.filter(({ method }) => diff --git a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.events.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.events.test.ts index 142955c787c..19e218768af 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.events.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.events.test.ts @@ -401,7 +401,7 @@ describe("Codex app-server Codex driver", () => { { runDelta: first, runDeltaComplete: true }, { runDelta: { ...first, ...second }, runDeltaComplete: false }, ]); - expect(await session.usage()).toMatchObject({ runDeltaComplete: false }); + expect(await session.usage!()).toMatchObject({ runDeltaComplete: false }); }); it("keeps usage reported between runs in the next run's delta so totals stay exact", async () => { @@ -478,7 +478,7 @@ describe("Codex app-server Codex driver", () => { expect(events.some((event) => event.eventType === "session.failed")).toBe( false, ); - expect(await session.usage()).toMatchObject({ + expect(await session.usage!()).toMatchObject({ total: { inputTokens: 10, outputTokens: 4 }, }); }); diff --git a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.lifecycle.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.lifecycle.test.ts index 4cb21eaa922..a0baee374a6 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.lifecycle.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.lifecycle.test.ts @@ -785,6 +785,7 @@ describe("Codex app-server Codex driver", () => { runId: "run-1", environmentLeaseId: "lease-1", runnerInstanceId: "runner-codex", + driverSessionId: "thread-1", normalizedSessionId: "normalized-1", }, capabilities, @@ -819,7 +820,7 @@ it("sends explicit skill input on every requested turn", async () => { await collectUntilTerminal(session.events()); transport.turnStartResponse = Promise.resolve({ turn: { id: "turn-2", status: "inProgress", items: [] } }); } - await session.close(); + await session.close({ reason: "fixture complete" }); }); @@ -830,7 +831,7 @@ it("reapplies explicit skills after recovery without persisting them into ordina runId: "initial", normalizedSessionId: "skill-recovery", workingDirectory: WORKSPACE, }); const snapshot = await original.snapshot(); - await original.close(); + await original.close({ reason: "fixture complete" }); for (const selected of [skillInputs, []]) { const transport = new FakeCodexTransport(); const recovery = await makeDriver([transport], { skillInputs: selected }).recoverSession(snapshot); @@ -840,6 +841,6 @@ it("reapplies explicit skills after recovery without persisting them into ordina await session.startTurn({ message: { role: "user", text: "Continue" } }); const items = transport.calls.find((call) => call.method === "turn/start")?.params.input as Array<{type:string}>; expect(items.filter((item) => item.type === "skill")).toEqual(selected); - } finally { await session.close(); } + } finally { await session.close({ reason: "fixture complete" }); } } }); diff --git a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.runtime-requests.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.runtime-requests.test.ts index ada36dcf876..c3b718401ea 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.runtime-requests.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.runtime-requests.test.ts @@ -76,7 +76,7 @@ it("preserves runnerd permission expiry before a failed provider terminal and re expect(resolve).not.toHaveBeenCalled(); transport.push("turn/completed", { threadId: "thread-1", turn: { id: turnId, status: "failed", error: { code: "ACPX_PROVIDER_PROCESS_LOST", message: "Provider died" } } }); await vi.waitFor(() => expect(events.some(event => event.eventType === "turn.failed")).toBe(true)); - await session.close(); await consume; + await session.close({ reason: "fixture complete" }); await consume; const settlements = events.filter(event => ["runtime_request.expired", "runtime_request.cancelled", "runtime_request.resolved"].includes(event.eventType)); expect(settlements).toHaveLength(1); expect(settlements[0]).toMatchObject({ eventType: "runtime_request.expired", turnId, itemId: "native-edit", payload: { ...payload, turnId, itemId: "native-edit" } }); @@ -105,7 +105,7 @@ it("preserves canonical question expiry after provider loss and refuses late ans await expect(session.resolveRuntimeRequest?.({ requestId: "native-question", turnId, resolution: { action: "accept" } })).rejects.toThrow(); transport.push("turn/completed", { threadId: "thread-1", turn: { id: turnId, status: "failed", error: { code: "ACPX_PROVIDER_PROCESS_LOST", message: "Provider died" } } }); await vi.waitFor(() => expect(events.some(event => event.eventType === "turn.failed" || event.eventType === "session.failed")).toBe(true)); - await session.close(); await consume; + await session.close({ reason: "fixture complete" }); await consume; expect(events.filter(event => event.eventType === "runtime_request.expired")).toHaveLength(1); expect(events.find(event => event.eventType === "runtime_request.expired")).toMatchObject({ turnId, itemId: "question-item", payload }); expect(events.some(event => event.eventType === "session.failed" && event.payload.code === "turn_binding_mismatch")).toBe(false); @@ -127,7 +127,7 @@ it.each(["turn", "kind", "type", "replay"])("refuses canonical permission expiry requestType: kind === "type" ? "input" : "permission", replayAllowed: kind === "replay", reason: "provider_process_lost" }; const notification = runnerdRuntimeRequestExpiryNotification("runtime_request.expired", payload, "thread-1", kind === "turn" ? "foreign-turn" : turnId)!; transport.push(notification.method, notification.params); - await pending; await consume; await session.close(); + await pending; await consume; await session.close({ reason: "fixture complete" }); expect(events.some(e => e.eventType === "runtime_request.expired")).toBe(false); expect(events.some(e => e.eventType === "session.failed" && e.payload.code === "turn_binding_mismatch")).toBe(true); }); @@ -159,7 +159,7 @@ describe("Codex app-server Codex driver", () => { }); expect(await response).toEqual({ action: "accept", content: null, _meta: null }); expect(session.pendingRuntimeRequests?.()).toHaveLength(0); - await session.close(); + await session.close({ reason: "fixture complete" }); }); it("validates runtime request resolutions against the kind of request they answer", async () => { diff --git a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.semantic-results.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.semantic-results.test.ts index 8982c48fd5e..7c470f79972 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.semantic-results.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.semantic-results.test.ts @@ -68,7 +68,7 @@ describe("Codex app-server Codex driver", () => { threadId: "thread-1", turnId: "turn-1", arguments: {} }); expect(feedback).toHaveBeenCalledExactlyOnceWith({ ...report, continuation }); expect((await session.snapshot()).semanticResult?.result).toEqual({ ...report, continuation }); - } finally { await session.close(); } + } finally { await session.close({ reason: "fixture complete" }); } }); it.each([ @@ -92,7 +92,7 @@ describe("Codex app-server Codex driver", () => { expect(response).toMatchObject({ success: false }); expect(feedback).not.toHaveBeenCalled(); expect((await session.snapshot()).semanticResult).toBeNull(); - } finally { await session.close(); } + } finally { await session.close({ reason: "fixture complete" }); } }); it("returns current approval feedback and permits correcting a rejected completion report", async () => { @@ -111,7 +111,7 @@ describe("Codex app-server Codex driver", () => { expect(await call("corrected")).toMatchObject({ success: true, contentItems: [{ type: "inputText", text: expect.stringContaining("/approvals/approval-1") }] }); expect((await session.snapshot()).semanticResult?.result).toEqual(result); - await session.close(); + await session.close({ reason: "fixture complete" }); }); it("accepts an explicit response-wake yield through paperclip_finish", async () => { @@ -194,7 +194,7 @@ describe("Codex app-server Codex driver", () => { }, }, })).toMatchObject({ success: false }); - await otherSession.close(); + await otherSession.close({ reason: "fixture complete" }); }); it("makes duplicate semantic completion idempotent and rejects changed payloads", async () => { diff --git a/packages/paperclip-runner/src/drivers/codex/codex-protocol-integrity.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-protocol-integrity.test.ts index 20dbb749a2a..080b2717bbd 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-protocol-integrity.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-protocol-integrity.test.ts @@ -16,8 +16,8 @@ import { NativeSessionProtocolIntegrityError } from "../../contracts/native-sess import { DurablePrpControlPlane, durableRecoveryInternals, - type DurableRecoveryIdentity, } from "../../control-plane/durable-prp-control-plane.js"; +import type { DurableRecoveryIdentity } from "../../control-plane/prp-transport-types.js"; import { createCapabilityRunnerdCodexTransport } from "../../live/runnerd-codex-transport.js"; import { executeNativeSession } from "../../native-session-runtime.js"; import { CodexAppServerDriver } from "./codex-app-server-driver.js"; @@ -44,7 +44,7 @@ async function authenticatedRunner( runnerBinary: string, ) { const framed = (domain: string, parts: Buffer[]) => { - const values = [Buffer.from(domain), Buffer.from([0])]; + const values: Buffer[] = [Buffer.from(domain), Buffer.from([0])]; for (const part of parts) { const length = Buffer.alloc(8); length.writeBigUInt64BE(BigInt(part.length)); @@ -476,6 +476,7 @@ describe("Codex protocol integrity propagation", () => { normalizedSessionId: identity.normalizedSessionId, driverKind: "codex_app_server", protocolVersion: 1, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, }, provider: { kind: "codex", model: null }, completionContract: { diff --git a/packages/paperclip-runner/src/drivers/codex/codex-question-adapter.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-question-adapter.test.ts index 9297dbd2c0f..c38675e3186 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-question-adapter.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-question-adapter.test.ts @@ -335,7 +335,7 @@ describe("Codex structured question adapter", () => { questions: [{ id: "form", question: "Choose one" }], }); expect(safeForm?.description).toContain("Bearer [REDACTED]"); - expect(safeForm?.description.endsWith(preservedTail)).toBe(true); + expect(safeForm?.description?.endsWith(preservedTail)).toBe(true); expect(() => normalizeCodexQuestionSet("mcpServer/elicitation/request", { requestedSchema: { diff --git a/packages/paperclip-runner/src/drivers/codex/codex-resume-usage.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-resume-usage.test.ts index 7e986c46baf..54d325146fb 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-resume-usage.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-resume-usage.test.ts @@ -35,7 +35,7 @@ describe("Codex resume accounting through the production driver", () => { turnId: string, ) => { const saved = JSON.parse(JSON.stringify(await session.snapshot!())); - await session.close(); + await session.close({ reason: "test complete" }); transport.readResponse = { thread: { id: "thread-1", sessionId: "provider-session-1", cwd: WORKSPACE, turns: [] }, }; @@ -60,7 +60,7 @@ describe("Codex resume accounting through the production driver", () => { session = await coldRestart(session, transports[2]!, "third", "turn-3"); await expect(runTurn(session, transports[2]!, "turn-3", { inputTokens: 170, outputTokens: 20 })) .resolves.toEqual({ inputTokens: 30, outputTokens: 4 }); - await session.close(); + await session.close({ reason: "test complete" }); }); it.each(["thread", "snapshot"] as const)( @@ -137,7 +137,7 @@ describe("Codex resume accounting through the production driver", () => { expect(await session.usage!()).toMatchObject({ runDelta: { inputTokens: 40, outputTokens: 6 }, }); - await session.close(); + await session.close({ reason: "fixture complete" }); second.readResponse = readResponse(140, 16); const recovery = await driver.recoverSession(persisted); expect(recovery.recovered).toBe(true); @@ -187,7 +187,7 @@ describe("Codex resume accounting through the production driver", () => { inputTokens: 140, outputTokens: 16, }); - await recoveredSession.close(); + await recoveredSession.close({ reason: "fixture complete" }); third.readResponse = readResponse(150, 19); const secondRecovery = await driver.recoverSession(advanced); expect(secondRecovery.recovered).toBe(true); @@ -205,8 +205,8 @@ describe("Codex resume accounting through the production driver", () => { ), ).toEqual([]); } finally { - await recoveredSession?.close(); - await session.close(); + await recoveredSession?.close({ reason: "fixture complete" }); + await session.close({ reason: "fixture complete" }); } }, ); @@ -245,7 +245,7 @@ describe("Codex resume accounting through the production driver", () => { baseline: { inputTokens: 100 }, latest: { inputTokens: 140 }, }; - await session.close(); + await session.close({ reason: "fixture complete" }); const tokenUsage = { total: { inputTokens: 150 }, last: { inputTokens: 10 }, @@ -270,8 +270,8 @@ describe("Codex resume accounting through the production driver", () => { second.calls.filter((call) => call.method === "turn/start"), ).toEqual([]); } finally { - await recoveredSession?.close(); - await session.close(); + await recoveredSession?.close({ reason: "fixture complete" }); + await session.close({ reason: "fixture complete" }); } }, ); @@ -300,7 +300,7 @@ describe("Codex resume accounting through the production driver", () => { }); await collectUntilTerminal(session.events()); const persisted = JSON.parse(JSON.stringify(await session.snapshot!())); - await session.close(); + await session.close({ reason: "fixture complete" }); second.readResponse = { thread: { id: "thread-1", @@ -384,7 +384,7 @@ describe("Codex resume accounting through the production driver", () => { .filter((call) => call.method === "thread/read") .every((call) => call.params.includeTurns === false), ).toBe(true); - await session.close(); + await session.close({ reason: "fixture complete" }); }); it("keeps usage reported between runs in the next run's delta after a restore", async () => { @@ -401,7 +401,7 @@ describe("Codex resume accounting through the production driver", () => { await new Promise(resolve => setImmediate(resolve)); const persisted = JSON.parse(JSON.stringify(await session.snapshot!())); expect(persisted.codexUsageBaseline).toMatchObject({ latest: { inputTokens: 130 }, accounted: { inputTokens: 100 } }); - await session.close(); + await session.close({ reason: "test complete" }); second.readResponse = { thread: { id: "thread-1", sessionId: "provider-session-1", cwd: WORKSPACE, turns: [{ id: "turn-1", status: "completed", items: [] }], tokenUsage: { total: { inputTokens: 130, outputTokens: 10 } } } }; const recovery = await driver.recoverSession(persisted); @@ -413,7 +413,7 @@ describe("Codex resume accounting through the production driver", () => { second.push("thread/tokenUsage/updated", { threadId: "thread-1", turnId: "turn-2", tokenUsage: { total: { inputTokens: 150, outputTokens: 16 } } }); for await (const event of recovered.events()) if (event.payload.kind === "usage") break; expect(await recovered.usage!()).toMatchObject({ runDelta: { inputTokens: 50, outputTokens: 6 } }); - await recovered.close(); + await recovered.close({ reason: "test complete" }); }); it("keeps the startup external sandbox profile on subsequent turns", async () => { @@ -439,6 +439,6 @@ describe("Codex resume accounting through the production driver", () => { transport.calls.find((call) => call.method === "turn/start")?.params .permissions, ).toBe(start.params.permissions); - await session.close(); + await session.close({ reason: "fixture complete" }); }); }); diff --git a/packages/paperclip-runner/src/drivers/codex/codex-turn-diff.test.ts b/packages/paperclip-runner/src/drivers/codex/codex-turn-diff.test.ts index c7cc22e1663..79849db1dba 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-turn-diff.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-turn-diff.test.ts @@ -1,17 +1,19 @@ import { describe, expect, it } from "vitest"; -import { parseCodexTurnDiff, summarizeCodexTurnDiff } from "./codex-turn-diff.js"; +import { parseCodexTurnDiff, summarizeCodexTurnDiff, type ParsedCodexTurnDiffFile } from "./codex-turn-diff.js"; describe("Codex turn diff parser", () => { it("summarizes parsed files for work-product metadata", () => { - expect(summarizeCodexTurnDiff([ + const textFiles: ParsedCodexTurnDiffFile[] = [ { path: "a.ts", operation: "modify", previousPath: null, additions: 4, deletions: 2, binary: false, diff: "patch" }, { path: "b.ts", operation: "create", previousPath: null, additions: 3, deletions: 0, binary: false, diff: "patch" }, - ])).toEqual({ files: 2, additions: 7, deletions: 2 }); + ]; + expect(summarizeCodexTurnDiff(textFiles)).toEqual({ files: 2, additions: 7, deletions: 2 }); - expect(summarizeCodexTurnDiff([ + const binaryFiles: ParsedCodexTurnDiffFile[] = [ { path: "logo.png", operation: "modify", previousPath: null, additions: null, deletions: null, binary: true, diff: null }, - ])).toEqual({ files: 1, additions: null, deletions: null }); + ]; + expect(summarizeCodexTurnDiff(binaryFiles)).toEqual({ files: 1, additions: null, deletions: null }); }); it("parses a complete snapshot with bounded file statistics", () => { diff --git a/packages/paperclip-runner/src/drivers/codex/prepared-context-transport.test.ts b/packages/paperclip-runner/src/drivers/codex/prepared-context-transport.test.ts index dadddbad2b6..5a4a522dc0e 100644 --- a/packages/paperclip-runner/src/drivers/codex/prepared-context-transport.test.ts +++ b/packages/paperclip-runner/src/drivers/codex/prepared-context-transport.test.ts @@ -91,7 +91,7 @@ describe("prepared native context transport", () => { text: expect.stringContaining('"task"'), }), ]); - expect((input?.[0] as { text: string }).text).toContain( + expect((input as Array<{ text: string }>)[0]!.text).toContain( '"message":"ordinary standalone task"', ); }); diff --git a/packages/paperclip-runner/src/drivers/cursor-cloud/app-server-proxy.test.ts b/packages/paperclip-runner/src/drivers/cursor-cloud/app-server-proxy.test.ts index 72fe00b3ec6..2cffbfa7c91 100644 --- a/packages/paperclip-runner/src/drivers/cursor-cloud/app-server-proxy.test.ts +++ b/packages/paperclip-runner/src/drivers/cursor-cloud/app-server-proxy.test.ts @@ -87,7 +87,7 @@ describe("Cursor Cloud bounded stdio proxy", () => { const terminalGate = new Promise(resolve => { releaseTerminalWrites = resolve; }); let pausedTerminalWrites = 0; const write = CursorCloudCheckpointStore.prototype.write; - vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(async function(value) { + vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(async function(this: CursorCloudCheckpointStore, value) { if (value.phase === "terminal" && value.pendingTerminalDelivery) { pausedTerminalWrites += 1; await terminalGate; @@ -151,7 +151,7 @@ describe("Cursor Cloud bounded stdio proxy", () => { }); let openedSession!: CursorCloudSessionHandle; const openSession = CursorCloudDriver.prototype.openSession; - vi.spyOn(CursorCloudDriver.prototype, "openSession").mockImplementation(async function(input) { + vi.spyOn(CursorCloudDriver.prototype, "openSession").mockImplementation(async function(this: CursorCloudDriver, input) { openedSession = await openSession.call(this, input); return openedSession; }); diff --git a/packages/paperclip-runner/src/drivers/cursor-cloud/cloud-client.test.ts b/packages/paperclip-runner/src/drivers/cursor-cloud/cloud-client.test.ts index 404986ecec0..7476a828047 100644 --- a/packages/paperclip-runner/src/drivers/cursor-cloud/cloud-client.test.ts +++ b/packages/paperclip-runner/src/drivers/cursor-cloud/cloud-client.test.ts @@ -139,7 +139,7 @@ describe("Cursor Cloud v1 HTTP client", () => { if (request.url?.includes("/artifacts/download?")) return json(response, { url: `https://${host}/proof?private-signature`, expiresAt: TIME }); response.end(bytes); }); - const externalHeaders: HeadersInit[] = []; + const externalHeaders: Array> = []; let unexpectedTargets = 0; const transport: typeof fetch = (input, init) => { const url = String(input); diff --git a/packages/paperclip-runner/src/drivers/cursor-cloud/cursor-cloud-driver.test.ts b/packages/paperclip-runner/src/drivers/cursor-cloud/cursor-cloud-driver.test.ts index 219f45cd95d..f871999474b 100644 --- a/packages/paperclip-runner/src/drivers/cursor-cloud/cursor-cloud-driver.test.ts +++ b/packages/paperclip-runner/src/drivers/cursor-cloud/cursor-cloud-driver.test.ts @@ -71,7 +71,7 @@ function maintainedMutations(peer: Peer, maxResponseBytes?: number) { async function fixture(peer = new Peer(), extra: Partial = {}) { const root = await mkdtemp("/tmp/cc-"); roots.push(root); - const handler = vi.fn(async () => ({ success: true, contentItems: [{ type: "inputText", text: "accepted" }] })); + const handler = vi.fn(async (_call: Parameters[0]) => ({ success: true, contentItems: [{ type: "inputText", text: "accepted" }] })); const options: CursorCloudDriverOptions = { model: "explicit-model", config: { schema: "paperclip.cursor-cloud.config.v1", repos: [{ url: "https://github.com/example/repo", startingRef: "main" }] }, binding: { companyId: "company", agentId: "agent", issueId: "issue", normalizedSessionId: "session" }, runtimeDirectory: root, relay: { url: "https://relay.example.test/mcp", token: "private-relay-token", socketPath: join(root, "mcp.sock"), expiresAt: Date.now() + 600_000 }, instructions: "Follow these exact instructions.", @@ -111,7 +111,11 @@ async function nextAuthority(f: Awaited>) { return { next, checkpoint: await savedCheckpoint(f.root, f.session), eventCount: f.events.length }; } -function relayHeaders(peer: Peer): Record { return structuredClone(peer.request!.mcpServers[0]!.headers!); } +function relayHeaders(peer: Peer): Record { + const server = peer.request!.mcpServers![0]!; + if (!("headers" in server)) throw new Error("Expected an HTTP relay MCP server"); + return structuredClone(server.headers!); +} async function rpc(path: string, body: unknown, token: string | Record = "private-relay-token", session?: string): Promise<{ status: number; value: any; session?: string }> { return new Promise((accept, reject) => { @@ -206,7 +210,7 @@ describe("Cursor Cloud native driver", () => { await f.session.startTurn({ message: { role: "user", text: "Stop an observation in flight" } }); await reading.promise; try { - await f.session.interrupt!(); + await f.session.interrupt!({}); await vi.waitFor(() => expect(readAborted).toBe(true), { timeout: 100 }); expect((await f.session.read()).phase).toBe("terminal"); } finally { blocked.resolve(await original()); } @@ -257,7 +261,7 @@ describe("Cursor Cloud native driver", () => { await f.session.startTurn({ message: { role: "user", text: "Stop during a provider retry delay" } }); await waitFor(() => f.events.some(event => (event.payload as { code?: string }).code === "cursor_cloud_observation_retry")); const signal = ((peer.getRun.mock.calls[0] as unknown[])[2] as { signal: AbortSignal }).signal; - await f.session.interrupt!(); + await f.session.interrupt!({}); expect(signal.aborted).toBe(true); expect((await f.session.read()).phase).toBe("terminal"); expect(peer.createAgent).toHaveBeenCalledTimes(1); expect(peer.createRun).not.toHaveBeenCalled(); @@ -354,7 +358,7 @@ describe("Cursor Cloud native driver", () => { } } await reading.promise; - await session.interrupt!(); + await session.interrupt!({}); await acknowledgeTerminal(session); await session.attachRun!({ runId: "paperclip-followup" }); const next = await session.startTurn({ message: { role: "user", text: "Second task" } }); @@ -370,7 +374,7 @@ describe("Cursor Cloud native driver", () => { expect(JSON.parse(await readFile(checkpointPath, "utf8"))).toEqual(before); expect(await session.read()).toMatchObject({ agentId: previous.agentId, runId: followupRunId, turnId: next.turnId, phase: "running", pendingTerminalDelivery: null }); expect(events.slice(eventCount)).toEqual([]); - await session.interrupt!(); + await session.interrupt!({}); expect(await session.read()).toMatchObject({ runId: followupRunId, phase: "terminal", pendingTerminalDelivery: { providerRunId: followupRunId, turnId: next.turnId, status: "CANCELLED" } }); expect(events.filter(event => event.turnId === next.turnId && event.eventType === "turn.interrupted")).toHaveLength(1); }); @@ -393,14 +397,14 @@ describe("Cursor Cloud native driver", () => { }; const f = await fixture(peer); await f.session.startTurn({ message: { role: "user", text: "First task" } }); await waiting.promise; - await f.session.interrupt!(); + await f.session.interrupt!({}); const next = await nextAuthority(f); release.resolve(); await new Promise(accept => setImmediate(accept)); expect(await savedCheckpoint(f.root, f.session)).toEqual(next.checkpoint); expect(f.events.slice(next.eventCount)).toEqual([]); expect(streams.map(stream => stream.runId)).toEqual([runId, peer.native!.id]); expect(streams[1]!.signal!.aborted).toBe(false); - await f.session.interrupt!(); + await f.session.interrupt!({}); expect((await f.session.read()).pendingTerminalDelivery).toMatchObject({ providerRunId: peer.native!.id, turnId: next.next.turnId }); }); @@ -421,7 +425,7 @@ describe("Cursor Cloud native driver", () => { released.resolve(); await new Promise(accept => setImmediate(accept)); expect(await savedCheckpoint(f.root, f.session)).toEqual(next.checkpoint); expect(f.events.slice(next.eventCount)).toEqual([]); - await f.session.interrupt!(); + await f.session.interrupt!({}); expect(await f.session.read()).toMatchObject({ phase: "terminal", pendingTerminalDelivery: { providerRunId: peer.native!.id, turnId: next.next.turnId } }); }); @@ -441,7 +445,7 @@ describe("Cursor Cloud native driver", () => { released.resolve(oldAgent); await oldReconcile; expect(await savedCheckpoint(f.root, f.session)).toEqual(next.checkpoint); expect(f.events.slice(next.eventCount)).toEqual([]); - await f.session.interrupt!(); + await f.session.interrupt!({}); expect(await f.session.read()).toMatchObject({ phase: "terminal", pendingTerminalDelivery: { providerRunId: peer.native!.id, turnId: next.next.turnId } }); }); @@ -453,19 +457,19 @@ describe("Cursor Cloud native driver", () => { const previous = { ...peer.native! }; const oldRead = deferred(), reading = deferred(); peer.getRun.mockImplementationOnce(async () => { reading.resolve(); return oldRead.promise; }); - const oldStop = f.session.interrupt!(); await reading.promise; + const oldStop = f.session.interrupt!({}); await reading.promise; peer.getRun.mockResolvedValueOnce({ ...previous, status: "FINISHED" }); await f.session.reconcile!(); const next = await nextAuthority(f); const currentRead = deferred(), currentReading = deferred(); peer.getRun.mockImplementationOnce(async () => { currentReading.resolve(); return currentRead.promise; }); - const currentStop = f.session.interrupt!(); await currentReading.promise; + const currentStop = f.session.interrupt!({}); await currentReading.promise; const before = await savedCheckpoint(f.root, f.session); oldRead.resolve(previous); await oldStop; expect(await savedCheckpoint(f.root, f.session)).toEqual(before); expect(peer.cancelRun).not.toHaveBeenCalled(); const readCount = peer.getRun.mock.calls.length; - const duplicateStop = f.session.interrupt!(); + const duplicateStop = f.session.interrupt!({}); await new Promise(accept => setImmediate(accept)); expect(peer.getRun).toHaveBeenCalledTimes(readCount); currentRead.resolve({ ...peer.native! }); await Promise.all([currentStop, duplicateStop]); @@ -480,7 +484,7 @@ describe("Cursor Cloud native driver", () => { if (phase === "follow-up") { await f.session.startTurn({ message: { role: "user", text: "First task" } }); await waitFor(() => f.events.some(event => event.eventType === "item.delta")); - await f.session.interrupt!(); await acknowledgeTerminal(f.session); + await f.session.interrupt!({}); await acknowledgeTerminal(f.session); await f.session.attachRun!({ runId: "paperclip-followup" }); } const before = await savedCheckpoint(f.root, f.session); @@ -498,7 +502,7 @@ describe("Cursor Cloud native driver", () => { expect(await savedCheckpoint(f.root, f.session)).toEqual(before); expect(f.events.slice(eventCount)).toEqual([]); const reconciliations = peer.getAgent.mock.calls.length, cancellations = peer.cancelRun.mock.calls.length; - await f.session.interrupt!(); + await f.session.interrupt!({}); expect(peer.getAgent).toHaveBeenCalledTimes(reconciliations); expect(peer.cancelRun).toHaveBeenCalledTimes(cancellations); const snapshot = await f.session.snapshot(); await f.session.close({ reason: "storage_recovered" }); await f.pump; expect(peer.getAgent).toHaveBeenCalledTimes(reconciliations); expect(peer.cancelRun).toHaveBeenCalledTimes(cancellations); @@ -507,7 +511,7 @@ describe("Cursor Cloud native driver", () => { expect(await savedCheckpoint(f.root, recovered)).toEqual({ ...before, relayTurnToken: before.relayTurnToken ?? null }); await recovered.startTurn({ message: { role: "user", text: "Retry after storage recovery" } }); await waitFor(() => peer.createAgent.mock.calls.length === 1 && peer.createRun.mock.calls.length === (phase === "fresh" ? 0 : 1)); - await recovered.interrupt!(); + await recovered.interrupt!({}); }); it("retains accepted provider work when its confirmed-identity checkpoint write fails", async () => { @@ -531,7 +535,7 @@ describe("Cursor Cloud native driver", () => { await rm(checkpointPath, { recursive: true }); await rename(savedPath, checkpointPath); } expect(await savedCheckpoint(f.root, f.session)).toMatchObject({ agentId: peer.native!.agentId, runId, phase: "running", pendingOperation: null }); - await f.session.interrupt!(); + await f.session.interrupt!({}); expect(peer.createAgent).toHaveBeenCalledTimes(1); expect(peer.cancelRun).toHaveBeenCalledTimes(1); expect(await f.session.read()).toMatchObject({ phase: "terminal", pendingTerminalDelivery: { status: "CANCELLED", providerRunId: runId } }); }); @@ -543,7 +547,7 @@ describe("Cursor Cloud native driver", () => { const failure = new Error("controlled post-publication persistence failure"); const originalWrite = CursorCloudCheckpointStore.prototype.write; let intercepted = false; - const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(value) { + const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(this: CursorCloudCheckpointStore, value) { const persisted = originalWrite.call(this, value); if (value.phase === "creating" && !intercepted) { intercepted = true; @@ -556,13 +560,13 @@ describe("Cursor Cloud native driver", () => { const started = f.session.startTurn({ message: { role: "user", text: "Stop before unsent launch" } }); const rejected = expect(started).rejects.toBe(failure); await held.promise; - const stopped = f.session.interrupt!(); await stoppingWritten.promise; + const stopped = f.session.interrupt!({}); await stoppingWritten.promise; expect(f.peer.createAgent).not.toHaveBeenCalled(); release.resolve(); await rejected; await stopped; expect(await savedCheckpoint(f.root, f.session)).toEqual(before); expect(f.peer.createAgent).not.toHaveBeenCalled(); expect(f.peer.getAgent).not.toHaveBeenCalled(); expect(f.peer.cancelRun).not.toHaveBeenCalled(); await f.session.startTurn({ message: { role: "user", text: "Next admitted task" } }); await f.peer.created.promise; - await f.session.interrupt!(); + await f.session.interrupt!({}); } finally { release.resolve(); write.mockRestore(); } }); @@ -571,10 +575,10 @@ describe("Cursor Cloud native driver", () => { const f = await fixture(peer); await f.session.startTurn({ message: { role: "user", text: "First task" } }); await peer.created.promise; await waitFor(() => f.events.some(event => event.eventType === "item.delta")); - await f.session.interrupt!(); await acknowledgeTerminal(f.session); + await f.session.interrupt!({}); await acknowledgeTerminal(f.session); const held = deferred(), release = deferred(), stoppingWritten = deferred(); const originalWrite = CursorCloudCheckpointStore.prototype.write; - const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(value) { + const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(this: CursorCloudCheckpointStore, value) { const persisted = originalWrite.call(this, value); if (value.phase === "creating") return persisted.then(async () => { held.resolve(); await release.promise; }); if (value.phase === "stopping") void persisted.then(() => stoppingWritten.resolve()); @@ -582,7 +586,7 @@ describe("Cursor Cloud native driver", () => { }); try { const started = f.session.startTurn({ message: { role: "user", text: "Second task" } }); await held.promise; - const stopped = f.session.interrupt!(); await stoppingWritten.promise; + const stopped = f.session.interrupt!({}); await stoppingWritten.promise; expect(peer.createRun).not.toHaveBeenCalled(); release.resolve(); const next = await started; await stopped; @@ -605,7 +609,7 @@ describe("Cursor Cloud native driver", () => { const after = await savedCheckpoint(f.root, f.session); expect(after).toEqual({ ...before, sourceSequence: before.sourceSequence + 1 }); expect(f.events.some(event => event.eventType === "turn.failed")).toBe(false); - await f.session.interrupt!(); + await f.session.interrupt!({}); expect(await f.session.read()).toMatchObject({ phase: "terminal", pendingTerminalDelivery: { providerRunId: runId } }); }); @@ -617,7 +621,7 @@ describe("Cursor Cloud native driver", () => { f.peer.getRun.mockResolvedValueOnce({ ...f.peer.native!, status: "FINISHED", ...(identity === "agent" ? { agentId: "bc-different" } : { id: "run-22222222-2222-4222-8222-222222222222" }) }); await expect(f.session.reconcile!()).rejects.toThrow("confirmed Agent/Run identity"); expect(await savedCheckpoint(f.root, f.session)).toEqual(before); - await f.session.interrupt!(); + await f.session.interrupt!({}); expect(await f.session.read()).toMatchObject({ phase: "terminal", pendingTerminalDelivery: { providerRunId: runId } }); }); @@ -660,7 +664,7 @@ describe("Cursor Cloud native driver", () => { const activeSnapshot = await restored.snapshot(); await restored.detachControllerForRestart!(); await expect(new CursorCloudDriver(f.options).recoverSession(activeSnapshot)).rejects.toThrow("instructions changed"); const final = (await new CursorCloudDriver(revised).recoverSession(activeSnapshot)).session!; sessions.push(final); - await final.interrupt!(); + await final.interrupt!({}); await acknowledgeTerminal(final); await final.startTurn({ message: { role: "user", text: "One more task" } }); await waitFor(() => f.peer.createRun.mock.calls.length === 2); @@ -702,7 +706,7 @@ describe("Cursor Cloud native driver", () => { expect(f.handler).toHaveBeenCalledWith(expect.objectContaining({ tool: "write_note", arguments: { note: "first" }, turnId: expect.stringMatching(/^turn-/) })); await rpc(path, { id: "missing", method: "tools/call", params: { name: "not_admitted", arguments: {} } }, relayHeaders(f.peer), initialized.session); expect(f.handler).toHaveBeenCalledTimes(1); - await f.session.interrupt!(); + await f.session.interrupt!({}); expect(f.peer.cancelRun).toHaveBeenCalledTimes(1); expect(f.peer.getRun.mock.calls.length).toBeGreaterThanOrEqual(2); const late = await rpc(path, { id: "late", method: "tools/call", params: { name: "write_note", arguments: { note: "late" } } }, relayHeaders(f.peer), initialized.session); @@ -849,7 +853,7 @@ describe("Cursor Cloud native driver", () => { await waitFor(() => peer.getAgent.mock.calls.length > 0); expect(peer.createAgent).toHaveBeenCalledTimes(1); expect((await restored.read?.())?.effectiveModelAcknowledged).toBe(false); - await restored.interrupt!(); + await restored.interrupt!({}); const restoredSnapshot = await restored.snapshot(); await restored.close({ reason: "done" }); await expect(new CursorCloudDriver({ ...f.options, model: "different-model" }).recoverSession(restoredSnapshot)).rejects.toThrow("changed"); }); @@ -873,7 +877,7 @@ describe("Cursor Cloud native driver", () => { expect(after.requestedAgentId).toMatch(/^bc-/); await expect(restored.startTurn({ message: { role: "user", text: "Must not replace uncertain work" } })).rejects.toThrow("unresolved remote work"); expect(peer.createAgent).toHaveBeenCalledTimes(1); expect(peer.createRun).not.toHaveBeenCalled(); - peer.getAgent.mockImplementation(getAgent); await restored.interrupt!(); + peer.getAgent.mockImplementation(getAgent); await restored.interrupt!({}); }); it("allows a definitive direct mutation rejection to settle without pretending work was accepted", async () => { @@ -893,7 +897,7 @@ describe("Cursor Cloud native driver", () => { if (phase === "follow-up") { await f.session.startTurn({ message: { role: "user", text: "Previous accepted task" } }); await peer.created.promise; - await f.session.interrupt!(); await acknowledgeTerminal(f.session); + await f.session.interrupt!({}); await acknowledgeTerminal(f.session); } const before = { fetch: fetch.mock.calls.length, getAgent: peer.getAgent.mock.calls.length, getRun: peer.getRun.mock.calls.length, cancel: peer.cancelRun.mock.calls.length }; const text = "\u0001".repeat(400_000); @@ -905,11 +909,11 @@ describe("Cursor Cloud native driver", () => { expect(fetch).toHaveBeenCalledTimes(before.fetch); expect(await f.session.read()).toMatchObject({ phase: "idle", providerStatus: "REJECTED" }); expect(f.events.some(event => event.turnId === turn.turnId && event.eventType === "turn.failed" && event.payload.remoteWorkAccepted === false)).toBe(true); - await f.session.interrupt!(); + await f.session.interrupt!({}); expect(peer.getAgent).toHaveBeenCalledTimes(before.getAgent); expect(peer.getRun).toHaveBeenCalledTimes(before.getRun); expect(peer.cancelRun).toHaveBeenCalledTimes(before.cancel); - const snapshot = await f.session.snapshot(); await f.session.detachControllerForRestart(); await f.pump; + const snapshot = await f.session.snapshot(); await f.session.detachControllerForRestart!(); await f.pump; const recovered = await new CursorCloudDriver(f.options).recoverSession(snapshot); expect(recovered.recovered).toBe(true); const restored = recovered.session!; sessions.push(restored); @@ -919,7 +923,7 @@ describe("Cursor Cloud native driver", () => { const next = await restored.startTurn({ message: { role: "user", text: "A smaller admitted task" } }); await vi.waitFor(async () => expect(await restored.read()).toMatchObject({ phase: "running", turnId: next.turnId })); expect(fetch).toHaveBeenCalledTimes(before.fetch + 1); - await restored.interrupt!(); + await restored.interrupt!({}); }); it.each(["fresh", "follow-up"] as const)("keeps a %s oversized response fenced after the mutation was sent", async phase => { @@ -928,7 +932,7 @@ describe("Cursor Cloud native driver", () => { const f = await fixture(peer, { client: mutations.client }); if (phase === "follow-up") { await f.session.startTurn({ message: { role: "user", text: "Previous accepted task" } }); - await peer.created.promise; await f.session.interrupt!(); await acknowledgeTerminal(f.session); + await peer.created.promise; await f.session.interrupt!({}); await acknowledgeTerminal(f.session); } const before = mutations.fetch.mock.calls.length; mutations.fetch.mockImplementationOnce(async (url, init) => { @@ -954,7 +958,7 @@ describe("Cursor Cloud native driver", () => { if (phase === "follow-up") { await f.session.startTurn({ message: { role: "user", text: "Previous accepted task" } }); await peer.created.promise; - await f.session.interrupt!(); + await f.session.interrupt!({}); await acknowledgeTerminal(f.session); } const entered = deferred(), rejectCreate = deferred(), stopHeld = deferred(), releaseStop = deferred(); @@ -975,7 +979,7 @@ describe("Cursor Cloud native driver", () => { const before = { getAgent: peer.getAgent.mock.calls.length, getRun: peer.getRun.mock.calls.length, cancel: peer.cancelRun.mock.calls.length }; const originalWrite = CursorCloudCheckpointStore.prototype.write; let pausedStop = false, pausedRejection = false; - const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(value) { + const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(this: CursorCloudCheckpointStore, value) { if (value.phase === "idle" && value.terminalStatus === "REJECTED" && !pausedRejection) { pausedRejection = true; rejectionHeld.resolve(); return releaseRejection.promise.then(() => originalWrite.call(this, value)); @@ -989,7 +993,7 @@ describe("Cursor Cloud native driver", () => { }); let stopDone = false; let stopped: Promise<{ error: unknown | null }> | undefined; - const stop = () => stopped = f.session.interrupt!().then(() => ({ error: null }), error => ({ error })) + const stop = () => stopped = f.session.interrupt!({}).then(() => ({ error: null }), error => ({ error })) .finally(() => { stopDone = true; }); try { const turn = await f.session.startTurn({ message: { role: "user", text: rejection === "unsent_payload" ? "\u0001".repeat(400_000) : "Rejected task" } }); @@ -1029,7 +1033,7 @@ describe("Cursor Cloud native driver", () => { expect(peer.cancelRun).toHaveBeenCalledTimes(before.cancel); if (mutations) expect(mutations.fetch).toHaveBeenCalledTimes(fetchesBefore!); const snapshot = await f.session.snapshot(); - await f.session.detachControllerForRestart(); await f.pump; + await f.session.detachControllerForRestart!(); await f.pump; const recovered = await new CursorCloudDriver(f.options).recoverSession(snapshot); expect(recovered.recovered).toBe(true); const restored = recovered.session!; sessions.push(restored); @@ -1037,7 +1041,7 @@ describe("Cursor Cloud native driver", () => { distinctFollowup(peer); peer.release = deferred(); const next = await restored.startTurn({ message: { role: "user", text: "Next admitted task" } }); await vi.waitFor(async () => expect(await restored.read()).toMatchObject({ phase: "running", turnId: next.turnId })); - await restored.interrupt!(); + await restored.interrupt!({}); expect(await restored.read()).toMatchObject({ phase: "terminal", pendingTerminalDelivery: { turnId: next.turnId } }); } finally { rejectCreate.resolve(); releaseStop.resolve(); releaseRejection.resolve(); @@ -1051,7 +1055,7 @@ describe("Cursor Cloud native driver", () => { const f = await fixture(peer), held = deferred(), release = deferred(); const originalWrite = CursorCloudCheckpointStore.prototype.write; let paused = false; - const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(value) { + const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(this: CursorCloudCheckpointStore, value) { if (value.phase === "idle" && value.terminalStatus === "REJECTED" && !paused) { paused = true; held.resolve(); return release.promise.then(() => originalWrite.call(this, value)); } @@ -1082,7 +1086,7 @@ describe("Cursor Cloud native driver", () => { await f.session.reconcile!(); await acknowledgeTerminal(f.session); await f.session.attachRun!({ runId: "current-owner" }); peer.createRun.mockRejectedValue(new CursorCloudClientError("http_error", { status: 422 })); const held = deferred(), release = deferred(), originalWrite = CursorCloudCheckpointStore.prototype.write; - const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(value) { + const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(this: CursorCloudCheckpointStore, value) { if (value.phase === "idle" && value.terminalStatus === "REJECTED") { held.resolve(); return release.promise.then(() => originalWrite.call(this, value)); } return originalWrite.call(this, value); }); @@ -1105,7 +1109,7 @@ describe("Cursor Cloud native driver", () => { const f = await fixture(peer, mutations ? { client: mutations.client } : {}); if (phase === "follow-up") { await f.session.startTurn({ message: { role: "user", text: "Previous accepted task" } }); await peer.created.promise; - await f.session.interrupt!(); await acknowledgeTerminal(f.session); + await f.session.interrupt!({}); await acknowledgeTerminal(f.session); } const entered = deferred(), rejected = deferred(); if (mutations) { @@ -1119,14 +1123,14 @@ describe("Cursor Cloud native driver", () => { }); peer.getAgent.mockRejectedValue(new CursorCloudClientError("http_error", { status: 404 })); const originalWrite = CursorCloudCheckpointStore.prototype.write; - const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(value) { + const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(this: CursorCloudCheckpointStore, value) { return value.phase === "idle" && value.terminalStatus === "REJECTED" ? Promise.reject(new Error("injected rejection checkpoint failure")) : originalWrite.call(this, value); }); try { const before = mutations?.fetch.mock.calls.length; const turn = await f.session.startTurn({ message: { role: "user", text: mutations ? "\u0001".repeat(400_000) : "Rejected task" } }); await entered.promise; - const stopped = f.session.interrupt!().then(() => null, error => error); + const stopped = f.session.interrupt!({}).then(() => null, error => error); rejected.resolve(); expect(await stopped).toMatchObject({ message: expect.stringContaining("remains fenced") }); if (mutations) expect(mutations.fetch).toHaveBeenCalledTimes(before!); @@ -1147,7 +1151,7 @@ describe("Cursor Cloud native driver", () => { peer.getAgent.mockRejectedValue(new CursorCloudClientError("http_error", { status: 404 })); const f = await fixture(peer), turn = await f.session.startTurn({ message: { role: "user", text: "Unknown create" } }); await entered.promise; - const stopped = f.session.interrupt!().then(() => null, error => error); released.resolve(); + const stopped = f.session.interrupt!({}).then(() => null, error => error); released.resolve(); expect(await stopped).toMatchObject({ message: expect.stringContaining("remains fenced") }); expect(await savedCheckpoint(f.root, f.session)).toMatchObject({ phase: "stopping", pendingOperation: "create_agent", terminalStatus: null }); expect(f.events.some(event => event.turnId === turn.turnId && event.payload.remoteWorkAccepted === false)).toBe(false); @@ -1159,7 +1163,7 @@ describe("Cursor Cloud native driver", () => { const f = await fixture(peer, { cancelTimeoutMs: 120 }); await f.session.startTurn({ message: { role: "user", text: "Keep running" } }); await peer.created.promise; await waitFor(() => f.events.some(event => event.eventType === "item.delta")); - await expect(f.session.interrupt!()).rejects.toThrow("did not confirm"); + await expect(f.session.interrupt!({})).rejects.toThrow("did not confirm"); expect(f.events.some(event => event.eventType === "turn.interrupted" || event.eventType === "turn.completed")).toBe(false); expect((await f.session.read?.())?.phase).toBe("stopping"); await expect(f.session.startTurn({ message: { role: "user", text: "New work" } })).rejects.toThrow("unresolved"); @@ -1204,7 +1208,7 @@ describe("Cursor Cloud native driver", () => { const originalWrite = CursorCloudCheckpointStore.prototype.write; const attempts: Array> = []; let injected = false; - const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(async function(value) { + const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(async function(this: CursorCloudCheckpointStore, value) { if (this.path !== checkpointPath || value.phase !== "terminal") return originalWrite.call(this, value); attempts.push(structuredClone(value.pendingTerminalDelivery)); if (injected) { await releaseRetry.promise; return originalWrite.call(this, value); } @@ -1248,7 +1252,7 @@ describe("Cursor Cloud native driver", () => { const held = deferred(), release = deferred(); const originalWrite = CursorCloudCheckpointStore.prototype.write; let paused = false; - const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(value) { + const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(this: CursorCloudCheckpointStore, value) { if (this.path === checkpointPath && value.phase === "terminal" && !paused) { paused = true; held.resolve(); return release.promise.then(() => originalWrite.call(this, value)); } @@ -1261,7 +1265,7 @@ describe("Cursor Cloud native driver", () => { const late = await rpc(f.options.relay.socketPath, { id: "late", method: "tools/call", params: { name: "write_note", arguments: { note: "No authority after provider termination" } } }, relayHeaders(f.peer), initialized.session); expect(late.status).toBe(403); expect(f.handler).not.toHaveBeenCalled(); let stopped = false, reconciled = false, snapshotted = false; - const stop = f.session.interrupt!().finally(() => { stopped = true; }); + const stop = f.session.interrupt!({}).finally(() => { stopped = true; }); const reconcile = f.session.reconcile!().finally(() => { reconciled = true; }); const snapshot = f.session.snapshot().finally(() => { snapshotted = true; }); await new Promise(accept => setImmediate(accept)); @@ -1290,7 +1294,7 @@ describe("Cursor Cloud native driver", () => { const originalWrite = CursorCloudCheckpointStore.prototype.write; const deliveries: unknown[] = []; let unavailable = true; - const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(value) { + const write = vi.spyOn(CursorCloudCheckpointStore.prototype, "write").mockImplementation(function(this: CursorCloudCheckpointStore, value) { if (this.path === checkpointPath && value.phase === "terminal") { deliveries.push(structuredClone(value.pendingTerminalDelivery)); if (unavailable) return Promise.reject(Object.assign(new Error("controlled unavailable checkpoint storage"), { code: "ENOSPC" })); @@ -1363,7 +1367,7 @@ describe("Cursor Cloud native driver", () => { await waitFor(() => stream.mock.calls.length > 0); expect(peer.createAgent).toHaveBeenCalledTimes(1); expect(peer.createRun).not.toHaveBeenCalled(); expect(JSON.parse(await readFile(checkpointPath, "utf8")).deadlineAt).toBe(deadlineAt); - await restored.interrupt!(); + await restored.interrupt!({}); }); it("enforces the saved deadline while the first recovery read remains pending", async () => { diff --git a/packages/paperclip-runner/src/drivers/dot/dot-harness-driver.test.ts b/packages/paperclip-runner/src/drivers/dot/dot-harness-driver.test.ts index 15fd39f32a2..05f174cc754 100644 --- a/packages/paperclip-runner/src/drivers/dot/dot-harness-driver.test.ts +++ b/packages/paperclip-runner/src/drivers/dot/dot-harness-driver.test.ts @@ -11,7 +11,7 @@ const result = { schema: "paperclip.run_result.v1", reportedWorkDisposition: "do async function fixture(overrides: Partial = {}) { const identity = { companyId: randomUUID(), agentId: randomUUID(), issueId: randomUUID(), runId: randomUUID(), sessionId: randomUUID() }; const principal = { companyId: identity.companyId, grantId: randomUUID() }; - const publish = vi.fn(async () => {}); + const publish = vi.fn(async () => {}); const executeTool = vi.fn(async () => ({ saved: true })); const assertAuthority = vi.fn(async () => {}); const driver = new DotHarnessDriver({ identity, principal, expiresAt: Date.now() + 60_000, diff --git a/packages/paperclip-runner/src/drivers/dot/runnerd-dot-driver.test.ts b/packages/paperclip-runner/src/drivers/dot/runnerd-dot-driver.test.ts index 9e14a1bdd2b..6758f8769b6 100644 --- a/packages/paperclip-runner/src/drivers/dot/runnerd-dot-driver.test.ts +++ b/packages/paperclip-runner/src/drivers/dot/runnerd-dot-driver.test.ts @@ -96,7 +96,7 @@ it.each([ return handle; }); const getCommand = controlPlane.DurablePrpControlPlane.prototype.getCommand; - const commandSpy = vi.spyOn(controlPlane.DurablePrpControlPlane.prototype, "getCommand").mockImplementation(function (id) { + const commandSpy = vi.spyOn(controlPlane.DurablePrpControlPlane.prototype, "getCommand").mockImplementation(function (this: controlPlane.DurablePrpControlPlane, id) { const command = getCommand.call(this, id); // Hold the SDK poll until the real process has exited. The authenticated // shutdown receipt and its ACK remain committed in the transport journal. @@ -112,7 +112,7 @@ it.each([ }); let session: Awaited> | undefined; try { - session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId! }); + session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, workingDirectory: root }); if (kind === "shutdown") { await expect(session.close({ reason: "Test complete" })).resolves.toBeUndefined(); expect(exited).toBe(true); @@ -152,7 +152,7 @@ it.each(["local", "local pending finish", "custom local directory", "external la identity: { runnerInstanceId: randomUUID(), environmentLeaseId: randomUUID(), runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, turnId: "turn-" + input.binding.runId, itemId: "item-" + input.binding.runId }, runnerBinary: resolve("runner/target/debug/paperclip-runnerd"), - onSpawn: process => { spawned = process; }, + onSpawn: async process => { spawned = process; }, controlPlaneRegistration: async authority => { if (emptyController) { authority.setCheckpointPersistence(async saved => { checkpoint = controlPlane.decodeDurablePrpCheckpoint(new Map(saved.records)); }); @@ -190,7 +190,7 @@ it.each(["local", "local pending finish", "custom local directory", "external la }; } const driver = new RunnerdDotDriver(options); - let session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId! }); + let session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, workingDirectory: root }); const operation = (action: ExternalProviderOperation["action"], args: Record): ExternalProviderOperation => ({ requestId: randomUUID(), bindingId: input.provider.binding.bindingId, bindingGeneration: 1, runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, turnId: options.identity.turnId, assignmentRevision: 1, diff --git a/packages/paperclip-runner/src/drivers/muse/runnerd-muse-driver.test.ts b/packages/paperclip-runner/src/drivers/muse/runnerd-muse-driver.test.ts index d0b4e6f6527..9e8da75fd51 100644 --- a/packages/paperclip-runner/src/drivers/muse/runnerd-muse-driver.test.ts +++ b/packages/paperclip-runner/src/drivers/muse/runnerd-muse-driver.test.ts @@ -82,7 +82,7 @@ it("uses the native request, exact question command ID and durable answer receip execution: input, stateDirectory: join(root, "state"), runnerBinary: resolve("runner/target/debug/paperclip-runnerd"), identity: { runnerInstanceId: randomUUID(), environmentLeaseId: randomUUID(), runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, turnId: "turn-" + input.binding.runId, itemId: "item-" + input.binding.runId }, - onSpawn: process => { spawned = process; }, + onSpawn: async process => { spawned = process; }, controlPlaneRegistration: async authority => { await authority.start(controllerPort); controllerPort = Number(new URL(authority.connectUrl).port); return { connectUrl: authority.connectUrl, release: () => authority.stop() }; }, port: { dispatch: async () => {}, settle: async event => { settlements.set(String(event.payload.requestId), event.payload.outcome); }, @@ -95,7 +95,7 @@ it("uses the native request, exact question command ID and durable answer receip }); const commandSpy = vi.spyOn(controlPlane.DurablePrpControlPlane.prototype, "queueCommand"); let driver = new RunnerdMuseDriver(options); - let session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId! }); + let session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, workingDirectory: root }); try { const prepared = commandSpy.mock.calls.find(call => call[0] === "run.prepare"); expect(JSON.stringify(prepared?.[1])).not.toContain("connections_search"); @@ -166,7 +166,7 @@ it("bounds default Muse question answer command IDs at the native queue boundary execution: input, stateDirectory: join(root, "state"), runnerBinary: resolve("runner/target/debug/paperclip-runnerd"), identity: { runnerInstanceId: randomUUID(), environmentLeaseId: randomUUID(), runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, turnId: "turn-" + input.binding.runId, itemId: "item-" + input.binding.runId }, - onSpawn: process => { spawned = process; }, + onSpawn: async process => { spawned = process; }, controlPlaneRegistration: async authority => { await authority.start(controllerPort); controllerPort = Number(new URL(authority.connectUrl).port); return { connectUrl: authority.connectUrl, release: () => authority.stop() }; }, port: { dispatch: async () => {}, settle: async event => { settlements.set(String(event.payload.requestId), event.payload.outcome); }, @@ -182,7 +182,7 @@ it("bounds default Muse question answer command IDs at the native queue boundary const response = { schema: "paperclip.question_response.v1" as const, answers: { environment: { selectedOptionIds: ["test"] } } }; const commandSpy = vi.spyOn(controlPlane.DurablePrpControlPlane.prototype, "queueCommand"); let driver = new RunnerdMuseDriver(options); - let session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId! }); + let session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, workingDirectory: root }); try { await session.startTurn({ message: { role: "user", text: "Answer boundary questions." } }); await send!(operation("accept", {})); @@ -253,7 +253,7 @@ it("rejects bridge-unsupported questions at native admission without a stored re execution: input, stateDirectory: join(root, "state"), runnerBinary: resolve("runner/target/debug/paperclip-runnerd"), identity: { runnerInstanceId: randomUUID(), environmentLeaseId: randomUUID(), runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, turnId: "turn-" + input.binding.runId, itemId: "item-" + input.binding.runId }, - onSpawn: process => { spawned = process; }, + onSpawn: async process => { spawned = process; }, port: { dispatch: async () => {}, settle: async () => {}, inputAvailable: async () => {}, attach: async callback => { send = callback; return async () => { send = undefined; }; } }, }; @@ -263,7 +263,7 @@ it("rejects bridge-unsupported questions at native admission without a stored re action: "request_user_input", input: args, digest: externalOperationDigest("request_user_input", args), }); const driver = new RunnerdMuseDriver(options); - const session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId! }); + const session = await driver.openSession({ runId: input.binding.runId, normalizedSessionId: input.session.normalizedSessionId!, workingDirectory: root }); const events: PrpEvent[] = []; let eventReadFailure: unknown; const reading = (async () => { for await (const event of session.events()) events.push(event); })() @@ -315,8 +315,8 @@ it("keeps the exact Muse question and assignment alive past the native live wind await writeFile(join(root, "AGENTS.md"), "Use only first-party tools."); const input = execution(root); const events: PrpEvent[] = []; - const dispatches: PrpEvent[] = []; - const deliveries: PrpEvent[] = []; + const dispatches: Array<{ sourceEventId: string; payload: Record }> = []; + const deliveries: Array<{ sourceEventId: string; payload: Record }> = []; const settlements = new Map>(); let send: ((operation: ExternalProviderOperation) => Promise) | undefined; let spawned: { pid: number; processGroupId: number | null; startedAt: string } | undefined; @@ -327,7 +327,7 @@ it("keeps the exact Muse question and assignment alive past the native live wind normalizedSessionId: input.session.normalizedSessionId!, turnId: "turn-" + input.binding.runId, itemId: "item-" + input.binding.runId }; const driver = new RunnerdMuseDriver({ execution: input, stateDirectory: join(root, "state"), identity, - runnerBinary: resolve("runner/target/debug/paperclip-runnerd"), onSpawn: process => { spawned = process; }, + runnerBinary: resolve("runner/target/debug/paperclip-runnerd"), onSpawn: async process => { spawned = process; }, port: { dispatch: async event => { dispatches.push(event); }, inputAvailable: async event => { deliveries.push(event); }, settle: async event => { settlements.set(String(event.payload.requestId), event.payload.outcome as Record); }, attach: async callback => { send = callback; return async () => { send = undefined; }; } }, diff --git a/packages/paperclip-runner/src/drivers/opencode/mcp-bridge-diagnostics.test.ts b/packages/paperclip-runner/src/drivers/opencode/mcp-bridge-diagnostics.test.ts index 4c3b253a83c..59844a49dc7 100644 --- a/packages/paperclip-runner/src/drivers/opencode/mcp-bridge-diagnostics.test.ts +++ b/packages/paperclip-runner/src/drivers/opencode/mcp-bridge-diagnostics.test.ts @@ -12,10 +12,14 @@ const report = { evidence: [], verification: [], }; +type RpcResponse = { + result: { isError?: boolean; tools: Array<{ name: string; inputSchema: unknown }> }; +}; + async function rpc(bridge: OpenCodeMcpBridge, id: number, method: string, params?: unknown) { return (await fetch(bridge.url, { method: "POST", headers: { Authorization: `Bearer ${bridge.secret}`, "Content-Type": "application/json", - }, body: JSON.stringify({ jsonrpc: "2.0", id, method, params }) })).json(); + }, body: JSON.stringify({ jsonrpc: "2.0", id, method, params }) })).json() as Promise; } it("binds closed rejection shapes to the actual offered finish schema without recording values", async () => { @@ -23,7 +27,7 @@ it("binds closed rejection shapes to the actual offered finish schema without re const bridge = await startOpenCodeMcpBridge({ handler, onDiagnostic: message => messages.push(message) }); bridges.push(bridge); const listed = await rpc(bridge, 1, "tools/list"); - const schema = listed.result.tools.find((tool: { name: string }) => tool.name === "paperclip_finish").inputSchema; + const schema = listed.result.tools.find((tool: { name: string }) => tool.name === "paperclip_finish")!.inputSchema; const digest = createHash("sha256").update(JSON.stringify(schema)).digest("hex"); for (const [index, input] of [report, { ...report, continuation: null }].entries()) { const result = await rpc(bridge, index + 2, "tools/call", { name: "paperclip_finish", arguments: input }); diff --git a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts index 5a2c95e2ad0..0613569216a 100644 --- a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts +++ b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts @@ -241,7 +241,7 @@ describe("OpenCodeServerDriver", () => { }); const session = await driver.openSession({ runId: "empty", normalizedSessionId: "empty", workingDirectory: root }); try { - expect(await session.read()).toMatchObject({ messages: [] }); + expect(await session.read!()).toMatchObject({ messages: [] }); } finally { await session.close({ reason: "test" }); } @@ -338,7 +338,7 @@ describe("OpenCodeServerDriver", () => { }); const descriptor = await driver.descriptor(); expect( - descriptor.capabilities.typedEventFamilies.find( + descriptor.capabilities.typedEventFamilies?.find( (family) => family.family === "plan", ), ).toMatchObject({ availability: "unsupported" }); @@ -751,7 +751,7 @@ describe("OpenCodeServerDriver", () => { // run.terminal is owned by the native-session layer above this driver. // Keep both absences explicit instead of synthesizing Codex-shaped input. expect( - (await driver.descriptor()).capabilities.typedEventFamilies.find( + (await driver.descriptor()).capabilities.typedEventFamilies?.find( (family) => family.family === "plan", ), ).toMatchObject({ availability: "unsupported" }); @@ -1105,7 +1105,7 @@ describe("OpenCodeServerDriver", () => { expect(JSON.stringify(events)).not.toContain( "submitted prompt must not become assistant text", ); - expect(await session.usage()).toMatchObject({ + expect(await session.usage!()).toMatchObject({ input: 3, output: 2, costUsd: 0.001, @@ -1772,7 +1772,7 @@ describe("OpenCodeServerDriver", () => { // A prompt tools map replaces OpenCode's session permissions. Question is // enabled in config without overwriting the allow/ask/deny or path policy. expect(submittedPrompt).not.toHaveProperty("tools"); - expect(JSON.stringify(submittedPrompt?.parts ?? null)).not.toContain( + expect(JSON.stringify((submittedPrompt as Record | null)?.parts ?? null)).not.toContain( PAPERCLIP_EXECUTION_PROMPT, ); const sessionRoot = join(root, "context"); @@ -2871,13 +2871,13 @@ describe("OpenCodeServerDriver", () => { await session.startTurn({ message: { role: "user", text: "finish" } }); await started; let stopped = false; - const stop = session[operation]({ reason: "fixture stop" }).then(() => { stopped = true; }); + const stop = session[operation]!({ reason: "fixture stop" }).then(() => { stopped = true; }); await new Promise(resolve => setImmediate(resolve)); expect(stopped).toBe(false); releaseFeedback(); await stop; expect((await session.snapshot()).semanticResult).not.toBeNull(); - const transcript = await session.transcript(); + const transcript = await session.transcript!(); expect(transcript.events.filter(event => event.eventType === "run.result.proposed")).toHaveLength(1); expect(transcript.events.some(event => event.eventType === "item.completed" && (event.payload.item as { is_error?: boolean })?.is_error)).toBe(false); } finally { diff --git a/packages/paperclip-runner/src/drivers/opencode/reasoning-mode.test.ts b/packages/paperclip-runner/src/drivers/opencode/reasoning-mode.test.ts index d6d9263046f..db3a875e4d2 100644 --- a/packages/paperclip-runner/src/drivers/opencode/reasoning-mode.test.ts +++ b/packages/paperclip-runner/src/drivers/opencode/reasoning-mode.test.ts @@ -57,7 +57,7 @@ describe("per-turn OpenCode reasoning", () => { }); expect(config.provider.openrouter.models["deepseek/deepseek-v4-flash-0731"].options).toBeUndefined(); } finally { - try { await session?.close(); } + try { await session?.close({ reason: "fixture complete" }); } finally { await rm(root, { recursive: true, force: true }); } } }); diff --git a/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts b/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts index c9818c65076..208a738d697 100644 --- a/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts +++ b/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts @@ -23,6 +23,8 @@ afterEach(async () => { await Promise.all(bridges.splice(0).map((bridge) => bridge.close())); }); +type ToolCallBody = { result: { content: unknown } }; + async function rpc( bridge: RunnerToolBridge, body: Record, @@ -563,7 +565,7 @@ describe("Copilot semantic receipt opt-in", () => { } }); bridges.push(bridge); const request = { id: 3, method: "tools/call", params: { name: "paperclip_finish", arguments: rawFinish } }; - const body = await (await rpc(bridge, request)).json(); + const body = await (await rpc(bridge, request)).json() as ToolCallBody; expect(receipts).toHaveLength(1); expect(readNativeSemanticReceipt({ contents: body.result.content })).toEqual(receipts[0]); expect(receipts[0]).toMatchObject({ schema: "paperclip.semantic_tool_receipt.v2", inputSha256: semanticInputSha256(rawFinish), @@ -587,11 +589,11 @@ describe("Copilot semantic receipt opt-in", () => { const normalizedFirst = validatePrpStructuredRunResult(rawFinish); const normalizedSecond = validatePrpStructuredRunResult(secondInput); if (!normalizedFirst.ok || !normalizedSecond.ok) throw new Error("Invalid fixture"); pending.get("second")!.capture(normalizedSecond.result)(); pending.get("second")!.settle(); - const secondReceipt = readNativeSemanticReceipt({ contents: (await (await second).json()).result.content }); + const secondReceipt = readNativeSemanticReceipt({ contents: (await (await second).json() as ToolCallBody).result.content }); expect(secondReceipt).toMatchObject({ inputSha256: semanticInputSha256(secondInput), normalizedInputSha256: semanticInputSha256(normalizedSecond.result) }); pending.get("second")!.capture(normalizedFirst.result)(); pending.get("first")!.capture(normalizedFirst.result)(); pending.get("first")!.settle(); - const firstReceipt = readNativeSemanticReceipt({ contents: (await (await first).json()).result.content }); + const firstReceipt = readNativeSemanticReceipt({ contents: (await (await first).json() as ToolCallBody).result.content }); expect(firstReceipt).toMatchObject({ inputSha256: semanticInputSha256(rawFinish), normalizedInputSha256: semanticInputSha256(normalizedFirst.result) }); expect(receipts).toEqual([secondReceipt, firstReceipt]); }); @@ -600,10 +602,10 @@ describe("Copilot semantic receipt opt-in", () => { const withEvidence = await startRunnerToolBridge({ tools: [tool("documents.read")], captureSemanticReceipt: () => () => {}, handler: async call => { expect(call.captureNormalizedInput).toBeUndefined(); return modelMetadata; } }); bridges.push(withEvidence); - const body = await (await rpc(withEvidence, { id: 1, method: "tools/call", params: { name: "documents.read", arguments: modelMetadata } })).json(); + const body = await (await rpc(withEvidence, { id: 1, method: "tools/call", params: { name: "documents.read", arguments: modelMetadata } })).json() as ToolCallBody; expect(readNativeSemanticReceipt({ contents: body.result.content })).toHaveProperty("normalizedInputSha256", null); const plain = await startRunnerToolBridge({ handler: async call => { expect(call.captureNormalizedInput).toBeUndefined(); return { accepted: true }; } }); bridges.push(plain); - const plainBody = await (await rpc(plain, { id: 2, method: "tools/call", params: { name: "paperclip_finish", arguments: rawFinish } })).json(); + const plainBody = await (await rpc(plain, { id: 2, method: "tools/call", params: { name: "paperclip_finish", arguments: rawFinish } })).json() as ToolCallBody; expect(readNativeSemanticReceipt({ contents: plainBody.result.content })).toBeNull(); }); it.each(["small", "chunked", "tagged", "error"])("preserves %s result encoding and captures scope before dispatch", async encoding => { diff --git a/packages/paperclip-runner/src/eval/workflow-evals.test.ts b/packages/paperclip-runner/src/eval/workflow-evals.test.ts index b4df89c143a..5c7a399fe1f 100644 --- a/packages/paperclip-runner/src/eval/workflow-evals.test.ts +++ b/packages/paperclip-runner/src/eval/workflow-evals.test.ts @@ -1,7 +1,7 @@ import { readFile, stat } from "node:fs/promises"; import { resolve } from "node:path"; -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { diff --git a/packages/paperclip-runner/src/evals/native-execution.test.ts b/packages/paperclip-runner/src/evals/native-execution.test.ts index 9700ae81b00..f241b35883b 100644 --- a/packages/paperclip-runner/src/evals/native-execution.test.ts +++ b/packages/paperclip-runner/src/evals/native-execution.test.ts @@ -4,7 +4,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { prpSchemaBundle } from "../protocol/generated/schema-bundle.js"; diff --git a/packages/paperclip-runner/src/live/acpx-request-recovery.test.ts b/packages/paperclip-runner/src/live/acpx-request-recovery.test.ts index 4eb952ed63d..c7ed7f36d3b 100644 --- a/packages/paperclip-runner/src/live/acpx-request-recovery.test.ts +++ b/packages/paperclip-runner/src/live/acpx-request-recovery.test.ts @@ -76,7 +76,7 @@ describe("live ACP request recovery", () => { recovered = await original.snapshot(); expect(recovered.pendingRuntimeRequests).toHaveLength(2); } finally { - await original.close(); + await original.close({ reason: "test complete" }); } class AdoptedTransport extends FakeCodexTransport { takeRestoredRuntimeRequests(): CodexRpcServerRequest[] { return requests; } @@ -99,7 +99,7 @@ describe("live ACP request recovery", () => { await expect(session.resolveRuntimeRequest?.({ requestId, turnId: "turn-1", resolution })).rejects.toThrow("no longer pending"); expect(deliver).toHaveBeenCalledTimes(1); } finally { - await session.close(); await drained; + await session.close({ reason: "test complete" }); await drained; expect(recoveredEvents.map(event => event.eventType)).not.toContain("runtime_request.created"); expect(recoveredEvents.filter(event => event.eventType === "runtime_request.expired").map(event => event.payload.requestId)).not.toContain(requestId); } diff --git a/packages/paperclip-runner/src/live/antigravity-candidate-startup.test.ts b/packages/paperclip-runner/src/live/antigravity-candidate-startup.test.ts index 80389f03b96..3776ad229ef 100644 --- a/packages/paperclip-runner/src/live/antigravity-candidate-startup.test.ts +++ b/packages/paperclip-runner/src/live/antigravity-candidate-startup.test.ts @@ -63,7 +63,9 @@ describe("Antigravity candidate trusted startup", () => { const root = await mkdtemp(join(tmpdir(), "antigravity-candidate-continuation-")); const admission = candidate(); const retained = { driverSessionId: "driver-retained", providerSessionId: "native-retained", providerIdentity: { - kind: "acpx", normalizedSessionId: identity.normalizedSessionId, + kind: "acpx" as const, normalizedSessionId: identity.normalizedSessionId, + acpxRecordId: "acpx-retained", backendSessionId: "backend-retained", agentSessionId: "native-retained", + workspaceDigest: `sha256:${"e".repeat(64)}`, providerLifetimeFenceCandidates: [1, 2, 3] as const, // The recovery binding digest is distinct from the release command digest. profileDigest: `sha256:${"a".repeat(64)}`, requestedModel: admission.model, effectiveModel: admission.model, } }; diff --git a/packages/paperclip-runner/src/live/clean-room-server.test.ts b/packages/paperclip-runner/src/live/clean-room-server.test.ts index 65b0872adab..f9a5df8b05c 100644 --- a/packages/paperclip-runner/src/live/clean-room-server.test.ts +++ b/packages/paperclip-runner/src/live/clean-room-server.test.ts @@ -15,6 +15,7 @@ import type { } from "../drivers/codex/app-server-transport.js"; import type { CapabilityIssueThreadSnapshot, CapabilityThreadItem } from "../issue-thread/types.js"; import type { CapabilityLiveTransportFactory } from "./live-session.js"; +import type { CapabilityRunnerdProcessEvidence } from "./runnerd-codex-transport.js"; import { createCapabilityTurnStreamDecoder, readCapabilityTurnStream, @@ -343,10 +344,25 @@ class FakeCleanRoomTransport implements CodexAppServerTransport { function fakeTransportFactory(state: FakeProviderState): CapabilityLiveTransportFactory { return (options) => { - const evidence = { + const evidence: CapabilityRunnerdProcessEvidence = { runnerPid: 7100, runnerProcessGroupId: 7100, + providerPid: null, + providerProcessStartedAt: null, codexPid: 7200, + codexProcessStartedAt: null, + sidecarPid: null, + sidecarProcessStartedAt: null, + agentPid: null, + agentProcessStartedAt: null, + providerDriver: null, + providerVersion: null, + acpxAgent: null, + agentServerVersion: null, + agentRuntimeVersion: null, + acpProtocolVersion: null, + providerExecutionKind: null, + providerService: null, runnerExited: false, runnerExitCode: null, runnerSignal: null, @@ -359,7 +375,7 @@ function fakeTransportFactory(state: FakeProviderState): CapabilityLiveTransport options.onEvidence?.(evidence); }); options.onEvidence?.(evidence); - return { transport, evidence: () => ({ ...evidence }) }; + return { transport, evidence: () => ({ ...evidence }), detachControllerForRestart: async () => {} }; }; } @@ -369,6 +385,8 @@ interface CleanRoomPayload { identity: { token: string; companyId: string; actorId: string; taskId: string; identifier: string }; limits: { maxTurns: number; maxMessageBytes: number }; view: CapabilityIssueThreadSnapshot; + toolResult?: unknown; + toolTurnId?: string | null; configuration: { provider: "codex" | "opencode"; model: string | null; @@ -801,9 +819,9 @@ describe("Capability clean-room chat server", () => { expect(after.toolTurnId).toMatch(/^turn-/); const turn = after.view.turns.find((candidate: { id: string }) => candidate.id === after.toolTurnId); expect(turn).toBeDefined(); - expect(turn.items.some((item: CapabilityThreadItem) => item.kind === "user_message")).toBe(true); - expect(turn.items.some((item: CapabilityThreadItem) => item.kind === "tool_activity" && item.operationId === "report_progress")).toBe(true); - expect(turn.items.some((item: CapabilityThreadItem) => item.kind === "durable_comment" && item.body === "Progress entered manually from DevTools.")).toBe(true); + expect(turn!.items.some((item: CapabilityThreadItem) => item.kind === "user_message")).toBe(true); + expect(turn!.items.some((item: CapabilityThreadItem) => item.kind === "tool_activity" && item.operationId === "report_progress")).toBe(true); + expect(turn!.items.some((item: CapabilityThreadItem) => item.kind === "durable_comment" && item.body === "Progress entered manually from DevTools.")).toBe(true); expect(after.view.evidence.state.at(-1)?.toRevision).toBeGreaterThan(0); }); diff --git a/packages/paperclip-runner/src/live/live-session.test.ts b/packages/paperclip-runner/src/live/live-session.test.ts index c3d4eb9abac..ec35ad7c5e8 100644 --- a/packages/paperclip-runner/src/live/live-session.test.ts +++ b/packages/paperclip-runner/src/live/live-session.test.ts @@ -24,7 +24,7 @@ import { type CapabilityLiveTurnEvent, } from "./live-session.js"; import { DurableCapabilityLiveSessionStore } from "./durable-live-session-store.js"; -import { defaultCapabilityRunnerdBinary } from "./runnerd-codex-transport.js"; +import { defaultCapabilityRunnerdBinary, type CapabilityRunnerdProcessEvidence } from "./runnerd-codex-transport.js"; import { persistedCursorUsageNotice } from "../drivers/acpx/usage-accounting.js"; import { captureTurnRejection } from "../../test/capture-turn-rejection.js"; import * as workspaceDiff from "./workspace-diff.js"; @@ -45,7 +45,7 @@ it("admits Pi live sessions without candidate opt-in while preserving the exact const session = await service.create({ provider: "acpx", acpxAgent: "pi", piThinkingLevel: "low", requestedModel: "openrouter/deepseek/deepseek-v4-flash-0731" }); try { expect(session.snapshot().config.acpxProfile).toMatchObject({ - agent: "pi", ...resolveQualifiedAcpxProfile("pi", "openrouter/deepseek/deepseek-v4-flash-0731"), + ...resolveQualifiedAcpxProfile("pi", "openrouter/deepseek/deepseek-v4-flash-0731"), agent: "pi", }); const custom = await service.create({ provider: "acpx", acpxAgent: "pi", piThinkingLevel: "low", requestedModel: "another-model" }); expect(custom.snapshot().config.acpxProfile).toMatchObject({ qualificationModel: "another-model", reportedModelId: "another-model" }); @@ -322,10 +322,25 @@ class FakeCapabilityCodexTransport implements CodexAppServerTransport { function fakeTransportFactory(state: FakeProviderState): CapabilityLiveTransportFactory { return (options) => { - const evidence = { + const evidence: CapabilityRunnerdProcessEvidence = { runnerPid: 4100 + state.transports.length, runnerProcessGroupId: 4100 + state.transports.length, + providerPid: null, + providerProcessStartedAt: null, codexPid: 5100 + state.transports.length, + codexProcessStartedAt: null, + sidecarPid: null, + sidecarProcessStartedAt: null, + agentPid: null, + agentProcessStartedAt: null, + providerDriver: null, + providerVersion: null, + acpxAgent: null, + agentServerVersion: null, + agentRuntimeVersion: null, + acpProtocolVersion: null, + providerExecutionKind: null, + providerService: null, runnerExited: false, runnerExitCode: null, runnerSignal: null, @@ -337,7 +352,7 @@ function fakeTransportFactory(state: FakeProviderState): CapabilityLiveTransport options.onEvidence?.(evidence); }); options.onEvidence?.(evidence); - return { transport, evidence: () => ({ ...evidence, runnerExited: transport.processInfo().exited }) }; + return { transport, evidence: () => ({ ...evidence, runnerExited: transport.processInfo().exited }), detachControllerForRestart: async () => {} }; }; } @@ -394,7 +409,7 @@ class TransientFailureLiveSessionStore implements CapabilityLiveSessionStore { throw new Error("transient live-session store failure"); } if ( - snapshot.terminalTurns.length > 0 && + snapshot.terminalTurns!.length > 0 && (this.#rejectTerminalSaves || this.#terminalSaveFailuresRemaining > 0) ) { if (this.#terminalSaveFailuresRemaining > 0) { @@ -647,7 +662,7 @@ describe("Capability live runnerd and Codex session", () => { outputTokens: 4, })).resolves.toBe("committed"); - expect((await store.load(session.id))?.usageLedger.map((receipt) => receipt.receiptId)).toEqual([ + expect((await store.load(session.id))?.usageLedger!.map((receipt) => receipt.receiptId)).toEqual([ "usage-before-recovery", "usage-after-recovery", ]); @@ -1159,7 +1174,7 @@ describe("Capability live runnerd and Codex session", () => { expect(first.snapshot.providerThreadId).toBe(second.snapshot.providerThreadId); expect(state.attachments).toHaveLength(1); expect(second.snapshot.providerRunBinding).toEqual(state.attachments[0]); - expect(second.snapshot.usageLedger.slice(-2)).toMatchObject([ + expect(second.snapshot.usageLedger!.slice(-2)).toMatchObject([ { providerRequests: 1, inputTokens: 100, outputTokens: 10 }, { providerRequests: 1, inputTokens: 100, outputTokens: 10 }, ]); @@ -1497,7 +1512,7 @@ describe("Capability live runnerd and Codex session", () => { const result = await session.sendMessage("report overlapping usage"); expect(result.snapshot.usageLedger).toHaveLength(1); - expect(result.snapshot.usageLedger[0]).toMatchObject({ + expect(result.snapshot.usageLedger![0]).toMatchObject({ providerRequests: 1, inputTokens: 100, outputTokens: 10, @@ -1596,7 +1611,7 @@ describe("Capability live runnerd and Codex session", () => { const result = await session.sendMessage("report late usage"); expect(result.snapshot.usageLedger).toHaveLength(1); - expect(result.snapshot.usageLedger[0]).toMatchObject({ + expect(result.snapshot.usageLedger![0]).toMatchObject({ providerRequests: 2, inputTokens: 100, outputTokens: 10, diff --git a/packages/paperclip-runner/src/live/runner-prepared-launch-workspace.test.ts b/packages/paperclip-runner/src/live/runner-prepared-launch-workspace.test.ts index 943871742f0..6112d7f87bb 100644 --- a/packages/paperclip-runner/src/live/runner-prepared-launch-workspace.test.ts +++ b/packages/paperclip-runner/src/live/runner-prepared-launch-workspace.test.ts @@ -106,10 +106,10 @@ it.each([ const recovered = await restarted.backend.recoverSession!(snapshot, { signal: new AbortController().signal }); expect(recovered).toMatchObject({ recovered: true }); if (!recovered.recovered) throw new Error(recovered.reason); - expect((await recovered.session.snapshot()).workingDirectory).toBe(cwd); + expect((await recovered.session!.snapshot()).workingDirectory).toBe(cwd); // Recovery reuses the saved preparation instead of resolving a new directory. expect(restarted.prepareLaunch.mock.calls[0]![1]).toMatchObject({ configurationId: "plugin-run" }); - await recovered.session.close({ reason: "test complete" }); + await recovered.session!.close({ reason: "test complete" }); const moved = await pluginBackend(root, join(root, "unused"), "runner_prepared_launch"); await expect(moved.backend.recoverSession!({ ...snapshot, workingDirectory: join(root, "elsewhere") }, diff --git a/packages/paperclip-runner/src/live/runnerd-acpx-questions.test.ts b/packages/paperclip-runner/src/live/runnerd-acpx-questions.test.ts index 4d208fc1652..e5fe9de7647 100644 --- a/packages/paperclip-runner/src/live/runnerd-acpx-questions.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-acpx-questions.test.ts @@ -24,7 +24,7 @@ describe("ACPX questions through the shared runner transport", () => { [timing!.id]: { selectedOptionIds: [timing!.options![1]!.id] }, } }; expect(runtimeRequestResponse({ requestId: "question-1", requestKind: "elicitation", method: origin.method, - turnId: "turn", itemId: "item", status: "pending", prompt: "Interview", input: shown }, { action: "submit", response }, context)).toEqual({ action: "submit", response }); + turnId: "turn", itemId: "item", status: "pending", prompt: "Interview", details: {}, input: shown }, { action: "submit", response }, context)).toEqual({ action: "submit", response }); expect(form.accept(response)).toEqual({ action: "accept", content: { organization: "Garden club", goal: "Welcome note", timing: "later" } }); }); it("does not admit an invalid canonical question set", () => { diff --git a/packages/paperclip-runner/src/live/runnerd-acpx-source-reconciliation.test.ts b/packages/paperclip-runner/src/live/runnerd-acpx-source-reconciliation.test.ts index bb26e9428b5..34263fd42a7 100644 --- a/packages/paperclip-runner/src/live/runnerd-acpx-source-reconciliation.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-acpx-source-reconciliation.test.ts @@ -47,7 +47,7 @@ async function fixture(agent: "codex" | "claude" = "codex", nativeSource = false binding: { companyId: nativeIdentity.companyId, agentId: nativeIdentity.agentId, issueId: nativeIdentity.issueId, runId: identity.runId, executionWorkspaceId: "workspace" }, task: { identifier: "TASK-1", title: "Complete fake work", description: null, prompt: "Complete fake work", workMode: "standard" }, workspace: { cwd: root, repoUrl: null, repoRef: null, branchName: null }, - session: { normalizedSessionId: identity.normalizedSessionId, driverKind: "acpx_runtime", protocolVersion: 1 }, + session: { normalizedSessionId: identity.normalizedSessionId, driverKind: "acpx_runtime", protocolVersion: 1, lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null } }, provider: { kind: "acpx", agent, model: "explicit-source-model", permissionPolicy: "interactive", profile: resolveQualifiedAcpxProfile(agent, "explicit-source-model") }, completionContract: { id: "contract", sha256: sha("contract"), schemaVersion: "paperclip.completion-contract.v1", contract: { revision: "1", objective: "Complete fake work", criteria: [{ id: "objective", requirement: "Complete fake work" }] } }, interactionResponses: [], credentialBindings: [] }; @@ -147,7 +147,7 @@ it.skipIf(process.platform === "win32")("recovers and adopts ACPX through the re environment: { PAPERCLIP_WORKSPACE_CWD: f.root }, dynamicTools: [] }); let recovered = await backend().recoverSession!(f.nativeSnapshot!, { signal: new AbortController().signal }); expect(recovered.recovered).toBe(true); - const pid = source.evidence().runnerPid, startedAt = source.transport.processInfo().startedAt; + const pid = source.evidence().runnerPid, startedAt = source.transport.processInfo!().startedAt; expect(await f.count("turn.start")).toBe(1); expect(await f.count("processStart")).toBe(2); await recovered.session!.detachControllerForRestart!(); const launch = vi.fn(() => { throw new Error("duplicate source launch"); }); @@ -197,7 +197,7 @@ it.skipIf(process.platform === "win32").each(["codex", "claude", "adopt", "pause await expect(source.transport.request("turn/start", {})).rejects.toThrow("source_attachment_pending"); const pid = source.evidence().runnerPid; if (mode === "adopt") { - const startedAt = source.transport.processInfo().startedAt; + const startedAt = source.transport.processInfo!().startedAt; await source.detachControllerForRestart(); const launch = vi.fn(() => { throw new Error("duplicate source launch"); }); source = f.recover({ runnerProcessLauncher: launch, authorizeRunnerAdoption: async () => { throw new Error("old source configuration"); }, diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport-adoption.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport-adoption.test.ts index 424db66444b..664f7db4a44 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport-adoption.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport-adoption.test.ts @@ -38,7 +38,7 @@ async function verifyLiveRunnerAdoption( evidence: [], verification: [], attentionRequests: [], artifacts: [], })); const server = createServer(); - let authority: DurablePrpControlPlane | null = null; + let authority = null as DurablePrpControlPlane | null; const checkpoint = vi.fn(async () => undefined); server.on("upgrade", (request, socket, head) => { if (!authority) { @@ -136,7 +136,7 @@ async function verifyLiveRunnerAdoption( ); } runnerPid = first.evidence().runnerPid; - runnerStartedAt = first.transport.processInfo().startedAt; + runnerStartedAt = first.transport.processInfo!().startedAt; expect(runnerPid).toEqual(expect.any(Number)); if (goalMidTurn) { @@ -404,7 +404,7 @@ async function verifyLiveRunnerAdoption( } else expect(redispatchedTool).not.toHaveBeenCalled(); } expect(adopted.evidence().runnerPid).toBe(runnerPid); - expect(adopted.transport.processInfo().startedAt).toBe(runnerStartedAt); + expect(adopted.transport.processInfo!().startedAt).toBe(runnerStartedAt); if (startWithoutCheckpoint) { const retained = JSON.parse(await readFile(controlPlaneStatePath, "utf8")); for (const type of ["run.prepare", "session.open"]) { @@ -448,7 +448,7 @@ async function verifyLiveRunnerAdoption( expect(await Promise.race([notificationsClosed, new Promise(resolve => { queueDeadline = setTimeout(() => resolve(false), 250); })])).toBe(true); } finally { clearTimeout(queueDeadline); } await expect(adopted.transport.close()).rejects.toBe(probeError); - expect(adopted.transport.processInfo()).toMatchObject({ exited: false }); + expect(adopted.transport.processInfo!()).toMatchObject({ exited: false }); expect(() => process.kill(runnerPid!, 0)).not.toThrow(); expect(signal).not.toHaveBeenCalled(); expect(checkpoint).not.toHaveBeenCalled(); diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport-attachment-recovery.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport-attachment-recovery.test.ts index d672d9bb2e3..e81f64320c7 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport-attachment-recovery.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport-attachment-recovery.test.ts @@ -14,9 +14,18 @@ import { expect, it, vi } from "vitest"; import { DurablePrpControlPlane } from "../control-plane/durable-prp-control-plane.js"; import * as durableControlPlane from "../control-plane/durable-prp-control-plane.js"; import { createCapabilityRunnerdCodexTransport, readRunnerdArtifactBinding } from "./runnerd-codex-transport.js"; -import { defaultCapabilityRunnerdBinary, fakeCodex, fakeCodexArgs } from "../../test/support/runnerd-transport.js"; +import { committableStore, defaultCapabilityRunnerdBinary, fakeCodex, fakeCodexArgs } from "../../test/support/runnerd-transport.js"; -it.each([ +type WarmAttachRecoveryCase = { + lossPoint: string; + routed: boolean; + recoveryFault: string; + executionPrepared?: boolean; + externalArtifact?: boolean; + ordinaryFollowup?: boolean | string; +}; + +it.each([ ...[ "before-result", "after-result", @@ -478,14 +487,15 @@ it.each([ ).toBe(false); return; } - const commit = core.store.commit.bind(core.store); + const store = committableStore(core); + const commit = store.commit.bind(store); let lossObserved = false; let signalLoss!: () => void; const loss = new Promise((resolveLoss) => { signalLoss = resolveLoss; }); commitSpy = vi - .spyOn(core.store, "commit") + .spyOn(store, "commit") .mockImplementation((candidate) => { // The selected process-crash boundary stays unavailable until the // exact owned runner is joined. A later replay must not silently diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport-deadlines.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport-deadlines.test.ts index 1f0fbaef19f..06cd0ae3735 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport-deadlines.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport-deadlines.test.ts @@ -67,7 +67,7 @@ it("keeps a quiet active Codex turn in the same process across connection lease expect(calls).not.toContain("turn/interrupt"); expect(calls).not.toContain("thread/resume"); const state = JSON.parse(await readFile(join(stateDirectory, "fake-codex-state.json"), "utf8")); - expect(state.threadId).toBe(opened.thread.id); + expect(state.threadId).toBe((opened.thread as { id: string }).id); expect(state.activeTurnId).toBe("provider-turn-1"); } finally { await bundle.transport.close(); diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport-settlement.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport-settlement.test.ts index 9c97cd48540..f06b0a3e8bf 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport-settlement.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport-settlement.test.ts @@ -286,7 +286,7 @@ it.each([ // output plus another full provider-owned prefix behind the old suspend. const runnerFile = join(original, "runner/runner-state.json"); const runnerBefore = JSON.parse(await readFile(runnerFile, "utf8")); - const template = builder.store.state.committedEvents[0]!.envelope.payload; + const template = builder.store.state.committedEvents[0]!.envelope.payload as Record; for (let index = 0; index < 90; index++) { const sourceSeq = runnerBefore.nextSourceSeq++; const event = { @@ -388,7 +388,7 @@ it.each([ sessionId: identity.normalizedSessionId, }, backend: { - kind: "codex", + kind: "runner" as const, name: maintenanceFixtureBackendName(fixtureId), }, identity, @@ -406,7 +406,7 @@ it.each([ recordEpoch, signal: maintenanceAbort.signal, }; - const close = vi.fn(async () => { + const close = vi.fn(async (): Promise => { throw new NativeSessionCloseUnrecoverableError(); }); const start = vi.fn(async () => { @@ -424,9 +424,10 @@ it.each([ capabilities: async () => capabilities, events: async function* () {}, startTurn: start, + result: async () => { throw new Error("fixture never produces a result"); }, close, snapshot: async () => ({ - backendKind: "codex", + backendKind: "runner", sessionId: input.binding.sessionId, identity: input.binding, providerSessionId: thread.thread.id, @@ -471,6 +472,7 @@ it.each([ normalizedSessionId: input.binding.sessionId, driverKind: "codex_app_server", protocolVersion: 1, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, }, completionContract: { id: "contract-maintenance", diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport-suspension.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport-suspension.test.ts index dba8ec470b3..004453760bd 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport-suspension.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport-suspension.test.ts @@ -211,6 +211,7 @@ it("does not retry a real memoized transport close whose suspension proof is una normalizedSessionId: identity.sessionId, driverKind: "codex_app_server", protocolVersion: 1, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, }, provider: { kind: "codex", model: null }, completionContract: { @@ -499,7 +500,7 @@ it.each([ message: { role: "user", text: "Keep working until interrupted." }, }); if (label === "interrupted") { - await session.interrupt({ + await session.interrupt!({ turnId: turn.turnId, reason: "Stopped by the user", }); @@ -525,7 +526,7 @@ it.each([ }, }); } finally { - await session.close().catch(() => undefined); + await session.close({ reason: "test complete" }).catch(() => undefined); await rm(stateDirectory, { recursive: true, force: true, diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport-warm-attachment.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport-warm-attachment.test.ts index 7739d4e2851..083290d5fae 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport-warm-attachment.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport-warm-attachment.test.ts @@ -13,6 +13,7 @@ import { codexSemanticToolSpecs } from "../drivers/codex/codex-app-server-driver import { releaseMaterializedNativeRuntimeSkills } from "../drivers/runtime-context-materializer.js"; import { createCapabilityRunnerdCodexTransport, readRunnerdArtifactBinding } from "./runnerd-codex-transport.js"; import { + committableStore, defaultCapabilityRunnerdBinary, fakeCodex, fakeCodexArgs, @@ -163,8 +164,9 @@ it.each([ providerPid = bundle.evidence().codexPid; const rotations: (typeof core.store.state)[] = []; const preparedStates: (typeof core.store.state)[] = []; - const commit = core.store.commit.bind(core.store); - vi.spyOn(core.store, "commit").mockImplementation((candidate) => { + const store = committableStore(core); + const commit = store.commit.bind(store); + vi.spyOn(store, "commit").mockImplementation((candidate) => { commit(candidate); // Capture the durable old epoch at publication. The authenticated // successor may activate before attachRun observes the completed command. @@ -716,7 +718,7 @@ it("recovers an active second warm business run after deleting its controller co await writeFile(join(skillRoot, "SKILL.md"), "# Pinned skill\n"); await writeFile(join(instructionRoot, "AGENTS.md"), "Pinned instructions\n"); const server = createServer(); - let authority: DurablePrpControlPlane | null = null, generation = 0; + let authority = null as DurablePrpControlPlane | null, generation = 0; let checkpoint: durableControlPlane.DurablePrpCheckpoint | undefined; const registered = new WeakSet(); server.on("upgrade", (request, socket, head) => authority @@ -760,7 +762,7 @@ it("recovers an active second warm business run after deleting its controller co providerPid = first.evidence().codexPid; expect(providerPid).toEqual(expect.any(Number)); const providerStart = execFileSync("ps", ["-p", String(providerPid), "-o", "lstart="], { encoding: "utf8" }).trim(); - const startedAt = first.transport.processInfo().startedAt; + const startedAt = first.transport.processInfo!().startedAt; const firstCompleted = waitComplete(first.transport); await within("first turn/start", first.transport.request("turn/start", { input: [{ type: "text", text: "first healthy business run" }] })); await within("healthy first turn completion", firstCompleted); @@ -805,7 +807,7 @@ it("recovers an active second warm business run after deleting its controller co expect(execFileSync("ps", ["-p", String(providerPid), "-o", "lstart="], { encoding: "utf8" }).trim()).toBe(providerStart); expect(calls).toBe(2); expect(authority!.store.state.commands.filter(command => command.type === "semantic_tool.result")).toHaveLength(0); - expect(recovered.transport.processInfo().startedAt).toBe(startedAt); + expect(recovered.transport.processInfo!().startedAt).toBe(startedAt); expect(authority!.store.state.identity).toEqual(savedIdentity); expect(authority!.store.state.commands.filter(command => command.type === "turn.start")).toHaveLength(1); expect(noSpawn).not.toHaveBeenCalled(); 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 1c2b35e6f7c..87253a57f48 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts @@ -283,7 +283,7 @@ it.each(["resolved", "rejected"] as const)( expect(error).toMatchObject({ message: expect.stringContaining("runner_process_termination_unconfirmed") }); if (outcome === "rejected") expect(error).toHaveProperty("cause", probeError); expect(children).toHaveLength(1); - expect(bundle.transport.processInfo()).toMatchObject({ exited: false }); + expect(bundle.transport.processInfo!()).toMatchObject({ exited: false }); expect(() => process.kill(children[0]!.pid!, 0)).not.toThrow(); } finally { const providerPid = bundle.evidence().codexPid; @@ -358,7 +358,7 @@ it("preserves the cause when a replacement runner's health cannot be verified", if (child) return { child: { pid: 42, exitCode: null, kill: () => false }, completion: Promise.reject(probeError), - isExitConfirmed: () => false, + isExitConfirmed: (): boolean => false, }; child = spawn(spec.command, [...spec.args], { cwd: spec.cwd, env: spec.environment, stdio: "ignore" }); const ownedChild = child; @@ -390,7 +390,7 @@ it("preserves the cause when a replacement runner's health cannot be verified", }); expect(error).toHaveProperty("message", expect.stringContaining("probe_timeout")); expect(launch).toHaveBeenCalledTimes(2); - expect(bundle.transport.processInfo()).toMatchObject({ exited: false }); + expect(bundle.transport.processInfo!()).toMatchObject({ exited: false }); } finally { const providerPid = bundle.evidence().codexPid; if (providerPid) { @@ -415,7 +415,7 @@ it("retries a replacement whose staging failed before any process launched", asy if (launch.mock.calls.length === 2) return { child: { pid: undefined, exitCode: null, kill: () => false }, completion: Promise.reject(stagingError), - isExitConfirmed: () => true, + isExitConfirmed: (): boolean => true, }; const child = spawn(spec.command, [...spec.args], { cwd: spec.cwd, env: spec.environment, stdio: "ignore" }); children.push(child); @@ -2659,7 +2659,7 @@ it.each(["reuse", "large-instructions", "rotated-recovery", "removed-context", " resolved.skills[0]!.bundle.rootPath = "/guest/.agents/skills/assigned"; const materialize = vi.spyOn(runtimeMaterializer, "prepareIsolatedCodexHome").mockRejectedValue(new Error("unexpected Core materialization")); const discover = vi.spyOn(codexCommandRuntime, "resolveCodexCommand").mockImplementation(() => { throw new Error("unexpected Core discovery"); }); - let configuration: Record | undefined; + let configuration: Record | null | undefined; let recovered = false; let requestedRun = ""; let requestedInstructions = ""; @@ -3574,7 +3574,7 @@ it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooper let template: Record | null = null; let registrationStarted!: () => void; const started = new Promise(resolve => { registrationStarted = resolve; }); - let returnRegistration!: (registration: { connection: { mode: "listen"; listenAddress: string; listenPort: number; listenPath: string }; release: () => Promise }) => void; + let returnRegistration!: (registration: { connection: { mode: "listen"; listenAddress: "0.0.0.0"; listenPort: number; listenPath: string }; release: () => Promise }) => void; const registration = new Promise[0]>(resolve => { returnRegistration = resolve; }); let finishRelease!: () => void; const releasing = new Promise(resolve => { finishRelease = resolve; }); @@ -3585,7 +3585,7 @@ it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooper if (releaseFails) throw releaseError; }); const registered = { - connection: { mode: "listen" as const, listenAddress: "127.0.0.1", listenPort: 43127, listenPath: "/setup-probe" }, + connection: { mode: "listen" as const, listenAddress: "0.0.0.0" as const, listenPort: 43127, listenPath: "/setup-probe" }, release, }; let registrationSignal: AbortSignal | undefined; @@ -4037,7 +4037,7 @@ it("runs the lab provider boundary through authenticated durable PRP", async () } expect(methods).toContain("turn/completed"); expect(terminalParams).toMatchObject({ - threadId: opened.thread.id, + threadId: (opened.thread as { id: string }).id, turnId: "provider-turn-1", }); expect(bundle.evidence().diagnostics).toContain( @@ -4081,7 +4081,7 @@ it("controls a Codex session goal end to end through durable PRP v2", async () = }, ], }); - const threadId = opened.thread.id; + const threadId = (opened.thread as { id: string }).id; await expect( bundle.transport.request("thread/goal/set", { @@ -4236,7 +4236,7 @@ it.each([false, true])("binds goal turns through the full Codex harness (autonom }); expect(observed.some((event) => event.eventType === "session.failed")).toBe(false); } finally { - await session?.close(); + await session?.close({ reason: "test complete" }); await bundle.transport.close(); await rm(stateDirectory, { recursive: true, force: true }); } @@ -4882,7 +4882,7 @@ it("binds an immediately failed durable turn before exposing its terminal", asyn ], }); } finally { - await session.close(); + await session.close({ reason: "test complete" }); await rm(stateDirectory, { recursive: true, force: true, @@ -5089,9 +5089,10 @@ it("captures exact provider frames and correlates Rust and TypeScript interpreta if (event.eventType === "turn.completed") break; } } finally { - await session.close(); + await session.close({ reason: "test complete" }); } const snapshot = await session.snapshot(); + if (snapshot.lastSourceSequence === undefined) throw new Error("Expected a source sequence in the session snapshot"); for ( let sourceSeq = 1; sourceSeq <= snapshot.lastSourceSequence; @@ -5791,7 +5792,7 @@ it.each([ expect(outcomes[1].result).toMatchObject({ content: [{ text: expect.stringContaining(feedback) }] }); if (adoptAfterTurn) { const snapshot = await session.snapshot(); - const runnerStartedAt = bundle.transport.processInfo().startedAt; + const runnerStartedAt = bundle.transport.processInfo!().startedAt; survivingPid = bundle.evidence().runnerPid; expect(survivingPid).toEqual(expect.any(Number)); const artifact = readRunnerdArtifactBinding(defaultCapabilityRunnerdBinary()); @@ -5833,7 +5834,7 @@ it.each([ expect(recovery.session).toBeDefined(); session = recovery.session!; expect(adopted.evidence().runnerPid).toBe(survivingPid); - expect(adopted.transport.processInfo().startedAt).toBe(runnerStartedAt); + expect(adopted.transport.processInfo!().startedAt).toBe(runnerStartedAt); expect((await session.snapshot()).providerSessionId).toBe(snapshot.providerSessionId); expect(duplicateLauncher).not.toHaveBeenCalled(); } @@ -5843,7 +5844,7 @@ it.each([ expect(allRequests.map(request => request.variant)).toEqual(supportsReasoning ? ["paperclip-no-reasoning", "paperclip-default"] : [undefined, undefined]); }, - closeSession: async () => { await session?.close(); }, + closeSession: async () => { await session?.close({ reason: "test complete" }); }, closeTransport: async () => { try { await adopted?.transport.close(); } finally { diff --git a/packages/paperclip-runner/src/live/runnerd-detach-durability.test.ts b/packages/paperclip-runner/src/live/runnerd-detach-durability.test.ts index d49672cf964..add7ff63fa8 100644 --- a/packages/paperclip-runner/src/live/runnerd-detach-durability.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-detach-durability.test.ts @@ -3,7 +3,7 @@ import fs from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { expect, it, vi } from "vitest"; -import { DurablePrpControlPlane } from "../control-plane/durable-prp-control-plane.js"; +import { DurablePrpControlPlane, type RunnerProcessResult } from "../control-plane/durable-prp-control-plane.js"; import { createCapabilityRunnerdCodexTransport, defaultCapabilityRunnerdBinary, readRunnerdArtifactBinding, runnerdRecoveryInternals } from "./runnerd-codex-transport.js"; it.each(["resolved", "rejected"] as const)("keeps the checkpoint behind controller retirement (%s)", async outcome => { @@ -39,7 +39,7 @@ it.each(["drain", "timeout", "external_close"] as const)("joins an output flush const artifact = readRunnerdArtifactBinding(binary); let core!: DurablePrpControlPlane; const start = DurablePrpControlPlane.prototype.start; - const startSpy = vi.spyOn(DurablePrpControlPlane.prototype, "start").mockImplementation(function (port) { + const startSpy = vi.spyOn(DurablePrpControlPlane.prototype, "start").mockImplementation(function (this: DurablePrpControlPlane, port) { core = this; return start.call(this, port); }); @@ -81,7 +81,7 @@ it.each(["drain", "timeout", "external_close"] as const)("joins an output flush let stderr = ""; owned.stderr?.on("data", chunk => { stderr += String(chunk); }); exit = new Promise(resolveExit => owned.once("exit", () => resolveExit())); - const completion = new Promise<{ code: number | null; signal: string | null; stdout: string; stderr: string }>((resolveExit, reject) => { + const completion = new Promise((resolveExit, reject) => { owned.once("error", reject); owned.once("exit", (code, signal) => resolveExit({ code, signal, stdout: "", stderr })); }); diff --git a/packages/paperclip-runner/src/live/runnerd-opencode-streaming-tools.test.ts b/packages/paperclip-runner/src/live/runnerd-opencode-streaming-tools.test.ts index 604cc561a98..1809b0cfc53 100644 --- a/packages/paperclip-runner/src/live/runnerd-opencode-streaming-tools.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-opencode-streaming-tools.test.ts @@ -76,14 +76,14 @@ it.skipIf(!compilerAvailable || !existsSync(runner)).each(["discovery", "complet expect(seen).toEqual(tool !== "completion" ? ["paperclip_search_assigned_tools", "paperclip_finish"] : ["paperclip_finish"]); expect(text.join("")).toBe("Streaming progress. ".repeat(800)); if (tool === "durable") { - const runnerPid = transport.processInfo().pid; + const runnerPid = transport.processInfo!().pid; await transport.attachRun!({ runId: "next-settled-run", turnId: "next-turn", itemId: "next-item" }); completed = (async () => { for await (const event of transport.notifications()) if (event.method === "turn/completed") return; })(); await transport.request("turn/start", { input: [{ type: "text", text: "stream-backlog-discovery" }] }); await completed; - expect(transport.processInfo().pid).toBe(runnerPid); + expect(transport.processInfo!().pid).toBe(runnerPid); expect(seen).toEqual(["paperclip_search_assigned_tools", "paperclip_finish", "paperclip_search_assigned_tools", "paperclip_finish"]); const provider = JSON.parse(await readFile(join(root, "state", "runner", "codex-provider-state.json"), "utf8")); expect(provider.toolBridge.pending).toEqual({}); diff --git a/packages/paperclip-runner/src/live/runnerd-restart-continuation.test.ts b/packages/paperclip-runner/src/live/runnerd-restart-continuation.test.ts index e1e10951657..efc2e95833b 100644 --- a/packages/paperclip-runner/src/live/runnerd-restart-continuation.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-restart-continuation.test.ts @@ -15,7 +15,7 @@ it.each(["none", "before-start", "after-start"] as const)("completes after the r const execution: NativeExecutionInputV1 = { schema: "paperclip.native-execution-input.v1", binding: { runId: identity.runId, companyId: identity.companyId, issueId: identity.issueId, agentId: identity.agentId, executionWorkspaceId: "workspace-restart" }, - session: { normalizedSessionId: identity.sessionId, driverKind: "codex_app_server", protocolVersion: 1 }, + session: { normalizedSessionId: identity.sessionId, driverKind: "codex_app_server", protocolVersion: 1, lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null } }, task: { identifier: "TEST-1", title: "Finish checks", description: null, prompt: "Watch the already running checks and finish the request.", workMode: "standard" }, workspace: { cwd: root, repoUrl: null, repoRef: null, branchName: null }, provider: { kind: "codex", model: null }, @@ -88,7 +88,7 @@ it.each(["none", "before-start", "after-start"] as const)("completes after the r throw interruptedController; } }, - async appendEvent(event) { events.push(event); durableEvents.push(event); return { cursor: durableEvents.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, + async appendEvent(event) { if (!("sourceSeq" in event)) throw new Error("Expected a durable PRP event"); events.push(event); durableEvents.push(event); return { cursor: durableEvents.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents(request) { const source = durableEvents.filter(event => event.sourceInstanceId === request.sourceInstanceId); return { events: source.filter(event => event.sourceSeq > request.afterSourceSeq), highestContiguousSourceSeq: source.at(-1)?.sourceSeq ?? 0 }; diff --git a/packages/paperclip-runner/src/live/runnerd-runtime-activity.test.ts b/packages/paperclip-runner/src/live/runnerd-runtime-activity.test.ts index 96bec323f05..6e2e3c46e14 100644 --- a/packages/paperclip-runner/src/live/runnerd-runtime-activity.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-runtime-activity.test.ts @@ -63,7 +63,7 @@ it("delivers authenticated runtime activity through the real Runner bridge acros const firstContext = (await helper(["notification-context"])).stdout.trim(); for (const runId of ["activity-first", "activity-next"]) { if (runId === "activity-next") { - attaching = session.attachRun!({ runId }); + attaching = Promise.resolve(session.attachRun!({ runId })); await releasing; await vi.waitFor(async () => { expect((await helper(["notification-context"])).stdout.trim()).not.toBe(firstContext); @@ -93,7 +93,7 @@ it("delivers authenticated runtime activity through the real Runner bridge acros } finally { releasePrevious(); await attaching?.catch(() => undefined); - await session?.close(); + await session?.close({ reason: "test complete" }); await bundle.transport.close(); await rm(root, { recursive: true, force: true }); } diff --git a/packages/paperclip-runner/src/live/runnerd-saved-continuation.test.ts b/packages/paperclip-runner/src/live/runnerd-saved-continuation.test.ts index 8f750ea11e7..72fcb3c100c 100644 --- a/packages/paperclip-runner/src/live/runnerd-saved-continuation.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-saved-continuation.test.ts @@ -49,7 +49,7 @@ it.each(["omitted", "null", "resolved"] as const)("retains a server-owned worksp expect(terminals).toHaveLength(1); expect(terminals[0].envelope.payload).toMatchObject({ runId, eventType: "run.terminal", payload: { runTerminalState: "succeeded", reportedWorkDisposition: "yielded" } }); } finally { - await session?.close(); + await session?.close({ reason: "test complete" }); await bundle.transport.close(); await rm(root, { recursive: true, force: true }); } diff --git a/packages/paperclip-runner/src/live/runnerd-source-reconciliation.test.ts b/packages/paperclip-runner/src/live/runnerd-source-reconciliation.test.ts index 0728c1b8832..6ef8adc0f4b 100644 --- a/packages/paperclip-runner/src/live/runnerd-source-reconciliation.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-source-reconciliation.test.ts @@ -61,7 +61,7 @@ async function sourceFixture(registration?: CapabilityRunnerdCodexTransportOptio runId: identity.runId, executionWorkspaceId: "workspace" }, task: { identifier: "TASK-1", title: "Complete fake work", description: null, prompt: "Complete fake work", workMode: "standard" }, workspace: { cwd: root, repoUrl: null, repoRef: null, branchName: null }, - session: { normalizedSessionId: identity.normalizedSessionId, driverKind: "codex_app_server", protocolVersion: 1 }, + session: { normalizedSessionId: identity.normalizedSessionId, driverKind: "codex_app_server", protocolVersion: 1, lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null } }, provider: { kind: "codex", model: null, approvalPolicy: "never" }, completionContract: { id: "contract", sha256: `sha256:${"a".repeat(64)}`, schemaVersion: "paperclip.completion-contract.v1", contract: { revision: "1", objective: "Complete fake work", criteria: [{ id: "objective", requirement: "Complete fake work" }] } }, interactionResponses: [], credentialBindings: [] }; @@ -231,7 +231,7 @@ it.each([["launch", "default"], ["adopt", "default"], ["adopt-denied", "default" expect((await read(fixture.coreFile)).preparedLaunchConfigurations).toEqual(before.preparedLaunchConfigurations); await expect(readFile(join(fixture.root, "launch-preparation.json"))).rejects.toHaveProperty("code", "ENOENT"); if (mode !== "launch") { - const evidence = resumed.evidence(), startedAt = resumed.transport.processInfo().startedAt; + const evidence = resumed.evidence(), startedAt = resumed.transport.processInfo!().startedAt; await resumed.detachControllerForRestart(); prepare.mockClear(); if (mode === "adopt-denied") prepare.mockRejectedValueOnce(new Error("source preparation authorization revoked")); diff --git a/packages/paperclip-runner/src/live/runnerd-staged-handoff.test.ts b/packages/paperclip-runner/src/live/runnerd-staged-handoff.test.ts index 4e16fd7a3b7..bdad642b3d5 100644 --- a/packages/paperclip-runner/src/live/runnerd-staged-handoff.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-staged-handoff.test.ts @@ -205,7 +205,7 @@ it.each([["connect", "owned"], ["listen", "owned"], ["external", "owned"], ["con try { const opened = await within(first.transport.request("thread/start", { cwd: root, dynamicTools: [] }), "source open"); runnerPid = first.evidence().runnerPid; providerPid = first.evidence().providerPid; - const startedAt = first.transport.processInfo().startedAt; + const startedAt = first.transport.processInfo!().startedAt; const providerStart = execFileSync("ps", ["-p", String(providerPid), "-o", "lstart="], { encoding: "utf8" }).trim(); expect(runnerPid).toBeGreaterThan(0); expect(providerPid).toBeGreaterThan(0); const attach = first.transport.attachRun!({ runId: target.runId, turnId: target.turnId, itemId: target.itemId }); diff --git a/packages/paperclip-runner/src/live/runnerd-trace-frame-index.test.ts b/packages/paperclip-runner/src/live/runnerd-trace-frame-index.test.ts index 2c1731e7ee2..63cb83f5fcb 100644 --- a/packages/paperclip-runner/src/live/runnerd-trace-frame-index.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-trace-frame-index.test.ts @@ -9,6 +9,9 @@ vi.mock("node:fs", async (importOriginal) => { return { ...fs, readSync: vi.fn(fs.readSync) }; }); +type PositionalRead = [fd: number, buffer: NodeJS.ArrayBufferView, offset: number, length: number, position: number | null]; +const readPosition = (call: number) => (vi.mocked(readSync).mock.calls[call] as unknown as PositionalRead)[4]; + const interpretation = (frameId: number, ...emittedEventIds: string[]) => `${JSON.stringify({ kind: "interpretation", frameId, emittedEventIds })}\n`; const settled = `${JSON.stringify({ kind: "trace_status", debugChannel: "rust_native", status: "complete" })}\n`; @@ -41,7 +44,7 @@ describe("runnerd trace frame index", () => { expect(index.locate(path, "pending-1")).toEqual({ frameId: 2, nativeChannelSettled: true }); expect(index.locate(path, "missing")).toEqual({ frameId: null, nativeChannelSettled: true }); expect(readSync).toHaveBeenCalledTimes(2); - expect(vi.mocked(readSync).mock.calls[1]![4]).toBe(Buffer.byteLength(first)); + expect(readPosition(1)).toBe(Buffer.byteLength(first)); }); it("retains partial lines and split UTF-8 until the append is complete", () => { @@ -87,7 +90,7 @@ describe("runnerd trace frame index", () => { expect(index.locate(path, "last").frameId).toBeNull(); expect(index.locate(path, "last").frameId).toBeNull(); expect(index.locate(path, "last").frameId).toBe(4); - for (const call of vi.mocked(readSync).mock.calls) expect(call[3]).toBeLessThanOrEqual(1024 * 1024); + for (const call of vi.mocked(readSync).mock.calls as unknown as PositionalRead[]) expect(call[3]).toBeLessThanOrEqual(1024 * 1024); const calls = vi.mocked(readSync).mock.calls.length; for (let i = 0; i < 1000; i++) index.locate(path, "missing"); expect(readSync).toHaveBeenCalledTimes(calls); @@ -124,8 +127,8 @@ describe("runnerd trace frame index", () => { } expect(readSync).toHaveBeenCalledTimes(72); for (let i = 0; i < 24; i++) { - expect(vi.mocked(readSync).mock.calls[24 + i]![4]).toBe(1024 * 1024); - expect(vi.mocked(readSync).mock.calls[48 + i]![4]).toBe(2 * 1024 * 1024); + expect(readPosition(24 + i)).toBe(1024 * 1024); + expect(readPosition(48 + i)).toBe(2 * 1024 * 1024); } }); diff --git a/packages/paperclip-runner/src/live/runnerd-welcome.integration.test.ts b/packages/paperclip-runner/src/live/runnerd-welcome.integration.test.ts index 49d44895434..933dc68564f 100644 --- a/packages/paperclip-runner/src/live/runnerd-welcome.integration.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-welcome.integration.test.ts @@ -39,7 +39,7 @@ it("starts one provider after delayed bootstrap admission and retains it for a s await bundle.transport.request("turn/start", { input: [{ type: "text", text }] }); for await (const event of bundle.transport.notifications()) { if (event.method === "turn/completed") { - expect(event.params).toMatchObject({ threadId: opened.thread.id }); + expect(event.params).toMatchObject({ threadId: (opened.thread as { id: string }).id }); terminalTurns.push(event.params); break; } diff --git a/packages/paperclip-runner/src/live/scenario-chat-demo-server.test.ts b/packages/paperclip-runner/src/live/scenario-chat-demo-server.test.ts index 59c88dfdd32..9b22f8f769d 100644 --- a/packages/paperclip-runner/src/live/scenario-chat-demo-server.test.ts +++ b/packages/paperclip-runner/src/live/scenario-chat-demo-server.test.ts @@ -136,7 +136,7 @@ describe("Scenario chat deployment boundary", () => { it("requires exact Origin and Fetch Metadata on every mutation", async () => { await withHttpRuntime(async ({ request }) => { const body = JSON.stringify({ scenarioId: SCENARIO_ID }); - const invalidHeaders = [ + const invalidHeaders: Record[] = [ { origin: `${PUBLIC_ORIGIN}/` }, { "sec-fetch-site": "none" }, { "sec-fetch-site": "cross-site" }, diff --git a/packages/paperclip-runner/src/live/turn-stream.test.ts b/packages/paperclip-runner/src/live/turn-stream.test.ts index e91f6e83527..f3547736500 100644 --- a/packages/paperclip-runner/src/live/turn-stream.test.ts +++ b/packages/paperclip-runner/src/live/turn-stream.test.ts @@ -14,6 +14,7 @@ import { type CapabilityLiveTransportFactory, type CapabilityLiveTurnEvent, } from "./live-session.js"; +import type { CapabilityRunnerdProcessEvidence } from "./runnerd-codex-transport.js"; import { createCapabilityTurnStreamDecoder, encodeCapabilityTurnStreamFrame, @@ -161,10 +162,25 @@ class GatedTransport implements CodexAppServerTransport { function gatedTransportFactory(gate: Gate): CapabilityLiveTransportFactory { return (options) => { - const evidence = { + const evidence: CapabilityRunnerdProcessEvidence = { runnerPid: 7100, runnerProcessGroupId: 7100, + providerPid: null, + providerProcessStartedAt: null, codexPid: 7200, + codexProcessStartedAt: null, + sidecarPid: null, + sidecarProcessStartedAt: null, + agentPid: null, + agentProcessStartedAt: null, + providerDriver: null, + providerVersion: null, + acpxAgent: null, + agentServerVersion: null, + agentRuntimeVersion: null, + acpProtocolVersion: null, + providerExecutionKind: null, + providerService: null, runnerExited: false, runnerExitCode: null, runnerSignal: null, @@ -172,7 +188,7 @@ function gatedTransportFactory(gate: Gate): CapabilityLiveTransportFactory { diagnostics: [], }; options.onEvidence?.(evidence); - return { transport: new GatedTransport(gate), evidence: () => ({ ...evidence }) }; + return { transport: new GatedTransport(gate), evidence: () => ({ ...evidence }), detachControllerForRestart: async () => {} }; }; } diff --git a/packages/paperclip-runner/src/mock-core/capability-mock-control-plane-adapter.test.ts b/packages/paperclip-runner/src/mock-core/capability-mock-control-plane-adapter.test.ts index a21225b63c8..80e19a86926 100644 --- a/packages/paperclip-runner/src/mock-core/capability-mock-control-plane-adapter.test.ts +++ b/packages/paperclip-runner/src/mock-core/capability-mock-control-plane-adapter.test.ts @@ -584,6 +584,7 @@ describe("CapabilityMockControlPlaneAdapter", () => { commandKind: "record_budget_usage", effect: "lost_ack", remaining: 1, + code: "budget_ack_lost", }], }); await adapter.start(); diff --git a/packages/paperclip-runner/src/mock-core/live-console-demo-server.test.ts b/packages/paperclip-runner/src/mock-core/live-console-demo-server.test.ts index c370aaf6e1b..56cfb03309a 100644 --- a/packages/paperclip-runner/src/mock-core/live-console-demo-server.test.ts +++ b/packages/paperclip-runner/src/mock-core/live-console-demo-server.test.ts @@ -333,7 +333,7 @@ class StubDriver implements HarnessDriver { const servers: LiveConsoleDemoServer[] = []; -function browserHeaders(url: string, headers: HeadersInit = {}): Headers { +function browserHeaders(url: string, headers: ConstructorParameters[0] = {}): Headers { const result = new Headers(headers); result.set("origin", new URL(url).origin); result.set("sec-fetch-site", "same-origin"); diff --git a/packages/paperclip-runner/src/mock-core/live-console-scripted-driver.test.ts b/packages/paperclip-runner/src/mock-core/live-console-scripted-driver.test.ts index 42010232374..9fa24ed1a2c 100644 --- a/packages/paperclip-runner/src/mock-core/live-console-scripted-driver.test.ts +++ b/packages/paperclip-runner/src/mock-core/live-console-scripted-driver.test.ts @@ -82,6 +82,7 @@ function reduce(events: readonly PrpEvent[]): SessionSnapshot { environmentLeaseId: "package-local", runnerInstanceId: "live-console-demo-server", normalizedSessionId: events[0]?.normalizedSessionId ?? "session", + driverSessionId: "live-console-demo", }, capabilities: { schema: "paperclip.prp.capabilities.v1", diff --git a/packages/paperclip-runner/src/mock-core/local-runner.test.ts b/packages/paperclip-runner/src/mock-core/local-runner.test.ts index 3fe96dcdfe0..d60b3296f60 100644 --- a/packages/paperclip-runner/src/mock-core/local-runner.test.ts +++ b/packages/paperclip-runner/src/mock-core/local-runner.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; +import type { LocalRunnerRunMetadata, LocalRunnerRunTrace } from "../contracts/local-runner.js"; import { applyLocalRunnerLiveEvent, createLocalRunnerLiveSnapshot, @@ -13,6 +14,16 @@ import { startLocalRunnerScenario, } from "./local-runner.js"; +function traceMetadata(trace: LocalRunnerRunTrace): LocalRunnerRunMetadata { + return { + schema: "paperclip.runner.local-runner.metadata.v1", + scenario: trace.scenario, + fixtureName: trace.fixtureName, + identity: trace.identity, + capabilities: trace.capabilities, + }; +} + describe("Local runner and fake harness", () => { it("runs lifecycle, tool, file, structured-result, and process-exit events", async () => { const trace = await runLocalRunnerScenario({ scenario: "happy-path", delayMs: 1 }); @@ -108,7 +119,7 @@ describe("Local runner and fake harness", () => { ), ).toBe(true); expect( - replayLocalRunnerEvents(trace, trace.events).timeline.find( + replayLocalRunnerEvents(traceMetadata(trace), trace.events).timeline.find( (event) => event.eventType === "harness.diagnostic", )?.summary, ).toBe( @@ -160,7 +171,7 @@ describe("Local runner and fake harness", () => { it("produces the same snapshot for validated live events and replay", async () => { const trace = await runLocalRunnerScenario({ scenario: "happy-path", delayMs: 1 }); - let live = createLocalRunnerLiveSnapshot(trace); + let live = createLocalRunnerLiveSnapshot(traceMetadata(trace)); for (const event of trace.events) { const next = applyLocalRunnerLiveEvent(live, event); expect(next.ok).toBe(true); @@ -168,7 +179,7 @@ describe("Local runner and fake harness", () => { live = next.snapshot; } } - const replay = replayLocalRunnerEvents(trace, trace.events); + const replay = replayLocalRunnerEvents(traceMetadata(trace), trace.events); expect(localRunnerSnapshotsMatch(live, replay)).toBe(true); }); diff --git a/packages/paperclip-runner/src/native-session-runtime.test.ts b/packages/paperclip-runner/src/native-session-runtime.test.ts index 2f9fc42456c..de63070094e 100644 --- a/packages/paperclip-runner/src/native-session-runtime.test.ts +++ b/packages/paperclip-runner/src/native-session-runtime.test.ts @@ -33,6 +33,7 @@ import { PAPERCLIP_EXECUTION_PROMPT_REVISION, canonicalNativeRuntimeContextDigest, nativeRuntimePromptDigest, + type NativeRuntimeContextSnapshot, } from "./contracts/runtime-context.js"; import { executeNativeSession, @@ -129,6 +130,7 @@ const input: NativeExecutionInputV1 = { normalizedSessionId: identity.sessionId, driverKind: "codex_app_server", protocolVersion: 1, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, }, provider: { kind: "codex", model: null }, completionContract: { @@ -145,13 +147,15 @@ const input: NativeExecutionInputV1 = { credentialBindings: [], }; +type RuntimeContextFields = Omit; + function preparedInput(): NativeExecutionInputV5 { const digest = "0".repeat(64); const context = { prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() }, instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 } }, skills: [], mcp: { assignmentSetId: "none", digest, bindingId: null }, - } as const; + } satisfies RuntimeContextFields; const requirement = input.completionContract.contract.criteria[0]!.requirement; const prompt = `${input.task.prompt}\n\n${requirement}`; return { @@ -256,7 +260,7 @@ describe("executeNativeSession recovery", () => { const completeRun = vi.fn(async () => undefined); const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { committed.push(structuredClone(event)); return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, + async appendEvent(event: PrpEvent) { committed.push(structuredClone(event)); return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents(query) { const events = committed.filter(event => event.sourceInstanceId === query.sourceInstanceId); return { events: events.filter(event => event.sourceSeq > query.afterSourceSeq).slice(0, query.limit), highestContiguousSourceSeq: highestContiguous(events) }; }, completeRun, }; @@ -318,7 +322,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, async loadSessionCheckpoint() { return recovered ? structuredClone(snapshot) : null; }, - async appendEvent(event) { return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, + async appendEvent(event: PrpEvent) { return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents() { return { events: [], highestContiguousSourceSeq: 0 }; }, async completeRun() {}, }; @@ -390,7 +394,7 @@ describe("executeNativeSession recovery", () => { async openRun() {}, async loadSessionCheckpoint() { return structuredClone(checkpoint); }, async checkpointSession(value) { checkpoints.push(structuredClone(value)); }, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { const existing = journal.find(value => value.sourceEventId === event.sourceEventId); if (existing) expect(event).toEqual(existing); else journal.push(structuredClone(event as PrpEvent)); @@ -479,7 +483,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession(snapshot) { checkpoints.push(structuredClone(snapshot)); }, - async appendEvent(event) { events.push(event); return { cursor: events.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, + async appendEvent(event: PrpEvent) { events.push(event); return { cursor: events.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents() { return { events: [], highestContiguousSourceSeq: 0 }; }, async completeRun() {}, }; @@ -533,7 +537,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, + async appendEvent(event: PrpEvent) { return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents() { return { events: [], highestContiguousSourceSeq: 0 }; }, async completeRun() {}, }; @@ -555,7 +559,7 @@ describe("executeNativeSession recovery", () => { resume: true, typedEvents: true, steering: false, interruption: true, structuredResult: true, }; - const cancel = vi.fn(async () => { finish(); }); + const cancel = vi.fn(() => { finish(); return { cleanup: Promise.resolve() }; }); const close = vi.fn(async () => { finish(); }); const session: NativeSession = { identity: () => identity, @@ -574,7 +578,7 @@ describe("executeNativeSession recovery", () => { }, cancel, close, }; - const appendEvent = vi.fn(async event => ({ + const appendEvent = vi.fn(async (event: PrpEvent) => ({ cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed", })); @@ -728,7 +732,7 @@ describe("executeNativeSession recovery", () => { prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() }, instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 } }, skills: [], mcp: { assignmentSetId: "none", digest, bindingId: null }, - } as const; + } satisfies RuntimeContextFields; const completed = await executeNativeSession({ input: snapshotBeforeUpdate ? { ...input, schema: "paperclip.native-execution-input.v4", executionMode: "default", planningContext: null, @@ -748,7 +752,7 @@ describe("executeNativeSession recovery", () => { controlPlane: { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { appended.push(event); return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, @@ -885,7 +889,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { appended.push(event); return { cursor: event.sourceSeq, @@ -1034,7 +1038,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { appended.push(event); return { cursor: event.sourceSeq, @@ -1185,7 +1189,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, @@ -1241,7 +1245,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents() { @@ -1574,7 +1578,7 @@ describe("executeNativeSession recovery", () => { }, controlPlane: { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { appended.push(event); return { + async appendEvent(event: PrpEvent) { appended.push(event); return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed", }; }, async replayEvents() { return { events: [], highestContiguousSourceSeq: 0 }; }, @@ -2568,12 +2572,12 @@ describe("executeNativeSession recovery", () => { }, skills: [], mcp: { assignmentSetId: "none", digest, bindingId: null }, - } as const; + } satisfies RuntimeContextFields; const openSession = vi.fn(); const backend: NativeSessionBackend = { async descriptor() { return { - kind: "future-provider", + kind: "mock", name: "future-provider", version: "1", capabilities: { @@ -2741,6 +2745,7 @@ describe("executeNativeSession recovery", () => { typedEvents: true, steering: false, interruption: true, + structuredResult: true, }; }, async *events() {}, @@ -2854,7 +2859,7 @@ describe("executeNativeSession recovery", () => { const snapshot = vi.fn(async () => { throw snapshotFailure; }); const session: NativeSession = { identity: () => identity, - async capabilities() { return { resume: false, typedEvents: true, steering: false, interruption: true }; }, + async capabilities() { return { resume: false, typedEvents: true, steering: false, interruption: true, structuredResult: true }; }, async *events() {}, async startTurn() { throw new Error("unexpected turn"); }, async result() { return null; }, @@ -2886,6 +2891,7 @@ describe("executeNativeSession recovery", () => { typedEvents: true, steering: false, interruption: true, + structuredResult: true, }; }, async *events() {}, @@ -3509,7 +3515,7 @@ describe("executeNativeSession recovery", () => { } }, ); - const checkpointSession = vi.fn( + const checkpointSession = vi.fn>( async () => undefined, ); const port: ControlPlanePort = { @@ -3744,8 +3750,9 @@ describe("executeNativeSession recovery", () => { timeoutMs: 10, keepSessionOpen: true, onSession: (current) => retainedSessions.push(current), - onPostCompletionEnrichmentFailure: ({ stage }) => - enrichmentFailures.push(stage), + onPostCompletionEnrichmentFailure: ({ stage }) => { + enrichmentFailures.push(stage); + }, }); await enrichmentStalled; if (stalledSignal !== undefined) @@ -4650,12 +4657,12 @@ describe("executeNativeSession recovery", () => { async *events() { throw new Error("provider failed after editing instructions"); }, startTurn: async () => ({ turnId: "instruction-turn" }), result: async () => null, - snapshot: async () => ({ backendKind: "local", sessionId: "instruction-session", identity: scopedIdentity, + snapshot: async () => ({ backendKind: "runner", sessionId: "instruction-session", identity: scopedIdentity, providerSessionId: "instruction-provider", cursor: null, activeTurnId: null, pendingRuntimeRequests: [], lineage: [] }), close: async () => { order.push("closing"); await closed; order.push("stopped"); }, }; const backend: NativeSessionBackend = { - descriptor: async () => ({ kind: "local", name: "instruction-close-test", version: "1", capabilities }), + descriptor: async () => ({ kind: "runner", name: "instruction-close-test", version: "1", capabilities }), openSession: async () => session, }; const controlPlane: ControlPlanePort = { @@ -4747,7 +4754,7 @@ describe("executeNativeSession recovery", () => { close: vi.fn(async () => { throw failure; }), }; const backend: NativeSessionBackend = { - descriptor: async () => ({ kind: "local", name: "local-stop-test", version: "1", capabilities }), + descriptor: async () => ({ kind: "runner", name: "local-stop-test", version: "1", capabilities }), openSession: vi.fn(async () => session), }; const controlPlane: ControlPlanePort = { @@ -4982,7 +4989,7 @@ describe("executeNativeSession recovery", () => { runnerInstanceId: "runner-recovery", controlPlaneInstanceId: "control-recovery", timeoutMs: 5_000, controlPlane: { async openRun() {}, async checkpointSession() {}, completeRun, - async appendEvent(event, options) { + async appendEvent(event: PrpEvent, options) { if (options?.deferPostCommitObservers) deferredEventTypes.push(event.eventType); events.push(structuredClone(event)); return { cursor: events.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed", @@ -5080,7 +5087,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { events.push(structuredClone(event as PrpEvent)); return { cursor: events.length, @@ -5199,7 +5206,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { events.push(structuredClone(event as PrpEvent)); const sourceEvents = events.filter( (candidate) => candidate.sourceInstanceId === event.sourceInstanceId, @@ -5417,7 +5424,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { if (event.eventType === "item.completed") { persistingAnswer = true; await persistence; @@ -5925,7 +5932,7 @@ describe("executeNativeSession recovery", () => { const openReplacementSession = vi.fn(); const backend: NativeSessionBackend = { descriptor: async () => ({ kind: "mock", name: "incompatible", version: "1", - capabilities: { resume: true, typedEvents: true, steering: false, interruption: true } }), + capabilities: { resume: true, typedEvents: true, steering: false, interruption: true, structuredResult: true } }), openSession, openReplacementSession, recoverSession: async () => ({ recovered: false, reason: "saved launch profile changed", failureClass: PROVIDER_STATE_INCOMPATIBLE }), @@ -6093,6 +6100,7 @@ describe("executeNativeSession recovery", () => { typedEvents: true, steering: false, interruption: true, + structuredResult: true, }; }, async *events() {}, @@ -6136,6 +6144,7 @@ describe("executeNativeSession recovery", () => { typedEvents: true, steering: false, interruption: true, + structuredResult: true, }, }; }, @@ -6224,6 +6233,7 @@ describe("executeNativeSession recovery", () => { typedEvents: true, steering: false, interruption: true, + structuredResult: true, }; }, async *events() {}, @@ -6268,6 +6278,7 @@ describe("executeNativeSession recovery", () => { typedEvents: true, steering: false, interruption: true, + structuredResult: true, }, }; }, @@ -6375,7 +6386,7 @@ describe("executeNativeSession recovery", () => { }, async replayEvents(replay) { return { - events: replay.afterSourceSeq === 0 ? [runnerEvent(1)] : [], + events: replay.afterSourceSeq === 0 ? [runnerEvent(1, "turn.started")] : [], highestContiguousSourceSeq: 1, }; }, @@ -6479,7 +6490,7 @@ describe("executeNativeSession recovery", () => { }, async replayEvents(replay) { return { - events: replay.afterSourceSeq === 0 ? [runnerEvent(1)] : [], + events: replay.afterSourceSeq === 0 ? [runnerEvent(1, "turn.started")] : [], highestContiguousSourceSeq: 1, }; }, @@ -6530,7 +6541,7 @@ describe("executeNativeSession recovery", () => { sourceEventId: "provider-recovery:1", sourceSeq: 1, sourceInstanceId: "provider-recovery", - sourceKind: "provider", + sourceKind: "runner", runId: identity.runId, normalizedSessionId: identity.sessionId, turnId: "turn-recovery", @@ -6594,7 +6605,7 @@ describe("executeNativeSession recovery", () => { return structuredClone(checkpoint); }, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { const list = bySource.get(event.sourceInstanceId) ?? []; list.push(structuredClone(event)); bySource.set(event.sourceInstanceId, list); @@ -6718,7 +6729,7 @@ describe("executeNativeSession recovery", () => { async checkpointSession(snapshot) { checkpoints.push(structuredClone(snapshot)); }, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { const target = event.sourceInstanceId === "runner-recovery" ? runnerEvents @@ -6852,7 +6863,7 @@ describe("executeNativeSession recovery", () => { return structuredClone(checkpoint); }, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { events.push(structuredClone(event)); return { cursor: events.length, @@ -6989,8 +7000,8 @@ describe("executeNativeSession recovery", () => { instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 } }, skills: [skill], mcp: { assignmentSetId: "none", digest, bindingId: null }, - } as const; - const legacyContext = { ...context, skills: [] as const }; + } satisfies RuntimeContextFields; + const legacyContext = { ...context, skills: [] } satisfies RuntimeContextFields; const prepared = preparedInput(); const executionInput = preparedMode ? { @@ -7049,7 +7060,7 @@ describe("executeNativeSession recovery", () => { terminal: null, terminalTurns: [], }; - const startTurn = vi.fn(async () => ({ turnId: "turn-replacement" })); + const startTurn = vi.fn(async () => ({ turnId: "turn-replacement" })); const replacementSession: NativeSession = { identity: () => identity, async capabilities() { @@ -7108,7 +7119,7 @@ describe("executeNativeSession recovery", () => { return structuredClone(checkpoint); }, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { events.push(structuredClone(event)); return { cursor: events.length, @@ -7194,7 +7205,7 @@ describe("executeNativeSession recovery", () => { sourceEventId: "provider-recovery:2", sourceSeq: 2, sourceInstanceId: "provider-recovery", - sourceKind: "provider", + sourceKind: "runner", runId: identity.runId, normalizedSessionId: identity.sessionId, turnId: "turn-continuation", @@ -7204,7 +7215,7 @@ describe("executeNativeSession recovery", () => { emittedAt: "2026-08-09T00:00:01.000Z", payload: {}, }; - const startTurn = vi.fn(async () => ({ turnId: "turn-continuation" })); + const startTurn = vi.fn(async () => ({ turnId: "turn-continuation" })); const session: NativeSession = { identity: () => identity, async capabilities() { @@ -7273,7 +7284,7 @@ describe("executeNativeSession recovery", () => { return structuredClone(checkpoint); }, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { const list = bySource.get(event.sourceInstanceId) ?? []; list.push(structuredClone(event)); bySource.set(event.sourceInstanceId, list); @@ -7490,7 +7501,7 @@ describe("executeNativeSession recovery", () => { sourceEventId: "provider-recovery:2", sourceSeq: 2, sourceInstanceId: "provider-recovery", - sourceKind: "provider", + sourceKind: "runner", runId: identity.runId, normalizedSessionId: identity.sessionId, turnId: "turn-disposition", @@ -7571,7 +7582,7 @@ describe("executeNativeSession recovery", () => { prematureDispositionCheckpoint = true; durableCheckpoint = structuredClone(snapshot); }, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { if (event.eventType === terminalType && failNextAppend) { failNextAppend = false; throw appendFailure; @@ -7674,7 +7685,7 @@ describe("executeNativeSession recovery", () => { sourceEventId: "runner-recovery:run-native:4", sourceSeq: 4, sourceInstanceId: "runner-recovery", - sourceKind: "provider", + sourceKind: "runner", runId: identity.runId, normalizedSessionId: identity.sessionId, turnId: "turn-disposition", @@ -7784,7 +7795,7 @@ describe("executeNativeSession recovery", () => { return structuredClone(checkpoint); }, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { const list = bySource.get(event.sourceInstanceId) ?? []; list.push(structuredClone(event)); bySource.set(event.sourceInstanceId, list); @@ -7935,7 +7946,7 @@ describe("executeNativeSession recovery", () => { return structuredClone(checkpoint); }, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { const list = bySource.get(event.sourceInstanceId) ?? []; list.push(structuredClone(event)); bySource.set(event.sourceInstanceId, list); @@ -8085,7 +8096,7 @@ describe("executeNativeSession recovery", () => { pendingRuntimeRequests: [], lineage: [], }; - const events = [ + const events: PrpEvent[] = [ { ...controlEvent(1, "run.result.accepted", { result }), turnId: "turn-with-result", @@ -8223,7 +8234,7 @@ describe("executeNativeSession recovery", () => { backend: { async descriptor() { return { kind: "mock", name: "missing-current-completion", version: "1", capabilities }; }, async openSession() { return session; } }, controlPlane: { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { events.push(structuredClone(event as PrpEvent)); return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, + async appendEvent(event: PrpEvent) { events.push(structuredClone(event as PrpEvent)); return { cursor: event.sourceSeq, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents() { return { events: [], highestContiguousSourceSeq: 0 }; }, completeRun, }, runnerInstanceId: "runner-recovery", controlPlaneInstanceId: "control-recovery", timeoutMs: 1000, resolveMissingResult: async () => null, @@ -8239,7 +8250,7 @@ describe("executeNativeSession recovery", () => { sourceEventId: "provider-recovery:1", sourceSeq: 1, sourceInstanceId: "provider-recovery", - sourceKind: "provider", + sourceKind: "runner", runId: identity.runId, normalizedSessionId: identity.sessionId, turnId: "turn-waiting", @@ -8420,7 +8431,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, completeRun: complete, async checkpointSession(value) { checkpoint = structuredClone(value); }, - async appendEvent(event) { history.push(event); return { cursor: history.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, + async appendEvent(event: PrpEvent) { history.push(event); return { cursor: history.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents(request) { return { events: history.filter(row => row.sourceInstanceId === request.sourceInstanceId && row.sourceSeq > request.afterSourceSeq), highestContiguousSourceSeq: 0 }; }, }; const outcome = expect(executeNativeSession({ input, backend, controlPlane: port, timeoutMs: 1_000, @@ -8475,7 +8486,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, completeRun: complete, async checkpointSession(value) { checkpoint = structuredClone(value); }, - async appendEvent(event) { history.push(event); return { cursor: history.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, + async appendEvent(event: PrpEvent) { history.push(event); return { cursor: history.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, async replayEvents(request) { return { events: history.filter(row => row.sourceInstanceId === request.sourceInstanceId && row.sourceSeq > request.afterSourceSeq), highestContiguousSourceSeq: 0 }; }, }; const execution = executeNativeSession({ input, backend, controlPlane: port, timeoutMs: operationMs ?? 120_000, @@ -8523,7 +8534,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, completeRun: complete, async checkpointSession(value) { checkpoint = structuredClone(value); }, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { const duplicate = history.some(row => row.sourceEventId === event.sourceEventId); if (!duplicate) history.push(event); return { cursor: history.length, highestContiguousSourceSeq: event.sourceSeq, disposition: duplicate ? "duplicate" : "committed" }; @@ -8585,7 +8596,7 @@ describe("executeNativeSession recovery", () => { throw new Error("controller lost after governed checkpoint"); } }, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { if (!history.some(row => row.sourceEventId === event.sourceEventId)) history.push(event); return { cursor: history.length, highestContiguousSourceSeq: event.sourceSeq, disposition: "committed" }; }, @@ -8673,14 +8684,14 @@ describe("executeNativeSession recovery", () => { }), sourceEventId: "provider-recovery:1", sourceInstanceId: "provider-recovery", - sourceKind: "provider", + sourceKind: "runner", turnId: "turn-waiting", }; const turnInterrupted: PrpEvent = { ...controlEvent(3, "turn.interrupted", { reason: "governed_wait" }), sourceEventId: "provider-recovery:3", sourceInstanceId: "provider-recovery", - sourceKind: "provider", + sourceKind: "runner", turnId: "turn-waiting", }; let releaseCancelled!: () => void; @@ -8945,7 +8956,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { events.push(structuredClone(event as PrpEvent)); if (event.eventType === "runtime_request.created") releaseCreated(); const sourceEvents = events.filter( @@ -9491,7 +9502,7 @@ describe("executeNativeSession recovery", () => { const port: ControlPlanePort = { async openRun() {}, async checkpointSession() {}, - async appendEvent(event) { + async appendEvent(event: PrpEvent) { if (event.eventType === "runtime_request.resolved") { markSettlementAppendStarted(); await settlementAppendReleased; @@ -9521,11 +9532,11 @@ describe("executeNativeSession recovery", () => { }; const originalSetTimeout = globalThis.setTimeout; - let queuedHandoffCallback: (() => void) | null = null; + let queuedHandoffCallback = null as (() => void) | null; const timeoutSpy = vi.spyOn(globalThis, "setTimeout").mockImplementation((( - callback, - delay, - ...args + callback: (...args: unknown[]) => void, + delay?: number, + ...args: unknown[] ) => { if (delay === 123_456) { queuedHandoffCallback = () => callback(...args); @@ -9576,7 +9587,7 @@ it("fails preconfigured recovery without replacing a missing provider session", const backend: NativeSessionBackend = { descriptor: async () => ({ kind: "mock", name: "preconfigured", version: "1", runtimeContextCapabilities: { instructions: "native", skills: "native", mcp: "native" }, - capabilities: { resume: true, typedEvents: true, steering: false, interruption: true } }), + capabilities: { resume: true, typedEvents: true, steering: false, interruption: true, structuredResult: true } }), openSession, openReplacementSession, recoverSession: async () => ({ recovered: false, reason: "provider history is missing" }), }; @@ -9586,7 +9597,7 @@ it("fails preconfigured recovery without replacing a missing provider session", }; await expect(executeNativeSession({ input: { ...preparedInput(), schema: "paperclip.native-execution-input.v7", homeMode: "preconfigured" }, - backend, controlPlane: port, runnerInstanceId: "runner-preconfigured", persistedSession: checkpoint, + backend, controlPlane: port, runnerInstanceId: "runner-preconfigured", controlPlaneInstanceId: "control-preconfigured", persistedSession: checkpoint, })).rejects.toThrow("native_session_recovery_failed: provider history is missing"); expect(openSession).not.toHaveBeenCalled(); expect(openReplacementSession).not.toHaveBeenCalled(); diff --git a/packages/paperclip-runner/src/protocol-actions/case-data.test.ts b/packages/paperclip-runner/src/protocol-actions/case-data.test.ts index ea569d9472d..d2459f92eac 100644 --- a/packages/paperclip-runner/src/protocol-actions/case-data.test.ts +++ b/packages/paperclip-runner/src/protocol-actions/case-data.test.ts @@ -1,4 +1,4 @@ -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { paperclipSemanticAction } from "../catalog/semantic-action-catalog.js"; import { paperclipSemanticActionNamespace } from "../semantic-tools/paperclip-discovery.js"; diff --git a/packages/paperclip-runner/src/protocol-actions/collaboration-contracts.test.ts b/packages/paperclip-runner/src/protocol-actions/collaboration-contracts.test.ts index 0458475389d..03758fe075b 100644 --- a/packages/paperclip-runner/src/protocol-actions/collaboration-contracts.test.ts +++ b/packages/paperclip-runner/src/protocol-actions/collaboration-contracts.test.ts @@ -1,4 +1,4 @@ -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { PAPERCLIP_COLLABORATION_PROTOCOL_ACTIONS } from "./collaboration.js"; diff --git a/packages/paperclip-runner/src/protocol-actions/core-contracts.test.ts b/packages/paperclip-runner/src/protocol-actions/core-contracts.test.ts index 8a76a6f2659..fe06c2f98d5 100644 --- a/packages/paperclip-runner/src/protocol-actions/core-contracts.test.ts +++ b/packages/paperclip-runner/src/protocol-actions/core-contracts.test.ts @@ -1,4 +1,4 @@ -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { PAPERCLIP_CORE_PROTOCOL_ACTIONS } from "./core.js"; diff --git a/packages/paperclip-runner/src/protocol/permission-request.test.ts b/packages/paperclip-runner/src/protocol/permission-request.test.ts index 2f5a18d2fa3..f0ac44e6fa4 100644 --- a/packages/paperclip-runner/src/protocol/permission-request.test.ts +++ b/packages/paperclip-runner/src/protocol/permission-request.test.ts @@ -1,4 +1,4 @@ -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { questionSetSchema, requestSchema } from "./generated/schema-bundle.js"; diff --git a/packages/paperclip-runner/src/protocol/provider-descriptor.test.ts b/packages/paperclip-runner/src/protocol/provider-descriptor.test.ts index 891fb68a5f5..d6d1f3d5b6e 100644 --- a/packages/paperclip-runner/src/protocol/provider-descriptor.test.ts +++ b/packages/paperclip-runner/src/protocol/provider-descriptor.test.ts @@ -1,4 +1,4 @@ -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; import { providerDescriptorSchema } from "./generated/schema-bundle.js"; @@ -19,7 +19,7 @@ describe("provider runtime descriptor", () => { } const event: PrpEvent = { schema: "paperclip.prp.event.v2", schemaVersion: 2, sourceEventId: "controls", sourceSeq: 1, - sourceInstanceId: "runner", sourceKind: "runner", runId: "run", eventType: "session.capabilities.updated", + sourceInstanceId: "runner", sourceKind: "runner", runId: "run", normalizedSessionId: "session", 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, diff --git a/packages/paperclip-runner/src/protocol/prp-v1-expressiveness-crosswalk.test.ts b/packages/paperclip-runner/src/protocol/prp-v1-expressiveness-crosswalk.test.ts index 2909598d388..e190ea1fa3e 100644 --- a/packages/paperclip-runner/src/protocol/prp-v1-expressiveness-crosswalk.test.ts +++ b/packages/paperclip-runner/src/protocol/prp-v1-expressiveness-crosswalk.test.ts @@ -1,7 +1,7 @@ import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, resolve } from "node:path"; -import Ajv2020 from "ajv/dist/2020"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { describe, expect, it } from "vitest"; type Classification = diff --git a/packages/paperclip-runner/src/protocol/replay-contract.test.ts b/packages/paperclip-runner/src/protocol/replay-contract.test.ts index bad4d2536ee..2b98ffe4acb 100644 --- a/packages/paperclip-runner/src/protocol/replay-contract.test.ts +++ b/packages/paperclip-runner/src/protocol/replay-contract.test.ts @@ -1,4 +1,4 @@ -import Ajv2020 from "ajv/dist/2020.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { usageSchema } from "./generated/schema-bundle.js"; import { readFile } from "node:fs/promises"; @@ -77,7 +77,7 @@ describe("PRP v1 JSON Schema contract", () => { expect(result.fixture.futureFixtureHint).toEqual({ producerVersion: "1.1-preview", }); - expect(result.fixture.events[0]?.futureEnvelopeField).toBe(42); + expect((result.fixture.events[0] as unknown as Record).futureEnvelopeField).toBe(42); } }); @@ -269,7 +269,7 @@ describe("PRP v1 JSON Schema contract", () => { expect(result.ok).toBe(true); if (!result.ok) return; expect( - result.fixture.events[0]?.payload.semantic_tool?.futureEnvelopeHint, + (result.fixture.events[0]?.payload.semantic_tool as { futureEnvelopeHint?: unknown } | undefined)?.futureEnvelopeHint, ).toEqual({ version: "1.1-preview" }); const withoutEnvelope = structuredClone(result.fixture); diff --git a/packages/paperclip-runner/src/protocol/workspace-diff-events.test.ts b/packages/paperclip-runner/src/protocol/workspace-diff-events.test.ts index 9a25aec4bb3..a6fcdd91d81 100644 --- a/packages/paperclip-runner/src/protocol/workspace-diff-events.test.ts +++ b/packages/paperclip-runner/src/protocol/workspace-diff-events.test.ts @@ -10,6 +10,7 @@ function workspaceEvent(eventType: "workspace.change.updated" | "workspace.diff. sourceInstanceId: "runner-1", sourceKind: "runner", runId: "run-1", + normalizedSessionId: "session-1", turnId: "turn-1", eventType, schemaVersion: 1, @@ -44,7 +45,7 @@ describe("PRP workspace change events", () => { it("rejects traversal paths and incomplete recorded diffs", () => { const traversal = workspaceEvent("workspace.diff.recorded"); - traversal.payload.files[0]!.path = "../secret"; + (traversal.payload.files as Array<{ path: string }>)[0]!.path = "../secret"; expect(validatePrpEvent(traversal).ok).toBe(false); const incomplete = workspaceEvent("workspace.diff.recorded"); incomplete.payload.complete = false; @@ -59,6 +60,7 @@ describe("PRP workspace change events", () => { sourceInstanceId: "runner-1", sourceKind: "runner", runId: "run-1", + normalizedSessionId: "session-1", turnId: "turn-1", eventType: "workspace.file.referenced", schemaVersion: 1, diff --git a/packages/paperclip-runner/src/react/public-contract.test.tsx b/packages/paperclip-runner/src/react/public-contract.test.tsx index cae0aa98f6a..3f60ddadd1c 100644 --- a/packages/paperclip-runner/src/react/public-contract.test.tsx +++ b/packages/paperclip-runner/src/react/public-contract.test.tsx @@ -3,7 +3,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it } from "vitest"; import type { SessionItemSnapshot } from "../reducer/session-reducer.js"; -import type { TranscriptEntry } from "../browser/transcript-model.js"; +import type { TranscriptEntry, TranscriptItemEntry } from "../browser/transcript-model.js"; import { Composer } from "./components/composer.js"; import { Conversation } from "./components/conversation.js"; import { parseStructuredResponse } from "./components/message.js"; @@ -17,7 +17,7 @@ const item: SessionItemSnapshot = { text: "Canonical answer", }; -const entries: TranscriptEntry[] = [ +const entries: TranscriptItemEntry[] = [ { kind: "item", key: "item:assistant-1", diff --git a/packages/paperclip-runner/src/tools/capability-semantic-tools.test.ts b/packages/paperclip-runner/src/tools/capability-semantic-tools.test.ts index 5a4685a670f..9d4b010b046 100644 --- a/packages/paperclip-runner/src/tools/capability-semantic-tools.test.ts +++ b/packages/paperclip-runner/src/tools/capability-semantic-tools.test.ts @@ -93,6 +93,11 @@ async function runtimeFor(options: { }; } +function pendingLeaseExpiry(snapshot: CapabilitySemanticToolRuntimeSnapshot | null): number | undefined { + const extension = snapshot?.extensions[0]; + return extension?.status === "pending" ? extension.leaseExpiresAtMs : undefined; +} + describe("Capability semantic tool catalog", () => { it("defines a unique versioned provider-neutral catalog and every optional group", () => { const operationIds = CAPABILITY_SEMANTIC_TOOL_CATALOG.map((tool) => tool.operationId); @@ -441,7 +446,7 @@ describe("Capability exposure and authorization", () => { }); it("retries the completion merge after a concurrent lease heartbeat", async () => { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; let injectedHeartbeats = 0; let completionAttempts = 0; const durableStore: CapabilitySemanticToolRuntimeStore = { @@ -494,7 +499,7 @@ describe("Capability exposure and authorization", () => { it("bounds completion contention and retries the fulfilled execution without rerunning it", async () => { vi.useFakeTimers({ now: 0 }); try { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; let rejectCompletion = true; let completionAttempts = 0; const durableStore: CapabilitySemanticToolRuntimeStore = { @@ -574,7 +579,7 @@ describe("Capability exposure and authorization", () => { vi.useFakeTimers({ now: 0 }); try { let now = 0; - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; let rejectCompletion = true; const durableStore: CapabilitySemanticToolRuntimeStore = { load: () => durableSnapshot === null ? null : structuredClone(durableSnapshot), @@ -704,7 +709,7 @@ describe("Capability exposure and authorization", () => { it("retains a failed lease until cleanup contention clears", async () => { vi.useFakeTimers({ now: 0 }); try { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; let failExecutionAllocation = true; let cleanupAttempts = 0; const durableStore: CapabilitySemanticToolRuntimeStore = { @@ -764,7 +769,7 @@ describe("Capability exposure and authorization", () => { }); it("allocates read result ids from the latest shared durable sequence", async () => { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; const durableStore: CapabilitySemanticToolRuntimeStore = { load: () => durableSnapshot === null ? null : structuredClone(durableSnapshot), save: (_runId, snapshot) => { @@ -800,7 +805,7 @@ describe("Capability exposure and authorization", () => { }); it("retries extension acquisition after an unrelated snapshot update", async () => { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; let injectedContention = false; const durableStore: CapabilitySemanticToolRuntimeStore = { load: () => durableSnapshot === null ? null : structuredClone(durableSnapshot), @@ -903,7 +908,7 @@ describe("Capability exposure and authorization", () => { }); it("does not revive a serialized pending extension when the durable store is empty", async () => { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; const durableStore: CapabilitySemanticToolRuntimeStore = { load: () => durableSnapshot === null ? null : structuredClone(durableSnapshot), save: (_runId, snapshot) => { @@ -928,7 +933,7 @@ describe("Capability exposure and authorization", () => { const inFlight = first.runtime.invoke(invocation); const serializedWhilePending = first.adapter.serialize(); await expect(inFlight).resolves.toMatchObject({ ok: true }); - durableSnapshot = null; + durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; const restoredAdapter = CapabilityMockControlPlaneAdapter.restore( serializedWhilePending, @@ -952,7 +957,7 @@ describe("Capability exposure and authorization", () => { }); it("does not revive serialized completed extensions when the durable store is empty", async () => { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; const durableStore: CapabilitySemanticToolRuntimeStore = { load: () => durableSnapshot === null ? null : structuredClone(durableSnapshot), save: (_runId, snapshot) => { @@ -974,7 +979,7 @@ describe("Capability exposure and authorization", () => { idempotencyKey: "stale-completed", })).resolves.toMatchObject({ ok: true }); const serializedWithCompleted = first.adapter.serialize(); - durableSnapshot = null; + durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; const restoredAdapter = CapabilityMockControlPlaneAdapter.restore( serializedWithCompleted, @@ -1004,7 +1009,7 @@ describe("Capability exposure and authorization", () => { it("renews a live extension lease before another runtime can reclaim it", async () => { vi.useFakeTimers({ now: 0 }); try { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; const durableStore: CapabilitySemanticToolRuntimeStore = { load: () => durableSnapshot === null ? null : structuredClone(durableSnapshot), @@ -1066,7 +1071,7 @@ describe("Capability exposure and authorization", () => { it("retries a lease heartbeat after a transient durable-store failure", async () => { vi.useFakeTimers({ now: 0 }); try { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; let failNextLoad = false; const durableStore: CapabilitySemanticToolRuntimeStore = { load: () => { @@ -1099,9 +1104,9 @@ describe("Capability exposure and authorization", () => { failNextLoad = true; expect(() => vi.advanceTimersByTime(10_000)).not.toThrow(); - expect(durableSnapshot?.extensions[0]?.leaseExpiresAtMs).toBe(30_000); + expect(pendingLeaseExpiry(durableSnapshot)).toBe(30_000); vi.advanceTimersByTime(10_000); - expect(durableSnapshot?.extensions[0]?.leaseExpiresAtMs).toBe(50_000); + expect(pendingLeaseExpiry(durableSnapshot)).toBe(50_000); await expect(inFlight).resolves.toMatchObject({ ok: true }); } finally { vi.useRealTimers(); @@ -1111,7 +1116,7 @@ describe("Capability exposure and authorization", () => { it("recovers a prepared extension after repeated heartbeat failures", async () => { vi.useFakeTimers({ now: 0 }); try { - let durableSnapshot: CapabilitySemanticToolRuntimeSnapshot | null = null; + let durableSnapshot = null as CapabilitySemanticToolRuntimeSnapshot | null; let failHeartbeatLoads = false; const durableStore: CapabilitySemanticToolRuntimeStore = { load: () => { diff --git a/packages/paperclip-runner/test/support/runnerd-transport.ts b/packages/paperclip-runner/test/support/runnerd-transport.ts index 171414f2415..4fb1359f567 100644 --- a/packages/paperclip-runner/test/support/runnerd-transport.ts +++ b/packages/paperclip-runner/test/support/runnerd-transport.ts @@ -8,6 +8,7 @@ import { nativeRuntimePromptDigest, type NativeRuntimeContextSnapshot, } from "../../src/contracts/runtime-context.js"; +import type { DurablePrpControlPlane, DurablePrpControlPlaneStore } from "../../src/control-plane/durable-prp-control-plane.js"; import { defaultCapabilityRunnerdBinary as qualifiedCapabilityRunnerdBinary } from "../../src/live/runnerd-codex-transport.js"; export const defaultCapabilityRunnerdBinary = () => @@ -86,3 +87,11 @@ export function assignedRuntimeContext( aggregateDigest: canonicalNativeRuntimeContextDigest(value), }; } + +export type CommittableStore = DurablePrpControlPlaneStore & { + commit(candidate: DurablePrpControlPlaneStore["state"], checkpointRequired?: boolean): void; +}; + +export function committableStore(authority: DurablePrpControlPlane): CommittableStore { + return authority.store as CommittableStore; +} diff --git a/packages/paperclip-runner/tsconfig.browser.json b/packages/paperclip-runner/tsconfig.browser.json index 624c92fc56c..f97c6806310 100644 --- a/packages/paperclip-runner/tsconfig.browser.json +++ b/packages/paperclip-runner/tsconfig.browser.json @@ -14,22 +14,21 @@ "vite/client", "node" ], - "baseUrl": ".", "paths": { "@paperclipai/paperclip-runner/browser": [ - "src/browser/index.ts" + "./src/browser/index.ts" ], "@paperclipai/paperclip-runner/react": [ - "src/react/index.ts" + "./src/react/index.ts" ], "@paperclipai/paperclip-runner/standalone": [ - "src/standalone/index.ts" + "./src/standalone/index.ts" ], "@paperclipai/paperclip-runner/styles.css": [ - "styles.css" + "./styles.css" ], "@paperclip-runner-local/capability": [ - "src/scenarios/index.ts" + "./src/scenarios/index.ts" ] } }, diff --git a/packages/paperclip-runner/tsconfig.browser.test.json b/packages/paperclip-runner/tsconfig.browser.test.json new file mode 100644 index 00000000000..4d74f669f27 --- /dev/null +++ b/packages/paperclip-runner/tsconfig.browser.test.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.browser.json", + "compilerOptions": { + "noEmit": true + }, + "include": [ + "devtools/browser/**/*.ts", + "devtools/browser/**/*.tsx", + "devtools/issue-thread/**/*.ts", + "devtools/issue-thread/**/*.tsx", + "examples/**/*.ts", + "examples/**/*.tsx" + ], + "exclude": ["**/dist/**", "**/vite.config.ts"] +} diff --git a/packages/paperclip-runner/tsconfig.surfaces.test.json b/packages/paperclip-runner/tsconfig.surfaces.test.json new file mode 100644 index 00000000000..b6b27ea0e3a --- /dev/null +++ b/packages/paperclip-runner/tsconfig.surfaces.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.surfaces.json", + "compilerOptions": { + "allowJs": true, + "noEmit": true, + "rootDir": "." + }, + "exclude": [] +} diff --git a/packages/paperclip-runner/tsconfig.test.json b/packages/paperclip-runner/tsconfig.test.json new file mode 100644 index 00000000000..1b05e128951 --- /dev/null +++ b/packages/paperclip-runner/tsconfig.test.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowJs": true, + "noEmit": true, + "rootDir": ".." + }, + "include": ["src/**/*.ts", "test/**/*.ts"], + "exclude": [ + "src/browser/**", + "src/devtools/**", + "src/issue-thread/**", + "src/react/**", + "src/scenarios/**", + "src/standalone/**" + ] +} diff --git a/packages/plugins/examples/plugin-orchestration-smoke-example/tests/plugin.spec.ts b/packages/plugins/examples/plugin-orchestration-smoke-example/tests/plugin.spec.ts index dd879eeef07..a583fb4080a 100644 --- a/packages/plugins/examples/plugin-orchestration-smoke-example/tests/plugin.spec.ts +++ b/packages/plugins/examples/plugin-orchestration-smoke-example/tests/plugin.spec.ts @@ -20,8 +20,11 @@ function issue(input: Partial & Pick title, description: null, status: "todo", + workMode: "standard", priority: "medium", + reviewPolicy: null, assigneeAgentId: null, + responsibleUserId: null, assigneeUserId: null, checkoutRunId: null, executionRunId: null, diff --git a/packages/plugins/examples/plugin-orchestration-smoke-example/tsconfig.test.json b/packages/plugins/examples/plugin-orchestration-smoke-example/tsconfig.test.json new file mode 100644 index 00000000000..f20d34cbaee --- /dev/null +++ b/packages/plugins/examples/plugin-orchestration-smoke-example/tsconfig.test.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "paths": { + "@paperclipai/shared": ["../../../shared/dist/index.d.ts"] + }, + "types": ["node"], + "typeRoots": ["../../../../server/node_modules/@types"] + }, + "include": ["tests"] +} diff --git a/packages/plugins/sandbox-providers/boxd/tsconfig.test.json b/packages/plugins/sandbox-providers/boxd/tsconfig.test.json new file mode 100644 index 00000000000..90040f25e53 --- /dev/null +++ b/packages/plugins/sandbox-providers/boxd/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/packages/plugins/sandbox-providers/cloudflare/bridge-template/tsconfig.test.json b/packages/plugins/sandbox-providers/cloudflare/bridge-template/tsconfig.test.json new file mode 100644 index 00000000000..a8d4317b49c --- /dev/null +++ b/packages/plugins/sandbox-providers/cloudflare/bridge-template/tsconfig.test.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": [] +} diff --git a/packages/plugins/sandbox-providers/cloudflare/tsconfig.test.json b/packages/plugins/sandbox-providers/cloudflare/tsconfig.test.json new file mode 100644 index 00000000000..90040f25e53 --- /dev/null +++ b/packages/plugins/sandbox-providers/cloudflare/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/packages/plugins/sandbox-providers/createos/src/file-sync.test.ts b/packages/plugins/sandbox-providers/createos/src/file-sync.test.ts index 4ac98c98907..259734a1f80 100644 --- a/packages/plugins/sandbox-providers/createos/src/file-sync.test.ts +++ b/packages/plugins/sandbox-providers/createos/src/file-sync.test.ts @@ -2,7 +2,7 @@ import path from "node:path"; import os from "node:os"; import { promises as fs } from "node:fs"; import * as tar from "tar"; -import { afterEach, beforeEach, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, expect, it, vi, type Mock } from "vitest"; import { CreateosClient } from "./client.js"; import { parseConfig } from "./config.js"; import { assertRemotePath, syncFiles, validateArchive } from "./file-sync.js"; @@ -16,7 +16,7 @@ const params = { driverKey: "createos", companyId: "c", environmentId: "e", conf let temp: string; let downloads: Buffer; let uploads: Buffer[]; -let fetchMock: ReturnType; +let fetchMock: Mock<(url: string, init?: RequestInit) => Promise>; beforeEach(async () => { temp = await fs.mkdtemp(path.join(os.tmpdir(), "createos-sync-test-")); diff --git a/packages/plugins/sandbox-providers/createos/tsconfig.test.json b/packages/plugins/sandbox-providers/createos/tsconfig.test.json new file mode 100644 index 00000000000..90040f25e53 --- /dev/null +++ b/packages/plugins/sandbox-providers/createos/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/packages/plugins/sandbox-providers/daytona/tsconfig.test.json b/packages/plugins/sandbox-providers/daytona/tsconfig.test.json new file mode 100644 index 00000000000..90040f25e53 --- /dev/null +++ b/packages/plugins/sandbox-providers/daytona/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/packages/plugins/sandbox-providers/e2b/src/plugin.test.ts b/packages/plugins/sandbox-providers/e2b/src/plugin.test.ts index 851807e98b1..e74c4b92beb 100644 --- a/packages/plugins/sandbox-providers/e2b/src/plugin.test.ts +++ b/packages/plugins/sandbox-providers/e2b/src/plugin.test.ts @@ -308,7 +308,7 @@ describe("E2B sandbox provider plugin", () => { expect(mockConnect).toHaveBeenCalledWith("sandbox-123", expect.objectContaining({ apiKey: "resolved-key" })); expect(sandbox.files.write).toHaveBeenCalledWith(expect.stringMatching(/^\/tmp\/paperclip-stdin-/), "input"); - const stdinCall = sandbox.commands.run.mock.calls.find(([cmd]: [string]) => cmd.includes("'printf'")); + const stdinCall = sandbox.commands.run.mock.calls.find(([cmd]) => cmd.includes("'printf'")); expect(stdinCall).toBeDefined(); if (!stdinCall) throw new Error("stdinCall not found"); expect(stdinCall[0]).toMatch(/\.profile/); @@ -365,7 +365,7 @@ describe("E2B sandbox provider plugin", () => { timeoutMs: 1000, }); - const fgCall = sandbox.commands.run.mock.calls.find(([cmd]: [string]) => cmd.includes("'printf'")); + const fgCall = sandbox.commands.run.mock.calls.find(([cmd]) => cmd.includes("'printf'")); expect(fgCall).toBeDefined(); if (!fgCall) throw new Error("fgCall not found"); expect(fgCall[0]).toMatch(/\.profile/); diff --git a/packages/plugins/sandbox-providers/e2b/tsconfig.test.json b/packages/plugins/sandbox-providers/e2b/tsconfig.test.json new file mode 100644 index 00000000000..90040f25e53 --- /dev/null +++ b/packages/plugins/sandbox-providers/e2b/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/packages/plugins/sandbox-providers/exe-dev/tsconfig.test.json b/packages/plugins/sandbox-providers/exe-dev/tsconfig.test.json new file mode 100644 index 00000000000..90040f25e53 --- /dev/null +++ b/packages/plugins/sandbox-providers/exe-dev/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/packages/plugins/sandbox-providers/kubernetes/package.json b/packages/plugins/sandbox-providers/kubernetes/package.json index 057f6685b8b..efb51c2a4f9 100644 --- a/packages/plugins/sandbox-providers/kubernetes/package.json +++ b/packages/plugins/sandbox-providers/kubernetes/package.json @@ -49,12 +49,12 @@ }, "dependencies": { "@kubernetes/client-node": "^1.0.0", - "zod": "^3.24.2" + "zod": "^4.6.6" }, "devDependencies": { "@types/node": "^24.0.0", "typescript": "^5.7.3", - "vitest": "^3.2.4" + "vitest": "^4.1.8" }, "engines": { "node": ">=24.11.0" diff --git a/packages/plugins/sandbox-providers/kubernetes/src/adapter-registry.ts b/packages/plugins/sandbox-providers/kubernetes/src/adapter-registry.ts index ce732b57db5..0e71c6035ce 100644 --- a/packages/plugins/sandbox-providers/kubernetes/src/adapter-registry.ts +++ b/packages/plugins/sandbox-providers/kubernetes/src/adapter-registry.ts @@ -19,7 +19,7 @@ export const adapterRegistryEntrySchema = z envKeys: z.array(z.string()).optional(), allowFqdns: z.array(z.string()).optional(), probeCommand: z.array(z.string()).optional(), - defaultEnv: z.record(z.string()).optional(), + defaultEnv: z.record(z.string(), z.string()).optional(), }) .strict(); diff --git a/packages/plugins/sandbox-providers/kubernetes/src/types.ts b/packages/plugins/sandbox-providers/kubernetes/src/types.ts index 5626daa9384..e39ccac78ed 100644 --- a/packages/plugins/sandbox-providers/kubernetes/src/types.ts +++ b/packages/plugins/sandbox-providers/kubernetes/src/types.ts @@ -28,7 +28,7 @@ export const kubernetesProviderConfigSchema = z .optional(), runtimeClassName: z.string().optional(), - serviceAccountAnnotations: z.record(z.string()).default({}), + serviceAccountAnnotations: z.record(z.string(), z.string()).default({}), jobTtlSecondsAfterFinished: z.number().int().nonnegative().default(900), podActivityDeadlineSec: z.number().int().positive().default(3600), diff --git a/packages/plugins/sandbox-providers/kubernetes/test/integration/end-to-end-run.test.ts b/packages/plugins/sandbox-providers/kubernetes/test/integration/end-to-end-run.test.ts index 8fb64270bcf..44f5772894b 100644 --- a/packages/plugins/sandbox-providers/kubernetes/test/integration/end-to-end-run.test.ts +++ b/packages/plugins/sandbox-providers/kubernetes/test/integration/end-to-end-run.test.ts @@ -65,6 +65,7 @@ describe("plugin-kubernetes end-to-end", () => { }); expect(lease.providerLeaseId).toMatch(/^pc-/); + const providerLeaseId = lease.providerLeaseId!; // Verify the Job exists in the tenant namespace const jobs = kubectl(`get jobs -n ${NAMESPACE} -o name`); @@ -137,6 +138,7 @@ describe("plugin-kubernetes end-to-end", () => { }); expect(lease.providerLeaseId).toMatch(/^pc-/); + const providerLeaseId = lease.providerLeaseId!; // Verify the Sandbox CR exists in the tenant namespace const sandboxes = kubectl( @@ -156,7 +158,7 @@ describe("plugin-kubernetes end-to-end", () => { await sandboxCrOrchestrator.waitForCompletion( clients, NAMESPACE, - lease.providerLeaseId, + providerLeaseId, { timeoutMs: 90_000, pollMs: 3000 }, ); @@ -164,7 +166,7 @@ describe("plugin-kubernetes end-to-end", () => { const podName = await sandboxCrOrchestrator.findPod( clients, NAMESPACE, - lease.providerLeaseId, + providerLeaseId, ); expect(podName).toBeTruthy(); diff --git a/packages/plugins/sandbox-providers/kubernetes/test/unit/adapter-defaults.test.ts b/packages/plugins/sandbox-providers/kubernetes/test/unit/adapter-defaults.test.ts index 513c2506809..f7f1b78037e 100644 --- a/packages/plugins/sandbox-providers/kubernetes/test/unit/adapter-defaults.test.ts +++ b/packages/plugins/sandbox-providers/kubernetes/test/unit/adapter-defaults.test.ts @@ -75,6 +75,7 @@ describe("getAdapterDefaults", () => { const registry: AdapterRegistryEntry[] = [ { adapterType: "opencode_local", + enabled: true, runtimeImage: "x", envKeys: [], allowFqdns: [], @@ -88,7 +89,7 @@ describe("getAdapterDefaults", () => { it("throws when a supplied registry entry is missing runtimeImage", () => { const registry: AdapterRegistryEntry[] = [ - { adapterType: "opencode_local", envKeys: [], allowFqdns: [], probeCommand: ["x"] }, + { adapterType: "opencode_local", enabled: true, envKeys: [], allowFqdns: [], probeCommand: ["x"] }, ]; expect(() => getAdapterDefaults("opencode_local", registry)).toThrow( /missing required runtime field: runtimeImage/, @@ -97,7 +98,7 @@ describe("getAdapterDefaults", () => { it("defaults the optional array fields to [] when the registry omits them", () => { const registry: AdapterRegistryEntry[] = [ - { adapterType: "opencode_local", runtimeImage: "img" }, + { adapterType: "opencode_local", enabled: true, runtimeImage: "img" }, ]; const d = getAdapterDefaults("opencode_local", registry); expect(d.envKeys).toEqual([]); diff --git a/packages/plugins/sandbox-providers/kubernetes/test/unit/cilium-network-policy.test.ts b/packages/plugins/sandbox-providers/kubernetes/test/unit/cilium-network-policy.test.ts index 419f4b1b33c..4cb6faa8693 100644 --- a/packages/plugins/sandbox-providers/kubernetes/test/unit/cilium-network-policy.test.ts +++ b/packages/plugins/sandbox-providers/kubernetes/test/unit/cilium-network-policy.test.ts @@ -1,5 +1,9 @@ import { describe, it, expect } from "vitest"; -import { buildCiliumNetworkPolicyManifest } from "../../src/cilium-network-policy.js"; +import { buildCiliumNetworkPolicyManifest as buildRawCiliumNetworkPolicyManifest, type BuildCiliumNetworkPolicyInput } from "../../src/cilium-network-policy.js"; +import type { CiliumNetworkPolicyView } from "./manifest-views.js"; + +const buildCiliumNetworkPolicyManifest = (input: BuildCiliumNetworkPolicyInput) => + buildRawCiliumNetworkPolicyManifest(input) as unknown as CiliumNetworkPolicyView; describe("buildCiliumNetworkPolicyManifest", () => { const baseInput = { @@ -27,7 +31,7 @@ describe("buildCiliumNetworkPolicyManifest", () => { }); const fqdnRule = cnp.spec.egress.find((e: { toFQDNs?: { matchName: string }[] }) => e.toFQDNs); expect(fqdnRule).toBeDefined(); - expect(fqdnRule.toFQDNs.map((f: { matchName: string }) => f.matchName).sort()).toEqual([ + expect(fqdnRule!.toFQDNs!.map((f: { matchName: string }) => f.matchName).sort()).toEqual([ "api.anthropic.com", "api.openai.com", ]); @@ -55,7 +59,7 @@ describe("buildCiliumNetworkPolicyManifest", () => { egressAllowCidrs: ["10.0.0.0/8"], }); const cidrRule = cnp.spec.egress.find((e: { toCIDRSet?: { cidr: string }[] }) => e.toCIDRSet); - expect(cidrRule.toCIDRSet[0].cidr).toBe("10.0.0.0/8"); + expect(cidrRule?.toCIDRSet?.[0]?.cidr).toBe("10.0.0.0/8"); }); it("targets only the granted run when building a scoped policy", () => { diff --git a/packages/plugins/sandbox-providers/kubernetes/test/unit/manifest-views.ts b/packages/plugins/sandbox-providers/kubernetes/test/unit/manifest-views.ts new file mode 100644 index 00000000000..a0314c5096e --- /dev/null +++ b/packages/plugins/sandbox-providers/kubernetes/test/unit/manifest-views.ts @@ -0,0 +1,98 @@ +// The manifest builders return Record because the manifests go +// straight to the Kubernetes API. These views name only the fields the tests read. + +export interface PodTemplateView { + metadata: { labels: Record }; + spec: { + automountServiceAccountToken: boolean; + restartPolicy: string; + runtimeClassName?: string; + imagePullSecrets?: { name: string }[]; + securityContext: { + runAsNonRoot: boolean; + runAsUser: number; + fsGroupChangePolicy: string; + seccompProfile: { type: string }; + }; + containers: { + command: string[]; + envFrom: { secretRef: { name: string } }[]; + securityContext: { + runAsNonRoot: boolean; + readOnlyRootFilesystem: boolean; + allowPrivilegeEscalation: boolean; + capabilities: { drop: string[] }; + }; + volumeMounts: { mountPath: string }[]; + }[]; + volumes: { emptyDir?: unknown }[]; + }; +} + +export interface JobManifestView { + apiVersion: string; + kind: string; + metadata: { labels: Record }; + spec: { + backoffLimit: number; + ttlSecondsAfterFinished: number; + activeDeadlineSeconds: number; + template: PodTemplateView; + }; +} + +export interface SandboxCrManifestView { + apiVersion: string; + kind: string; + metadata: { + name: string; + namespace: string; + labels: Record; + ownerReferences?: unknown[]; + }; + spec: { podTemplate: PodTemplateView }; +} + +export interface NetworkPolicyEgressRuleView { + to: { + namespaceSelector?: { matchLabels?: Record }; + podSelector?: { matchLabels?: Record }; + ipBlock?: { cidr: string; except?: string[] }; + }[]; + ports?: { protocol: string; port: number }[]; +} + +export interface DenyAllPolicyView { + metadata: { name: string }; + spec: { + podSelector: Record; + policyTypes: string[]; + ingress?: unknown[]; + egress?: unknown[]; + }; +} + +export interface EgressAllowPolicyView { + metadata: { name: string; ownerReferences?: unknown[] }; + spec: { + podSelector: { matchLabels: Record }; + egress: NetworkPolicyEgressRuleView[]; + }; +} + +export interface CiliumEgressRuleView { + toEndpoints?: { matchLabels: Record }[]; + toPorts?: { ports: { port: string }[] }[]; + toFQDNs?: { matchName: string }[]; + toCIDRSet?: { cidr: string }[]; +} + +export interface CiliumNetworkPolicyView { + apiVersion: string; + kind: string; + metadata: { name: string; ownerReferences?: unknown[] }; + spec: { + endpointSelector: { matchLabels: Record }; + egress: CiliumEgressRuleView[]; + }; +} diff --git a/packages/plugins/sandbox-providers/kubernetes/test/unit/network-policy.test.ts b/packages/plugins/sandbox-providers/kubernetes/test/unit/network-policy.test.ts index 80338e70129..5562d8ed6e4 100644 --- a/packages/plugins/sandbox-providers/kubernetes/test/unit/network-policy.test.ts +++ b/packages/plugins/sandbox-providers/kubernetes/test/unit/network-policy.test.ts @@ -1,5 +1,9 @@ import { describe, it, expect } from "vitest"; -import { buildNetworkPolicyManifests } from "../../src/network-policy.js"; +import { buildNetworkPolicyManifests as buildRawNetworkPolicyManifests, type BuildNetworkPolicyInput } from "../../src/network-policy.js"; +import type { DenyAllPolicyView, EgressAllowPolicyView } from "./manifest-views.js"; + +const buildNetworkPolicyManifests = (input: BuildNetworkPolicyInput) => + buildRawNetworkPolicyManifests(input) as unknown as [DenyAllPolicyView, EgressAllowPolicyView]; describe("buildNetworkPolicyManifests", () => { const baseInput = { @@ -50,7 +54,7 @@ describe("buildNetworkPolicyManifests", () => { r.to.some((t) => t.podSelector?.matchLabels?.app === "paperclip-server"), ); expect(callbackRule).toBeDefined(); - expect(callbackRule.ports[0].port).toBe(3100); + expect(callbackRule!.ports![0].port).toBe(3100); }); it("adds public-IPv4 fallback (with private/link-local excluded) when FQDNs are configured and no CIDRs are supplied", () => { @@ -62,10 +66,10 @@ describe("buildNetworkPolicyManifests", () => { r.to.some((t) => t.ipBlock?.cidr === "0.0.0.0/0"), ); expect(fallback).toBeDefined(); - expect(fallback.to[0].ipBlock.except).toEqual( + expect(fallback!.to[0].ipBlock!.except).toEqual( expect.arrayContaining(["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", "127.0.0.0/8"]), ); - expect(fallback.ports).toEqual( + expect(fallback!.ports).toEqual( expect.arrayContaining([ { protocol: "TCP", port: 443 }, { protocol: "TCP", port: 80 }, @@ -107,6 +111,6 @@ describe("buildNetworkPolicyManifests", () => { expect(egress.metadata.ownerReferences).toHaveLength(1); expect(egress.spec.podSelector.matchLabels).toEqual({ "paperclip.io/run-id": "run-123" }); expect(egress.spec.egress).toHaveLength(1); - expect(egress.spec.egress[0].to[0].ipBlock.cidr).toBe("0.0.0.0/0"); + expect(egress.spec.egress[0].to[0].ipBlock?.cidr).toBe("0.0.0.0/0"); }); }); diff --git a/packages/plugins/sandbox-providers/kubernetes/test/unit/pod-spec-builder.test.ts b/packages/plugins/sandbox-providers/kubernetes/test/unit/pod-spec-builder.test.ts index 75d1da3cc92..593dca7df74 100644 --- a/packages/plugins/sandbox-providers/kubernetes/test/unit/pod-spec-builder.test.ts +++ b/packages/plugins/sandbox-providers/kubernetes/test/unit/pod-spec-builder.test.ts @@ -1,5 +1,9 @@ import { describe, it, expect } from "vitest"; -import { buildJobManifest } from "../../src/pod-spec-builder.js"; +import { buildJobManifest as buildRawJobManifest, type BuildJobManifestInput } from "../../src/pod-spec-builder.js"; +import type { JobManifestView } from "./manifest-views.js"; + +const buildJobManifest = (input: BuildJobManifestInput) => + buildRawJobManifest(input) as unknown as JobManifestView; const baseInput = { namespace: "paperclip-acme", diff --git a/packages/plugins/sandbox-providers/kubernetes/test/unit/sandbox-cr-builder.test.ts b/packages/plugins/sandbox-providers/kubernetes/test/unit/sandbox-cr-builder.test.ts index 97beb8e9a53..01e68d1f59e 100644 --- a/packages/plugins/sandbox-providers/kubernetes/test/unit/sandbox-cr-builder.test.ts +++ b/packages/plugins/sandbox-providers/kubernetes/test/unit/sandbox-cr-builder.test.ts @@ -1,5 +1,9 @@ import { describe, it, expect } from "vitest"; -import { buildSandboxCrManifest } from "../../src/sandbox-cr-builder.js"; +import { buildSandboxCrManifest as buildRawSandboxCrManifest, type BuildSandboxCrManifestInput } from "../../src/sandbox-cr-builder.js"; +import type { SandboxCrManifestView } from "./manifest-views.js"; + +const buildSandboxCrManifest = (input: BuildSandboxCrManifestInput) => + buildRawSandboxCrManifest(input) as unknown as SandboxCrManifestView; const baseInput = { namespace: "paperclip-acme", diff --git a/packages/plugins/sandbox-providers/kubernetes/tsconfig.test.json b/packages/plugins/sandbox-providers/kubernetes/tsconfig.test.json new file mode 100644 index 00000000000..8305c630843 --- /dev/null +++ b/packages/plugins/sandbox-providers/kubernetes/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "." + }, + "include": ["src", "test"], + "exclude": [] +} diff --git a/packages/plugins/sandbox-providers/modal/tsconfig.test.json b/packages/plugins/sandbox-providers/modal/tsconfig.test.json new file mode 100644 index 00000000000..90040f25e53 --- /dev/null +++ b/packages/plugins/sandbox-providers/modal/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/packages/plugins/sandbox-providers/novita/src/plugin.test.ts b/packages/plugins/sandbox-providers/novita/src/plugin.test.ts index 29a3fe22514..6f6b9b7898f 100644 --- a/packages/plugins/sandbox-providers/novita/src/plugin.test.ts +++ b/packages/plugins/sandbox-providers/novita/src/plugin.test.ts @@ -120,10 +120,10 @@ describe("Novita sandbox provider plugin", () => { mockSandboxConnect.mockRejectedValueOnce(new Error("sandbox not found")); const result = await plugin.definition.onEnvironmentExecute?.({ + driverKey: "novita", companyId: "company-1", environmentId: "env-1", issueId: "issue-1", - runId: "run-1", config: { apiKey: "sk-test" }, lease: { providerLeaseId: "sb-expired", metadata: {} }, command: "true", diff --git a/packages/plugins/sandbox-providers/novita/tsconfig.test.json b/packages/plugins/sandbox-providers/novita/tsconfig.test.json new file mode 100644 index 00000000000..90040f25e53 --- /dev/null +++ b/packages/plugins/sandbox-providers/novita/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src"], + "exclude": [] +} diff --git a/packages/plugins/sdk/tests/testing-actions.test.ts b/packages/plugins/sdk/tests/testing-actions.test.ts index ff5eeaf9cef..203a9851d9d 100644 --- a/packages/plugins/sdk/tests/testing-actions.test.ts +++ b/packages/plugins/sdk/tests/testing-actions.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { createTestHarness } from "../src/testing.js"; -import type { PaperclipPluginManifestV1 } from "../src/types.js"; +import type { PaperclipPluginManifestV1, PluginIssuesClient } from "../src/types.js"; const manifest = { id: "paperclip.test-actions", @@ -12,7 +12,7 @@ const manifest = { author: "Paperclip", categories: ["automation"], capabilities: [], - entrypoints: {}, + entrypoints: { worker: "./worker.js" }, } satisfies PaperclipPluginManifestV1; describe("createTestHarness action context", () => { @@ -136,7 +136,8 @@ describe("createTestHarness issue interactions", () => { minSelected: 1, maxSelected: 2, }, - }, + // Omits the fields the schema defaults, so the assertions below cover those defaults. + } as Parameters[1], "company-1", { authorAgentId: "agent-1" }, ); diff --git a/packages/plugins/sdk/tests/testing-lifecycle.test.ts b/packages/plugins/sdk/tests/testing-lifecycle.test.ts index dc8963f6bfa..53b794b33e5 100644 --- a/packages/plugins/sdk/tests/testing-lifecycle.test.ts +++ b/packages/plugins/sdk/tests/testing-lifecycle.test.ts @@ -4,7 +4,7 @@ import type { PaperclipPluginManifestV1, ResourceLifecycleEvent } from "../src/t const manifest: PaperclipPluginManifestV1 = { id: "fixture.lifecycle", apiVersion: 1, version: "1.0.0", displayName: "Lifecycle", description: "Fixture", - author: "Tests", categories: ["automation"], capabilities: ["events.subscribe"], entrypoints: {}, + author: "Tests", categories: ["automation"], capabilities: ["events.subscribe"], entrypoints: { worker: "./worker.js" }, }; describe("Lifecycle inbox test harness", () => { diff --git a/packages/plugins/sdk/tests/worker-rpc-host.test.ts b/packages/plugins/sdk/tests/worker-rpc-host.test.ts index ae409e00ef6..9aafcf5a26b 100644 --- a/packages/plugins/sdk/tests/worker-rpc-host.test.ts +++ b/packages/plugins/sdk/tests/worker-rpc-host.test.ts @@ -874,8 +874,8 @@ describe("worker setup-token pseudo-terminal dispatch", () => { let nextRequestId = 1; // The fake session the opener returns. The test drives its output and exit. - let emitOutput: ((chunk: string) => void) | null = null; - let resolveWait: ((value: { exitCode: number | null }) => void) | null = null; + let emitOutput = null as ((chunk: string) => void) | null; + let resolveWait = null as ((value: { exitCode: number | null }) => void) | null; const inputs: string[] = []; let killed = 0; let closed = 0; @@ -1029,16 +1029,16 @@ describe("worker duplex channel dispatch", () => { let stopped = 0; let closed = 0; - // The plugin declares the four duplex channel handlers. The open returns a - // worker session id. The close keys on the host route id and returns a bound - // acknowledgement. + // The plugin declares the four duplex channel handlers. The open echoes the + // host route id and returns a worker session id. The close keys on the host + // route id and returns a bound acknowledgement. const controllablePlugin = definePlugin({ async setup() {}, async onDuplexChannelOpen(params) { expect(params.hostRouteId).toBe("route-1"); expect(params.command).toEqual(["paperclip-bridge"]); expect(params.providerLeaseId).toBe("lease-1"); - return { workerSessionId: "ws-1" }; + return { hostRouteId: params.hostRouteId, workerSessionId: "ws-1" }; }, async onDuplexChannelWrite(params) { writes.push(params.data); @@ -1117,7 +1117,7 @@ describe("worker duplex channel dispatch", () => { providerLeaseId: "lease-1", command: ["paperclip-bridge"], }), - ).resolves.toEqual({ workerSessionId: "ws-1" }); + ).resolves.toEqual({ hostRouteId: "route-1", workerSessionId: "ws-1" }); await callWorker("duplexChannelWrite", { workerSessionId: "ws-1", data: "payload" }); await callWorker("duplexChannelStop", { workerSessionId: "ws-1" }); @@ -1212,7 +1212,7 @@ describe("AI connection router RPC", () => { const reader = createInterface({ input: output }); const request = { companyId: "company", taskKey: "task", candidates: [], memberOrder: [] }; let received: unknown; - const plugin = definePlugin({ async setup() {}, onRouteAiConnection(params) { received = params; return { kind: "selected", memberId: "authorized-member" }; } }); + const plugin = definePlugin({ async setup() {}, async onRouteAiConnection(params) { received = params; return { kind: "selected", memberId: "authorized-member" }; } }); const worker = startWorkerRpcHost({ plugin, stdin: input, stdout: output }); let sequence = 0; const pending = new Map void>(); @@ -1235,9 +1235,10 @@ describe("Durable lifecycle inbox RPC", () => { const event = { id: "7", companyId: "company-a", resourceType: "agent", resourceId: "agent-a", action: "pause", createdAt: new Date(0).toISOString() }; const plugin = definePlugin({ async setup(ctx) { ctx.data.register("drain", async ({ companyId }) => { - const events = await ctx.events.listLifecycle(companyId!, 10, "6"); - await ctx.events.acknowledgeLifecycle(companyId!, events[0].id); - await ctx.agents.getRuntime("agent-a", companyId!, "environment-a", true); + if (typeof companyId !== "string") throw new Error("companyId is required"); + const events = await ctx.events.listLifecycle(companyId, 10, "6"); + await ctx.events.acknowledgeLifecycle(companyId, events[0].id); + await ctx.agents.getRuntime("agent-a", companyId, "environment-a", true); return events; }); } }); diff --git a/packages/plugins/sdk/tsconfig.test.json b/packages/plugins/sdk/tsconfig.test.json new file mode 100644 index 00000000000..508201b3b8e --- /dev/null +++ b/packages/plugins/sdk/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "." + }, + "include": ["src", "tests"] +} diff --git a/scripts/typecheck-sandbox-providers/package.json b/scripts/typecheck-sandbox-providers/package.json new file mode 100644 index 00000000000..c26c721beb8 --- /dev/null +++ b/scripts/typecheck-sandbox-providers/package.json @@ -0,0 +1,8 @@ +{ + "name": "@paperclipai/typecheck-sandbox-providers", + "private": true, + "description": "Installs sandbox provider dependencies for scripts/typecheck-test-programs.mjs. See doc/DEVELOPING.md.", + "engines": { + "node": ">=24.11.0" + } +} diff --git a/scripts/typecheck-sandbox-providers/pnpm-lock.yaml b/scripts/typecheck-sandbox-providers/pnpm-lock.yaml new file mode 100644 index 00000000000..b3c4b347403 --- /dev/null +++ b/scripts/typecheck-sandbox-providers/pnpm-lock.yaml @@ -0,0 +1,4206 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + ../../packages/plugins/sandbox-providers/boxd: + dependencies: + '@boxd-sh/sdk': + specifier: ^0.2.20 + version: 0.2.22 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + + ../../packages/plugins/sandbox-providers/cloudflare: + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + + ../../packages/plugins/sandbox-providers/cloudflare/bridge-template: + dependencies: + '@cloudflare/sandbox': + specifier: ^0.7.0 + version: 0.7.21 + devDependencies: + '@cloudflare/workers-types': + specifier: ^4.20260501.0 + version: 4.20260702.1 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + wrangler: + specifier: ^4.15.0 + version: 4.156.2(@cloudflare/workers-types@4.20260702.1)(@types/node@24.19.2) + + ../../packages/plugins/sandbox-providers/createos: + dependencies: + tar: + specifier: ^7.5.22 + version: 7.5.22 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + + ../../packages/plugins/sandbox-providers/daytona: + dependencies: + '@daytonaio/sdk': + specifier: 0.203.0 + version: 0.203.0 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + + ../../packages/plugins/sandbox-providers/e2b: + dependencies: + e2b: + specifier: ^2.19.0 + version: 2.62.1 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + + ../../packages/plugins/sandbox-providers/exe-dev: + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + + ../../packages/plugins/sandbox-providers/kubernetes: + dependencies: + '@kubernetes/client-node': + specifier: ^1.0.0 + version: 1.4.0 + zod: + specifier: ^4.6.6 + version: 4.6.7 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + + ../../packages/plugins/sandbox-providers/modal: + dependencies: + modal: + specifier: ^0.7.4 + version: 0.7.6 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + + ../../packages/plugins/sandbox-providers/novita: + dependencies: + novita-sandbox: + specifier: ^1.0.3-b2 + version: 1.0.3-b2 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.19.2 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + +packages: + + '@aws-sdk/checksums@3.1002.1': + resolution: {integrity: sha512-CXNESwkQC9jdrUG6cKXBfudVt1frGcYQykcCYbNclVrsewuGMbo3PlFjRteMe4OftNAqhyc3xC2jTz7SUqLasQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-s3@3.1161.0': + resolution: {integrity: sha512-iRZGMsJnYRrI98O+U6o5fHeLPQTP+Dc/w0mWLcBLQcEQ10DA06+XQj0WmdRiCM7vIso7QEtN1fsj9Vzo5/R3LQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.979.1': + resolution: {integrity: sha512-ayOlV1VhLNMiq8PW9ZkVobG4gkldnUoHjG69yy2b8j+VYFlhz/uAL7ZdYQAoQ+eShtGUdNDnnG5rxDg6yUJrOg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.80': + resolution: {integrity: sha512-uO5TiSvb6j1w9RTe5z28OaOmsrOxVQUPaC5+wZl50RfOXcrYD/KgnS8I9lfyGXc7sGbkCopK6VnhNIAoLqfewQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.82': + resolution: {integrity: sha512-vQzlDHlC24Yz42p+rT4NBoP/5VdetkBoujbOLq9h6QYWC8THuZSPYnXIR9OP3XcOrdMgPBBipOrdssfxnIiWUg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.973.25': + resolution: {integrity: sha512-b5xl71uCRdWsu7P1VeGpiGuCf6zfYETsSNOv8AmYVTrsgbvTJRXdmmOPVfxkvGGVNsiKkGctClQW3jAn0SZLug==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.87': + resolution: {integrity: sha512-C4ZFmcwM+Zj14Z8huVUk54Gv4uzT1ZdzG9cY3Gdh8FePakq7Bss1OFsgZjAhWFOkFOGpBvFBeu0OWjWf8HNKRQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.92': + resolution: {integrity: sha512-jC3zmOo7Qxlk+oBivvSsS1Ph7sFGm0QfuhqDGbqgRXi4cxUYMG+dWtBwS2XYYXgc8TIVXJiwgQ65sbNpbYupTA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.80': + resolution: {integrity: sha512-xqfixO5aclje7tbQJDias+iNND5wp/3QZixCdZcVQpgWPTZ0V5DfTW9A804A5Z7ezu7sH3WRO+vzUjtiJBA8yg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.973.24': + resolution: {integrity: sha512-nlg3BKG03Cgzvv/dMnhxvDJPhAhQSpaN0RUdwgU7ziwkasoev7iI8el8Xy4Rd5sqzgaDUbV269fOXck5SargqA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.86': + resolution: {integrity: sha512-zKTyLJBEyfQBVlYqT6Jt+mYV4r8kGbWM7dn6v4Y4MRARizhYafjDXP/iMu76UMRhToSPNmIKX1XAb24gtpBFIA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/lib-storage@3.1161.0': + resolution: {integrity: sha512-ROSytGylVRtkUsp64mpzryv1Puvgo6jAm4RWTW2m3kC4SSsjrvhqZa5uPKfz116a0U1kk/hBz9S1eBItkyd++w==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@aws-sdk/client-s3': ^3.1161.0 + + '@aws-sdk/middleware-sdk-s3@3.972.85': + resolution: {integrity: sha512-Kyl2G6zhIS9qFoeLdlxvXFrWyc6g9D9ZCIRJnGkkfX6JZhxCZB5ebXBc7GKEeetrnhXcail6ALZT0lKbojcaNA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.54': + resolution: {integrity: sha512-/OczlavsS7U5sOodRO10Ug4R/AKFVZiXTstKipWe68EfjPkmdDFNXNl9FCJyXWkg1DG6RBm3qAuVB8FNW9O8KQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.51': + resolution: {integrity: sha512-+aHvUugVBuMPXxgkPfWnTMyGdE/yWS8GHi5+qQ37HowS0YqzrbuaDLznj+Dtmtu5jgCdqz36gXmujOToUhNNWw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1161.0': + resolution: {integrity: sha512-ypbXSofNcSHwf/KA40bFCXJb0MLaAKrfT7sot4dFaA+2lsuXNah4zLRJIAU7Qmvs9PFJ6rnY0bZFEC+mE7attA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.975.0': + resolution: {integrity: sha512-WQlhs6juByzWOzj5fwxOx0mPJaEqMcG/sDpl3Id61B3XhKE63xY+0QWcnG+sWR/sCfprmreyUo1lM7IKZmHMzw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.42': + resolution: {integrity: sha512-smX8/gNP8Kjq+CWTBMZQLQ912vOQjiWExzwzvMO+eujPB5RY8IbtF0AGdUUXHKN7KoDWK9dIcuW0WG269kiP6w==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.3.0': + resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} + engines: {node: '>=18.0.0'} + + '@boxd-sh/sdk@0.2.22': + resolution: {integrity: sha512-NvvYy5nblUPUM+F3EUcjgIH7uzu1ebMiEuL7tf0+4D/2CaszleSAd93S3Pn7py5ywrVLXfB5Rzj/GuABj67Z5g==} + engines: {node: '>=20'} + + '@bufbuild/protobuf@2.16.0': + resolution: {integrity: sha512-FWa0sPlqYGJgpTs6OxBRcL/AW4JT0OIO+W1ajerluoTVh8CV0B7XM1qsNCRgnkSCkRCgOICKHRQlY17jrsm0+Q==} + + '@cbor-extract/cbor-extract-darwin-arm64@2.2.3': + resolution: {integrity: sha512-qpUy9Yoisi6a7QHOVa+AlnqfN7eVstSRhpLwiLnwi6i3SNLkM5D2Z7MXl4LC5nUGA77mZEt479cPH3u8GD163Q==} + cpu: [arm64] + os: [darwin] + + '@cbor-extract/cbor-extract-darwin-x64@2.2.3': + resolution: {integrity: sha512-6bZbK7dyKn8BUNmG3UfzSnM4L/OzJ8xGAtBHYycprl+bgvtZmZoQC7CucWXuF/oHJP5skzqkqFr6BLSSGIkoWw==} + cpu: [x64] + os: [darwin] + + '@cbor-extract/cbor-extract-linux-arm64@2.2.3': + resolution: {integrity: sha512-QAuFuQkOw0cm4MNc+QwxH9aiMxmrczESnVEu33B8m2Du9fBuH/1AvJfmO8YajpeWLoHqt2iUG3d1/gIWAKY8IQ==} + cpu: [arm64] + os: [linux] + + '@cbor-extract/cbor-extract-linux-arm@2.2.3': + resolution: {integrity: sha512-joCovTc7uEYCL9XxdAJJpGIH7Vvc31Fi7vh3HnlY5zgkLSU/64WxYl5iUi/puE21qT3bmxMfOZEZUbnqVKeJTQ==} + cpu: [arm] + os: [linux] + + '@cbor-extract/cbor-extract-linux-x64@2.2.3': + resolution: {integrity: sha512-IQMPVLulOvFv9Had7J1idJyfQzsFamDVdjH37qW7suHI7mgO4w1Oo3MisL7AjnWARxo0/ZmEEQ3i81WA7y34Ug==} + cpu: [x64] + os: [linux] + + '@cbor-extract/cbor-extract-win32-x64@2.2.3': + resolution: {integrity: sha512-fGFfq6F3l0GCoKOZeyjjm3q+NHO7jo8ILtQ93U3rYJOgG2BGLS/QmBdoFLNEsoWPjyxAS8svWhTyo73MTDawog==} + cpu: [x64] + os: [win32] + + '@cloudflare/containers@0.1.1': + resolution: {integrity: sha512-YTdobRTnTlUOUPMFemufH367A9Z8pDfZ+UboYMLbGpO0VlvEXZDiioSmXPQMHld2vRtkL31mcRii3bcbQU6fdw==} + + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} + + '@cloudflare/sandbox@0.7.21': + resolution: {integrity: sha512-BlfbhIWF83NkmixxGVpE9zrcR0Ix5fVgV+zzfJByxyRnXUS+g2aUeIXK2opBcnX8WWlk6v5+RYTKkSz1XKQ4eg==} + peerDependencies: + '@openai/agents': ^0.3.3 + '@opencode-ai/sdk': ^1.1.40 + '@xterm/xterm': '>=5.0.0' + peerDependenciesMeta: + '@openai/agents': + optional: true + '@opencode-ai/sdk': + optional: true + '@xterm/xterm': + optional: true + + '@cloudflare/unenv-preset@2.16.2': + resolution: {integrity: sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: '>1.20260305.0 <2.0.0-0' + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/workerd-darwin-64@1.20261102.1': + resolution: {integrity: sha512-5vxE27qqftDMv9H0mCZt0B1cr+EvszT/lhpoFmdKqtS2Uu+Jo2PMIUYGepLe6yxbdV1hPKkgLBD2scnW3lXe/Q==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20261102.1': + resolution: {integrity: sha512-VanKNl3pLP2k8j9nEvETp/l3+dvfGvdWKI6bNJByKVz5szVVIMnGvDvrr+JmotreUZHWTDqfLh2RzPGRq1cZzg==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20261102.1': + resolution: {integrity: sha512-hQdSdaHLS4UFGZRoue9TFVOO9UPnOAGWw3ZTZnUtXoUGWf4THwatq0l55F7IbOQYcOJfeKpHoFcMoGEH/88GCA==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20261102.1': + resolution: {integrity: sha512-cqMnRpvDT6v6K80YClFs7XnZMq2RC5H6aHawlrVsIHrmVdSEXUm7OEVMQ9lY5nx3EI4JftiTeFpkLHFZqTSNVg==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20261102.1': + resolution: {integrity: sha512-s9iKBbAdNIdVjpMUHh48zbCfCHw5wbLl8I2aIW74H3OtxpWZP/cHeEwZZMq3xAz5kt4MmvwwO4hDbNYoAD4yhw==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workers-types@4.20260702.1': + resolution: {integrity: sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==} + + '@connectrpc/connect-web@2.0.0-rc.3': + resolution: {integrity: sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw==} + peerDependencies: + '@bufbuild/protobuf': ^2.2.0 + '@connectrpc/connect': 2.0.0-rc.3 + + '@connectrpc/connect-web@2.3.0': + resolution: {integrity: sha512-JPXOiWaVsj+ZwaXOQjwvhc0ORnkl0a1w36Qyl3j1WTuak6/VYyuWGl1Aq8I60gQfQRjsjLLFGr64tO0sjwstcg==} + peerDependencies: + '@bufbuild/protobuf': ^2.7.0 + '@connectrpc/connect': 2.3.0 + + '@connectrpc/connect@2.0.0-rc.3': + resolution: {integrity: sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ==} + peerDependencies: + '@bufbuild/protobuf': ^2.2.0 + + '@connectrpc/connect@2.3.0': + resolution: {integrity: sha512-rjAn8L9KbAZrkwiB8m1qzDsLs74hkYmwwM2iSWuxTPVOTRm/EZw31smKbQqPU50AXqJC5eKKpEnA6Uf/swQOgQ==} + peerDependencies: + '@bufbuild/protobuf': ^2.7.0 + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@daytona/analytics-api-client@0.203.0': + resolution: {integrity: sha512-sUELDOmSJW73kG8xsEOUVAE+SGAhPIaLThIBJc6Zbp+AnEMObeUNwXFh9r9ATCdYULoDkTVVA8mMqzP46io9LQ==} + + '@daytona/api-client@0.203.0': + resolution: {integrity: sha512-d72iv8+FECQo0h4G9fOIAr7iVuJaa/c1KJXfG2LeeI3uG+uCWh+E0d8pfgW9nAMQS4wD1ZoTuuzPPUedSn7g7Q==} + + '@daytona/toolbox-api-client@0.203.0': + resolution: {integrity: sha512-J3X72PLZOrzR4KUQj6EHdNU4ALZnN8FBPt49h7Q7UqGWdeq21ZmgQ69acOcGZyDq/rc6xPlHKgfQ5W5YLYHdpg==} + + '@daytonaio/sdk@0.203.0': + resolution: {integrity: sha512-wrCfeZFm36l/oVVyOVJsbPwmcaxNAkzH/z00UKBaEjoda5PYz9gqTqJF9e8vgSpkDT28ZMz3CaYnyl6AO/5Ucw==} + deprecated: 'Moved to @daytona/sdk, same API, no breaking changes. Please update: npm uninstall @daytonaio/sdk && npm i @daytona/sdk' + + '@e2b/dockerfile-utils@0.1.1': + resolution: {integrity: sha512-eDmlWRMrJNzSUbBOf6Y/qTChd1Ys/o3YY8PK6tRQmwkV2u7z3ZS2cak/YJB07odHMT/Z6HJKoUElR/b0N6bHUw==} + engines: {node: '>=22'} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@grpc/grpc-js@1.14.7': + resolution: {integrity: sha512-W4W/fcBMGRVtG6dW2c7cw2mnpS3hvbBd+oQXjh2iYFTZDk1JJmcNqkM5KSaPubQpamlUUurrlb7G1s9NJeERmQ==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + + '@iarna/toml@2.2.5': + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.5': + resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.5': + resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.5': + resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.4': + resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.4': + resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.4': + resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.3.4': + resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.3.4': + resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.3.4': + resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.3.4': + resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.3.4': + resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.35.5': + resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.35.5': + resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.35.5': + resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.35.5': + resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.35.5': + resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.35.5': + resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.35.5': + resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.35.5': + resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.35.5': + resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.5': + resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.35.5': + resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.5': + resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.35.5': + resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@isaacs/nonrecursive-glob-match@1.0.3': + resolution: {integrity: sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@jsep-plugin/assignment@1.4.0': + resolution: {integrity: sha512-nw2Px23Eirx0PFTVoPrb3FEq9Wkmg6vxcDtt38MtBCf1G0tjLCB9cK+3seOXyiw+6+aalgYhvJabQbWwMVKlEQ==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + + '@jsep-plugin/regex@1.1.0': + resolution: {integrity: sha512-2Vkbda+XRjK6VTI6Lrh0sNBuMT8mZ51EhqMvO+V2rSdmqjs7NAGssxPZfkL23EFzYfSrvxyd1ZjOdqPQMM50og==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + + '@kubernetes/client-node@1.4.0': + resolution: {integrity: sha512-Zge3YvF7DJi264dU1b3wb/GmzR99JhUpqTvp+VGHfwZT+g7EOOYNScDJNZwXy9cszyIGPIs0VHr+kk8e95qqrA==} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@opentelemetry/api-logs@0.220.0': + resolution: {integrity: sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.10.0': + resolution: {integrity: sha512-lMkyes8iHrG+mk6WH7X70MdCLI2RS0FwNBY1PyQ8UemaijE2m5mv5A0Niy3HtbYTFrgkUEQjFMXmSAk2hQDuPQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/configuration@0.220.0': + resolution: {integrity: sha512-glfIVKnZevRin8fY/9uES/mhRtMT1lGINLHc9MIo5fTQZXswEEHamJtgjv4MTtzgnhHGC92mIS/0lzAUZMyE0w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/context-async-hooks@2.9.0': + resolution: {integrity: sha512-OQ0vzvbZBiUhjqLnUaoNfYmP8553Crr3aggB4y0ZUi815mZ7idpdJXQmoKdeBKJelYttoBlLSSHubmyw3wvX4w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.12.0': + resolution: {integrity: sha512-1HsSAuvT4/my0QrXWsyXFdWjKaPeCHsismKZgEEIb8NK13LP6DrMRlsUbvD/0F9mDWK3xDom9AMv3IscxgOnOQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.9.0': + resolution: {integrity: sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.220.0': + resolution: {integrity: sha512-s0sRPCSlXYqlgObOpCftomJllp3LfUL9FobQ5csg2172ydVhSEnu1ptpsVBJadazs5nUNp7vDuLE03FAFWTLOQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-http@0.220.0': + resolution: {integrity: sha512-8186thl+pTw64iz/qEEen5oJZoZ/gO73XruChdaGlYdWOdBIQ42r+vHLf6a7vIDqTD4b8ZOoMlyxptanECaI9A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.220.0': + resolution: {integrity: sha512-8LZAxdJ0ENDAFwr4j0oY35mHBltiSzvlhdQAPGiC7p9VnxtuSq4SW1gfBAdW6t6hiQG6OwUl8w7KHaOdJPKHWg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-grpc@0.220.0': + resolution: {integrity: sha512-U128izvJfX/dW9jRGP0gIfadR1Hg7ft3UEGIeRxLFK70m2BWw6AtNCOnsUygpw2zCgR/ygdWbGpcL6TmhW0ZGw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.220.0': + resolution: {integrity: sha512-Yqt3RBw/bRVncaE9qIIhk4WfjbAQqXuP9FgAaU+IKPndnLEp/cUqZlSC324+bpmduRz7DoTjig8Ub0PeILWXUA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-proto@0.220.0': + resolution: {integrity: sha512-lyO+IQBdSvqHN/ZOW/OzrSWemtfD+HgWngn+HBNLhjy0YrCQQTz0OE/kSekH2Pl340dn9DWzhqHdz5Eftr+HLA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-prometheus@0.220.0': + resolution: {integrity: sha512-JZD5DL/NBpVd2BHefvYosm3G40UZ/KzExLv5tc0eZe0CtrsHHtcOk3YPUxR2EINmUeBf8+w5UReTV8fFPn95lA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.220.0': + resolution: {integrity: sha512-bv1xmNhmNwIM6MdUBw4yYuJeVcEViVLk3uD69vOQMwueHBnfyl/u0HnBlB1FNY/Te0UOzJzvcbyR8wN6b+iGbA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.220.0': + resolution: {integrity: sha512-/+ExB3lRkf+erv4PnoywyL7RHKITidxtUpUTS55k7OQ0dB42S7gEF1gry7swb9MSm1hYLUhJg4QQh9W8SpwwqA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.220.0': + resolution: {integrity: sha512-voTAD8XgJxlK7zLkXh8EzMB09zrQr3tyY/BsnDTlDiQU/UdK58MZ63A3mUjdEDrxMjCVmBHU3WQJhRmQe+Dvzg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@2.9.0': + resolution: {integrity: sha512-RwINoce2BH8T4obT5pMcAla2sWma1YZvYuaktWmTluQ0PkQdvv5D060rWI1+kawX+J2qBRcMbwrZJJNcMJUauQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-http@0.220.0': + resolution: {integrity: sha512-Szt4dO2Boz2CDr38DaSw/lnqwhwKl+IAdgNGEGgSm2Anb+fwPtIAGmIwkhsLLN69QQZQE96JxjMKYY4rlRkYKw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.220.0': + resolution: {integrity: sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.220.0': + resolution: {integrity: sha512-CXYo8UD5Mn9YbgebO2EL4wejtA+gxLmLiu6HCk2KH2BR7XhFN6/6p1UlCb23DYCjeYkndevLHuejCCN1yx4+OQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.220.0': + resolution: {integrity: sha512-/eIkBPMBTIvM3x/0mDX4aJeSkYifYClnBPr68PL1h5LV4VQv4+SV6CGrpiZ4fIWDnobVmhTWCm1J/QRdAWUfvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.220.0': + resolution: {integrity: sha512-lXGrv7KXZ0gNH9SVNUaa6vv6phVYGvJxfXAlMbzbakiXru75f5MZl8Z7oqiMMQD77riVHJCFlQvbZs/VVN2/4A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@2.9.0': + resolution: {integrity: sha512-WrOT1WsOUG+B7hstD2RYoMPIOK76G8E9AQHhMjUvrQaGx/oA7rPWQvvr1Rqv7+yy4R0ZMVwWLC4vW2xnkgWPAQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@2.9.0': + resolution: {integrity: sha512-4mYGty27rYvSM0jtp1ZUOqd3LfVRCYg9H5G9OFzSx5HViYToU21MFhWfco7x1HwXr7ER8yGOiCIHZUwjPksc0Q==} + engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Package no longer supported. See https://github.com/open-telemetry/opentelemetry-js/pull/7077 + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@2.12.0': + resolution: {integrity: sha512-+rSRoOdln6NhXTVcBdHSlZxJDNzwxadeweK2ltZO3tVtfp6aklABc06xclDk6ovRR+EiGgIcSbh/6NgjFhnb3A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.9.0': + resolution: {integrity: sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.220.0': + resolution: {integrity: sha512-WywcTkQtv2iNmt+6y5Kcd4rzvx9bLVsBa2Nwcmg01IUaBTkTow3W4d9KE5vNBpEDtb9tp21WcRBY/lANRrApYA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.9.0': + resolution: {integrity: sha512-Xx8RGS4H5XEBl01WuCreMIpiah9cCXMbSkeuIePPdD2cUpq/vUzYmj8E/MK1OsbOc93FuAD4jfn2WOacKwLn7Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-node@0.220.0': + resolution: {integrity: sha512-wHtGyHhSKHNH3fym33xRu4Ef/HXTFvX8eQ42xdQdEO9LYx9Y2qNyBDJytyqVlvmo6abWZlNYTUthuAGUMYqYnQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.12.0': + resolution: {integrity: sha512-JOERgZCxRM06FEJ5kEWxlSRChah6MmXDx3KwCruqEv/iEyLkTokCSemf2y1w8oIHtfrr8EVTpzebq/31vn2Cpg==} + engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.9.0': + resolution: {integrity: sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag==} + engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.9.0': + resolution: {integrity: sha512-ec9a7ps37huy5itYk0MalaZdSLlM6AXWp/FhtEjgMpp5leEGojBDvAl/UWttQnkMZOvFHKzRESn8TD3yKTF5nQ==} + engines: {node: ^18.19.0 || >=20.6.0} + deprecated: Use @opentelemetry/sdk-trace package + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace@2.12.0': + resolution: {integrity: sha512-ImVuRQ6faOsTi1Wna88s2avgrrf/iDgXPigIeynkUhjstMuoQUDOJXgkez2j1mlU4btjXcXBGa+q7qKFYR3GsA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace@2.9.0': + resolution: {integrity: sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.44.0': + resolution: {integrity: sha512-E3JkOlMBi3N8OGGkbl7tFyqk2MAS6/lySRZBK3Mu7AzjF1fT8oIChH9ChE48SUWzDn9Kdwta+G/p5LJ4xW4CLQ==} + engines: {node: '>=14'} + + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@rollup/rollup-android-arm-eabi@4.67.4': + resolution: {integrity: sha512-/v/pUAPOcwp+r1Hjjdg0cBV1TE89CzGKrKh2FgkIQw2QBcIMmVPOCSfuOZStCn4Xh/98HXfo/ht6ztcssctPNg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.67.4': + resolution: {integrity: sha512-6tmmB/P52Vt167FXXEw5auiEUgTtZz7aqUgWHqYXToIUE0HFDY6fTdAyeekjjsNk6O/8dtpSKtClxSj1mMneKQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-android-x64@4.67.4': + resolution: {integrity: sha512-8Z9Q4RmotVmuL4llOvqX814JNiOK1oqoBv+glG5KbpRfUiypqzz2/fxIzK4Pq129l4dT5a8Gszmpc0/XPYoFuQ==} + cpu: [x64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.67.4': + resolution: {integrity: sha512-pnq6hssxTtwuRAl6Kdi/+IDY+b1IhkUetjhUkdLAAJCssjfqtL2Oqy/FXJVJFtAWOu1oRlArIvf0Yk1O92x2HQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.67.4': + resolution: {integrity: sha512-BeIrGZvHMVdJg/N+ohZfFywG/gF41684YhDj2wBvouj7m9ziP1tZDUNwzXRiDY+3WIKngaC4hiUBqVtBPGP5og==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.67.4': + resolution: {integrity: sha512-fRFX/oqNg/g780DKoS8RIfYcZD59Ru5O/bSeK1N7gQljBFrTxJY/q5EX4eMs5SrJf32VgVBVVYCdfPpzAwPPnQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.67.4': + resolution: {integrity: sha512-bSrBRopG1q+0RuOsW0NtYTtIODlvNA3T3ebx5WnQVdSxWIoXFmj+rZdHUHdSLgFdZ5M6VGmekYE7r4RMGQXjfg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.67.4': + resolution: {integrity: sha512-L8IvdrJVtl65g1ozwDSyjGPG+FmibhCdFUwmqTItJLw0S3+Oj4QzFs7rfgTUiK7rsPjkCLdG1/GApFxoynXFJQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.67.4': + resolution: {integrity: sha512-h7PcoQOzC31jshNpQ48J0akB9tsghjkSjSl5AZ6XsVGTxVsln7t7C4SQyw62tNVSaz2SeHgk4VT2ELbL17BSHQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.67.4': + resolution: {integrity: sha512-KSPuOBNHoOl7AsVjeY1KZci8+0up0U4TFAHU5IzBYN8P+Z6cB9DZq28+JQycZW7f6WLLa/tSzhV7Rdy95bVi+g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.67.4': + resolution: {integrity: sha512-0E/mKmVk1PlQeQYygIwQm0jlwjANTzGSiNBMPrFx+rdFGFj/2Axh4p6XwcrystQIqXZVudTaGsdbDpzYgJ4wSA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.67.4': + resolution: {integrity: sha512-Fvw7dOSc2Sa9UOipRZVI0zOgdLCzsjVyD/6JuHXzUHTXFu7mMgmeX/0/bCVBIut1dz+nWz3pV4WpS/Qg0XVnxw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.67.4': + resolution: {integrity: sha512-jzxbVHaE7V3F+P0wEK7UydQtvv7Twfdtx58jI5/3VRx1jJxyrn+qFwfXmqjVFxPhBl+zjaRs+ZJMFJw7vdHaQA==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.67.4': + resolution: {integrity: sha512-fhtrzp9p4GjEJVaigaap9g6X9nP6xgJtKiLLXSDgpE18HXufA5aqfltGn+KAqMtVZReJZfOrXm//y5YSA9BWbA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.67.4': + resolution: {integrity: sha512-uynaF7IUHwAckV0S+yC1f5esy9gB+aqCBsPH5BOck32TYdCuAn2lVG5tvUcFnEsrviGEmRZxE1NHSOieUPYJEg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.67.4': + resolution: {integrity: sha512-uaqRFdfb8eIa2bVHN/FE3ndCvR0zV2xv0AwjCfxrqSu6l6fp/54VhjfhslKlf2RIik9L1jwlt4E56PeVX6tQtQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.67.4': + resolution: {integrity: sha512-JZRow7kmxUDEASnxHuVG5itTm+nTXi9BQUABcVUnUEngC+OPwMrpx1uJ9hIA/v+IcAEBL52ktCSPXk5OJn9Jqg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.67.4': + resolution: {integrity: sha512-rdrh4e+Lq9fCNKoDPaCqeHXbQCe7yl77Ukl45tlSClwYdZ1gpFGZz1XLJdYtY++FRlfL5aHvb6/0n3GDW/Nxsw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.67.4': + resolution: {integrity: sha512-wL7MS4r0fYcxluF3ne2plofHZiB/LsqNCnyaUlyiKpAJLb6agPSFz7DEFCfkonWAZXMrLwCmC08b2ZEnEdJkog==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.67.4': + resolution: {integrity: sha512-4H0XHfd2VufGjX7NjUyeue78sD0bRIoKSGjGqO+F5nSvL6jN48NAU7esdmtFEYxZXwObLtt3MJ/kHS0jASMW3g==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.67.4': + resolution: {integrity: sha512-osRHQbE55pZpCLetpMsFmhkapyZnAUH73qRKMh+npi3boUom6u6hdaTTPSxlInXxwJHsn9r2uDhdz0V7Yoxx2A==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.67.4': + resolution: {integrity: sha512-95+exYEyOwrZePsE0czi4q7X5lxKPa21+lmyAZlwbYENITQ0Unk4HAQAFp/p+KhXBHBx9mUABXRYjOC5A1wY3Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.67.4': + resolution: {integrity: sha512-FOXOcKhNQpO4Po+Ynan6IbtM67SrXZbgpRJWVYwz53CT5veI7sVzDwVBmWS65N5Imv+wK3SVhIPAl+C5DXXNCg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.67.4': + resolution: {integrity: sha512-cklKh0gJOkAZQcWziQa3hrjxZKN+PCHZ7SPTyGJDAiBVr05ZXlEjGFCG7zFJMaVibxsmzAe1FimN4u/HBns9+Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.67.4': + resolution: {integrity: sha512-AegwxJPc9bvUp8fQtlsUHkFMOMkGylWU7glyL+YD4vwG+QaYoGXYLSsQoeTD7kvoLZrVlMWNVhTg1dedY5C1VQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.67.4': + resolution: {integrity: sha512-RJZnrwTWj7Iq2ooBwoGbDdC65tDwYawneGcAfFMJqVUrRZMcskka6nTkBpXR/47G7kzn3EW2qupExnaBy11vwg==} + cpu: [x64] + os: [win32] + + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + + '@smithy/core@3.38.1': + resolution: {integrity: sha512-U/mB2XiwTLaf+NSgLSWwJfptsMT/H8aBi0aTAA/E5e2/TagFeGCqAyhKGG0w0A9IWTkm4qYEmLoD/HB6aYfnRA==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.5.2': + resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.8.0': + resolution: {integrity: sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.14.1': + resolution: {integrity: sha512-/T6E1jzCL0fErS9uq8JqES/5yYQQ4ppVmmYHwXw2nlov8KFzLb+3QqHR5iAun1BsuAB50IPxZliV8YMgghLwhA==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.7.5': + resolution: {integrity: sha512-WPjdr5oWOk1LArpI4pavu+fSo+/evi0Re1olxGvqwuZopd4gYR1UGCi+Soyn/IjmafqbWs+ZC+3AKxJS3ZpwUg==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.19.0': + resolution: {integrity: sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==} + engines: {node: '>=18.0.0'} + + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + + '@speed-highlight/core@1.2.24': + resolution: {integrity: sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.10': + resolution: {integrity: sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@24.19.2': + resolution: {integrity: sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==} + + '@types/stream-buffers@3.0.8': + resolution: {integrity: sha512-J+7VaHKNvlNPJPEJXX/fKa9DZtR/xPMwuIbe+yNOwp1YB+ApUOBv2aUpEoBJEi8nJgbgs1x8e73ttg0r1rSUdw==} + + '@vitest/expect@4.1.12': + resolution: {integrity: sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ==} + + '@vitest/mocker@4.1.12': + resolution: {integrity: sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.12': + resolution: {integrity: sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA==} + + '@vitest/runner@4.1.12': + resolution: {integrity: sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w==} + + '@vitest/snapshot@4.1.12': + resolution: {integrity: sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw==} + + '@vitest/spy@4.1.12': + resolution: {integrity: sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg==} + + '@vitest/utils@4.1.12': + resolution: {integrity: sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA==} + + abort-controller-x@0.5.0: + resolution: {integrity: sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + aws4fetch@1.0.20: + resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==} + + axios@1.21.0: + resolution: {integrity: sha512-YCzXMB53Q9PZBsh7yN7wtapit4WepwcHS2fP8QhGsBxSz5YtgYobV7GGlWlAJi+RxfU77GQTQi2DSLUma42EYA==} + + b4a@1.9.0: + resolution: {integrity: sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + bare-events@2.9.2: + resolution: {integrity: sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.8.4: + resolution: {integrity: sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ==} + engines: {bare: '>=1.28.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-path@3.1.2: + resolution: {integrity: sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==} + + bare-stream@2.13.4: + resolution: {integrity: sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.5.5: + resolution: {integrity: sha512-iM9qJCruvpt2rPaLsrumVDru9pogI7PHziHIa9kTNRFt8C8HuSa+kI9kFezK+NvHEFnnz71rKGZ1EAilq/3V/Q==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + + bowser@2.15.0: + resolution: {integrity: sha512-xic6KSZglg/I4ymp45fdM4GWfYpBK79Fxd/TOvJktlaPgQqqDxvmGwOPZl2V0dyeS3Lj5c180bLhXF43iyAjZQ==} + + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + cbor-extract@2.2.3: + resolution: {integrity: sha512-zTOCEYwiQDChj5DV/sUqccv3jhdZqbhymztwcrW2LncIG2fPPkb1ceK5rmr0bdtxzJCXoWzZ95shA+uaSH8cKw==} + hasBin: true + + cbor-x@1.6.9: + resolution: {integrity: sha512-TCT9XHLb4iPGQ99fpQjcTRncbus6cXHvSD/RY+bPrDACBCJKiQLmAM6r7/BgQcI5i+8h4gwp4vjfb6S6T8bPGw==} + + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + cjs-module-lexer@2.3.0: + resolution: {integrity: sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + e2b@2.62.1: + resolution: {integrity: sha512-8TxPojt1659LmX9SX8zSet6fdefWxeSZoh+obad8DuC5wdRQdMbzN6ahEKNGzjrCNJFR26YeRZWOboXsy9UDYA==} + engines: {node: '>=22.19.0'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + engine.io-client@6.6.7: + resolution: {integrity: sha512-GyAvmPA7gnRCpDQbYACAy79Lg4ewevrmaPzySZz0OVvgyZc3gEfLLqPTYk8wVBScjOEpM9ZO4KryyFpVItjGEA==} + + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + es-module-lexer@3.0.4: + resolution: {integrity: sha512-BldNY5mwkVu+cQ5Ymn3e6hB9Sx8XHx4ZevWbiUyZbfKxJxYO27Hf0wfzkxIaD5CAJMaGl3YspKELj0ff/7nCMA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.3: + resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + follow-redirects@1.16.2: + resolution: {integrity: sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@14.0.3: + resolution: {integrity: sha512-A1vjIwC3qpdCso45Iw127gnp7sPhqgY8lUQhDrArIs+GbmfaaVPFuoyy7WyLT9EPZCRZ7j7aLdwUhQmHWesEow==} + engines: {node: 18 || 20 || >=22} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + + hpagent@1.2.0: + resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} + engines: {node: '>=14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + import-in-the-middle@3.5.3: + resolution: {integrity: sha512-usm/IiY63VtOyx/dm7uEoZV+EsanCDmwRUroQZIkFLGu1NVXZ8mZqIOhaN4QIJ7EsbMTuQbJXwCySCx+ozMA8g==} + engines: {node: '>=18'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.7.5: + resolution: {integrity: sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==} + engines: {node: '>= 12'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isomorphic-ws@5.0.0: + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} + peerDependencies: + ws: '*' + + jose@6.2.15: + resolution: {integrity: sha512-qdF6ewcZARtef63fJ9AKKZPgxw+t7USH6HLXYcD4CrdmSvDp79rpJSPFEKc90QBeGS63u1KQtN2MZQHC0yDIzA==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + jsep@1.6.0: + resolution: {integrity: sha512-Y8Tr54CX7B5YGAwo7/n8eumidEX3Opx98RyyQVnlHqUaZcPA8uUzL2v1rshr9ypW9Q6hNjfJtg3sL+8kaNE7TA==} + engines: {node: '>= 20.16.0'} + + jsonpath-plus@10.4.0: + resolution: {integrity: sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==} + engines: {node: '>=18.0.0'} + hasBin: true + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + miniflare@5.20261102.0-beta: + resolution: {integrity: sha512-FGqoi2BF7Vq6v8aSraq9Tmhg8ORpUBC5RkNGhfa11J597cWlLJ5QP9vJwDMOzwJx+W4U+IuHWSidVceBK6VRZQ==} + engines: {node: '>=22.0.0'} + + minimatch@11.0.4: + resolution: {integrity: sha512-/UJ1dEoVQTjPNDh6SGlUo+ueLMmoHTas4q+8yaAR/5t6nLAIiBDJtoYUh20IYleix7ehiQkgczb84PE3uzHkxg==} + engines: {node: 20 || >=22} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + modal@0.7.6: + resolution: {integrity: sha512-AOFRO/eGl4fcNKxHkNLx55+tL318IeAiTDJCMh/Q1ZXhoaZFnpmlirVV2J5BhO32XLA7AiH6KYGA7gRBGA09lQ==} + + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nice-grpc-common@2.0.4: + resolution: {integrity: sha512-gOEXlD6ShXZMZ8k+49wm/bA2j1+3IKbEFV9WYREJcvZV4kM5L1KkILYTX9VYBzZF2OuYCe1wp8c82bQkvR0fHw==} + + nice-grpc@2.1.18: + resolution: {integrity: sha512-m1if7HuVIhhVo/obvLgoSco2tj46zfeIEKkavlAnAsjklBzmFthvYX/uoKZmMKIwpVJllYpj+HVFRb+qGxibrA==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build-optional-packages@5.1.1: + resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} + hasBin: true + + novita-sandbox@1.0.3-b2: + resolution: {integrity: sha512-W0TbVfacreckxdbw1Tbh9dPYVZKjr4e0qRnMj/oxNj+gw01CRKA5Nf8uwSPaMI5daRazyo+ZVnV7WZchKhHbNQ==} + engines: {node: '>=16.0.0'} + + oauth4webapi@3.8.8: + resolution: {integrity: sha512-8N28E+a/oxfXWBgOMt+ZP/JUf/XR+IFbvkAEPP3gznXOMv9BpAAwiIj0TFNz3tGTPc0ZQ8zmWBNgN1nAys0gng==} + + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + openapi-fetch@0.17.0: + resolution: {integrity: sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig==} + + openapi-fetch@0.9.8: + resolution: {integrity: sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg==} + + openapi-typescript-helpers@0.0.8: + resolution: {integrity: sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g==} + + openapi-typescript-helpers@0.1.0: + resolution: {integrity: sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==} + + openid-client@6.8.9: + resolution: {integrity: sha512-6mXMRNJxDOf6BflEWLXul1UwzLFz++9Ofz1PYwPwwsJsyXBsbfkO+Jo255SW1qnFNLYz4EJWHz42X4OJJrya/g==} + + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + + path-scurry@3.0.0: + resolution: {integrity: sha512-1pTbQqH1wIwMi/eleQ5leFTTCkzFsPcgXNjwOA8YGGW/W4/kHT3dssE7Z6NH+zuqNLOzOB0uaD61yl7cAcVhgg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + + postcss@8.5.32: + resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==} + engines: {node: ^10 || ^12 || >=14} + + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfc4648@1.5.4: + resolution: {integrity: sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==} + + rollup@4.67.4: + resolution: {integrity: sha512-Lsv3oKcz5jgOQJej/p7nfwbVEdAbDT+z6OPf/SCdI+nF+q2ENp0pTKT35ZwXAmH8g45mfrIiJeEyAqKYSYoL9Q==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + semver@7.8.6: + resolution: {integrity: sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.35.5: + resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + shell-quote@1.12.0: + resolution: {integrity: sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + smol-toml@1.9.1: + resolution: {integrity: sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==} + engines: {node: '>= 18'} + + socket.io-client@4.8.4: + resolution: {integrity: sha512-7wjKAvVXYOJsbmUgkWbnhPD4bFpkqrUR0++XOjpGFmqDqsqV1CI93VTraeExLxpjxEqrWgFoSRZu1ihjLWfkAA==} + engines: {node: '>=10.0.0'} + + socket.io-parser@4.2.7: + resolution: {integrity: sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==} + engines: {node: '>=10.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.10: + resolution: {integrity: sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + + stream-buffers@3.0.3: + resolution: {integrity: sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==} + engines: {node: '>= 0.10.0'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + streamx@2.28.1: + resolution: {integrity: sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + tar-fs@3.1.3: + resolution: {integrity: sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==} + + tar-stream@3.2.2: + resolution: {integrity: sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==} + + tar@7.5.22: + resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==} + engines: {node: '>=18'} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-error@1.0.6: + resolution: {integrity: sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tslib@2.9.1: + resolution: {integrity: sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + undici@7.30.1: + resolution: {integrity: sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==} + engines: {node: '>=20.18.1'} + + undici@8.12.3: + resolution: {integrity: sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==} + engines: {node: '>=22.19.0'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} + hasBin: true + + vite@7.3.7: + resolution: {integrity: sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.12: + resolution: {integrity: sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.12 + '@vitest/browser-preview': 4.1.12 + '@vitest/browser-webdriverio': ^4.1.11 + '@vitest/coverage-istanbul': 4.1.12 + '@vitest/coverage-v8': 4.1.12 + '@vitest/ui': 4.1.12 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + workerd@1.20261102.1: + resolution: {integrity: sha512-5A+/vm0M5oc98pB3aR+DZ5cIOvRMhVWVS9tg+CrjUap7PIo8tqAnvNdvuiHBIE47/wkxtNM3jToAaU38dBsZTw==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.156.2: + resolution: {integrity: sha512-3RKI10rRqDHlqLFeCgCOmuPzoXw8npyGZbixYB5Vj5RW9jWPoSrupo8CzxCSkw1nVdvpzlFaRrHFjh4+AMt8Cw==} + engines: {node: '>=22.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^5.20261102.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.22.1: + resolution: {integrity: sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xmlhttprequest-ssl@2.1.2: + resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} + engines: {node: '>=0.4.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + + zod@4.6.7: + resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} + +snapshots: + + '@aws-sdk/checksums@3.1002.1': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1161.0': + dependencies: + '@aws-sdk/checksums': 3.1002.1 + '@aws-sdk/core': 3.979.1 + '@aws-sdk/credential-provider-node': 3.972.92 + '@aws-sdk/middleware-sdk-s3': 3.972.85 + '@aws-sdk/signature-v4-multi-region': 3.996.51 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.14.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/core@3.979.1': + dependencies: + '@aws-sdk/types': 3.975.0 + '@aws-sdk/xml-builder': 3.972.42 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.38.1 + '@smithy/signature-v4': 5.7.5 + '@smithy/types': 4.19.0 + bowser: 2.15.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.80': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.82': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.14.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.973.25': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/credential-provider-env': 3.972.80 + '@aws-sdk/credential-provider-http': 3.972.82 + '@aws-sdk/credential-provider-login': 3.972.87 + '@aws-sdk/credential-provider-process': 3.972.80 + '@aws-sdk/credential-provider-sso': 3.973.24 + '@aws-sdk/credential-provider-web-identity': 3.972.86 + '@aws-sdk/nested-clients': 3.997.54 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.87': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/nested-clients': 3.997.54 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.92': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.80 + '@aws-sdk/credential-provider-http': 3.972.82 + '@aws-sdk/credential-provider-ini': 3.973.25 + '@aws-sdk/credential-provider-process': 3.972.80 + '@aws-sdk/credential-provider-sso': 3.973.24 + '@aws-sdk/credential-provider-web-identity': 3.972.86 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.80': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.973.24': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/nested-clients': 3.997.54 + '@aws-sdk/token-providers': 3.1161.0 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.86': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/nested-clients': 3.997.54 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/lib-storage@3.1161.0(@aws-sdk/client-s3@3.1161.0)': + dependencies: + '@aws-sdk/client-s3': 3.1161.0 + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.85': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/signature-v4-multi-region': 3.996.51 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.54': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/signature-v4-multi-region': 3.996.51 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.14.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.51': + dependencies: + '@aws-sdk/types': 3.975.0 + '@smithy/signature-v4': 5.7.5 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1161.0': + dependencies: + '@aws-sdk/core': 3.979.1 + '@aws-sdk/nested-clients': 3.997.54 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/types@3.975.0': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.42': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.3.0': {} + + '@boxd-sh/sdk@0.2.22': + dependencies: + '@bufbuild/protobuf': 2.16.0 + '@grpc/grpc-js': 1.14.7 + + '@bufbuild/protobuf@2.16.0': {} + + '@cbor-extract/cbor-extract-darwin-arm64@2.2.3': + optional: true + + '@cbor-extract/cbor-extract-darwin-x64@2.2.3': + optional: true + + '@cbor-extract/cbor-extract-linux-arm64@2.2.3': + optional: true + + '@cbor-extract/cbor-extract-linux-arm@2.2.3': + optional: true + + '@cbor-extract/cbor-extract-linux-x64@2.2.3': + optional: true + + '@cbor-extract/cbor-extract-win32-x64@2.2.3': + optional: true + + '@cloudflare/containers@0.1.1': {} + + '@cloudflare/kv-asset-handler@0.5.0': {} + + '@cloudflare/sandbox@0.7.21': + dependencies: + '@cloudflare/containers': 0.1.1 + aws4fetch: 1.0.20 + + '@cloudflare/unenv-preset@2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261102.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20261102.1 + + '@cloudflare/workerd-darwin-64@1.20261102.1': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20261102.1': + optional: true + + '@cloudflare/workerd-linux-64@1.20261102.1': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20261102.1': + optional: true + + '@cloudflare/workerd-windows-64@1.20261102.1': + optional: true + + '@cloudflare/workers-types@4.20260702.1': {} + + '@connectrpc/connect-web@2.0.0-rc.3(@bufbuild/protobuf@2.16.0)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.16.0))': + dependencies: + '@bufbuild/protobuf': 2.16.0 + '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.16.0) + + '@connectrpc/connect-web@2.3.0(@bufbuild/protobuf@2.16.0)(@connectrpc/connect@2.3.0(@bufbuild/protobuf@2.16.0))': + dependencies: + '@bufbuild/protobuf': 2.16.0 + '@connectrpc/connect': 2.3.0(@bufbuild/protobuf@2.16.0) + + '@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.16.0)': + dependencies: + '@bufbuild/protobuf': 2.16.0 + + '@connectrpc/connect@2.3.0(@bufbuild/protobuf@2.16.0)': + dependencies: + '@bufbuild/protobuf': 2.16.0 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@daytona/analytics-api-client@0.203.0': + dependencies: + axios: 1.21.0 + transitivePeerDependencies: + - debug + - supports-color + + '@daytona/api-client@0.203.0': + dependencies: + axios: 1.21.0 + transitivePeerDependencies: + - debug + - supports-color + + '@daytona/toolbox-api-client@0.203.0': + dependencies: + axios: 1.21.0 + transitivePeerDependencies: + - debug + - supports-color + + '@daytonaio/sdk@0.203.0': + dependencies: + '@aws-sdk/client-s3': 3.1161.0 + '@aws-sdk/lib-storage': 3.1161.0(@aws-sdk/client-s3@3.1161.0) + '@daytona/analytics-api-client': 0.203.0 + '@daytona/api-client': 0.203.0 + '@daytona/toolbox-api-client': 0.203.0 + '@iarna/toml': 2.2.5 + '@opentelemetry/api': 1.10.0 + '@opentelemetry/exporter-trace-otlp-http': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/instrumentation-http': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-node': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace-base': 2.12.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + axios: 1.21.0 + busboy: 1.6.0 + dotenv: 17.4.2 + expand-tilde: 2.0.2 + fast-glob: 3.3.3 + form-data: 4.0.6 + isomorphic-ws: 5.0.0(ws@8.22.1) + pathe: 2.0.3 + shell-quote: 1.12.0 + socket.io-client: 4.8.4 + tar: 7.5.22 + tslib: 2.8.1 + ws: 8.22.1 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@e2b/dockerfile-utils@0.1.1': {} + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.9.1 + optional: true + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@grpc/grpc-js@1.14.7': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.6 + yargs: 17.7.3 + + '@iarna/toml@2.2.5': {} + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.4 + optional: true + + '@img/sharp-darwin-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.4 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + optional: true + + '@img/sharp-linux-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.4 + optional: true + + '@img/sharp-linux-arm@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.4 + optional: true + + '@img/sharp-linux-ppc64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.4 + optional: true + + '@img/sharp-linux-riscv64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.4 + optional: true + + '@img/sharp-linux-s390x@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.4 + optional: true + + '@img/sharp-linux-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + optional: true + + '@img/sharp-wasm32@0.35.5': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 + optional: true + + '@img/sharp-win32-arm64@0.35.5': + optional: true + + '@img/sharp-win32-ia32@0.35.5': + optional: true + + '@img/sharp-win32-x64@0.35.5': + optional: true + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + + '@isaacs/nonrecursive-glob-match@1.0.3': {} + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@jsep-plugin/assignment@1.4.0(jsep@1.6.0)': + dependencies: + jsep: 1.6.0 + + '@jsep-plugin/regex@1.1.0(jsep@1.6.0)': + dependencies: + jsep: 1.6.0 + + '@kubernetes/client-node@1.4.0': + dependencies: + '@types/js-yaml': 4.0.9 + '@types/node': 24.19.2 + '@types/node-fetch': 2.6.13 + '@types/stream-buffers': 3.0.8 + form-data: 4.0.6 + hpagent: 1.2.0 + isomorphic-ws: 5.0.0(ws@8.22.1) + js-yaml: 4.3.2 + jsonpath-plus: 10.4.0 + node-fetch: 2.7.0 + openid-client: 6.8.9 + rfc4648: 1.5.4 + socks-proxy-agent: 8.0.5 + stream-buffers: 3.0.3 + tar-fs: 3.1.3 + ws: 8.22.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - encoding + - react-native-b4a + - supports-color + - utf-8-validate + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.3 + + '@opentelemetry/api-logs@0.220.0': + dependencies: + '@opentelemetry/api': 1.10.0 + + '@opentelemetry/api@1.10.0': {} + + '@opentelemetry/configuration@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + yaml: 2.9.1 + + '@opentelemetry/context-async-hooks@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + + '@opentelemetry/core@2.12.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/core@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/exporter-logs-otlp-grpc@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@grpc/grpc-js': 1.14.7 + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 0.220.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-logs-otlp-http@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 0.220.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-logs-otlp-proto@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 0.220.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-metrics-otlp-grpc@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@grpc/grpc-js': 1.14.7 + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-metrics-otlp-http@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-metrics-otlp-proto@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-prometheus@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@grpc/grpc-js': 1.14.7 + '@opentelemetry/api': 1.10.0 + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-trace-otlp-http@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/exporter-zipkin@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/instrumentation-http@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/api-logs': 0.220.0 + import-in-the-middle: 3.5.3 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@grpc/grpc-js': 1.14.7 + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/otlp-transformer@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/propagator-b3@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/propagator-jaeger@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/resources@2.12.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.12.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/resources@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/sdk-logs@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/sdk-metrics@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/sdk-node@0.220.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/configuration': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/context-async-hooks': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-logs-otlp-http': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-metrics-otlp-proto': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-prometheus': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-trace-otlp-http': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/exporter-zipkin': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/propagator-b3': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/propagator-jaeger': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-logs': 0.220.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace-node': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace-base@2.12.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.12.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.12.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 2.12.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/sdk-trace-node@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/context-async-hooks': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.10.0) + + '@opentelemetry/sdk-trace@2.12.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.12.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.12.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/sdk-trace@2.9.0(@opentelemetry/api@1.10.0)': + dependencies: + '@opentelemetry/api': 1.10.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.10.0) + '@opentelemetry/semantic-conventions': 1.44.0 + + '@opentelemetry/semantic-conventions@1.44.0': {} + + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + + '@rollup/rollup-android-arm-eabi@4.67.4': + optional: true + + '@rollup/rollup-android-arm64@4.67.4': + optional: true + + '@rollup/rollup-android-x64@4.67.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.67.4': + optional: true + + '@rollup/rollup-darwin-x64@4.67.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.67.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.67.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.67.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.67.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.67.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.67.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.67.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.67.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.67.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.67.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.67.4': + optional: true + + '@sindresorhus/is@7.2.0': {} + + '@smithy/core@3.38.1': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.5.2': + dependencies: + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.8.0': + dependencies: + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.14.1': + dependencies: + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.7.5': + dependencies: + '@smithy/core': 3.38.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/types@4.19.0': + dependencies: + tslib: 2.8.1 + + '@socket.io/component-emitter@3.1.2': {} + + '@speed-highlight/core@1.2.24': {} + + '@standard-schema/spec@1.1.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.10': {} + + '@types/js-yaml@4.0.9': {} + + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 24.19.2 + form-data: 4.0.6 + + '@types/node@24.19.2': + dependencies: + undici-types: 7.24.6 + + '@types/stream-buffers@3.0.8': + dependencies: + '@types/node': 24.19.2 + + '@vitest/expect@4.1.12': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.12 + '@vitest/utils': 4.1.12 + chai: 6.3.0 + tinyrainbow: 3.2.0 + + '@vitest/mocker@4.1.12(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1))': + dependencies: + '@vitest/spy': 4.1.12 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.7(@types/node@24.19.2)(yaml@2.9.1) + + '@vitest/pretty-format@4.1.12': + dependencies: + tinyrainbow: 3.2.0 + + '@vitest/runner@4.1.12': + dependencies: + '@vitest/utils': 4.1.12 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.12': + dependencies: + '@vitest/pretty-format': 4.1.12 + '@vitest/utils': 4.1.12 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.12': {} + + '@vitest/utils@4.1.12': + dependencies: + '@vitest/pretty-format': 4.1.12 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 + + abort-controller-x@0.5.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + asynckit@0.4.0: {} + + aws4fetch@1.0.20: {} + + axios@1.21.0: + dependencies: + follow-redirects: 1.16.2 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + b4a@1.9.0: {} + + balanced-match@4.0.4: {} + + bare-events@2.9.2: {} + + bare-fs@4.8.4: + dependencies: + bare-events: 2.9.2 + bare-path: 3.1.2 + bare-stream: 2.13.4(bare-events@2.9.2) + bare-url: 2.5.5 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-path@3.1.2: {} + + bare-stream@2.13.4(bare-events@2.9.2): + dependencies: + b4a: 1.9.0 + streamx: 2.28.1 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.5.5: + dependencies: + bare-path: 3.1.2 + + base64-js@1.5.1: {} + + blake3-wasm@2.1.5: {} + + bowser@2.15.0: {} + + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer@5.6.0: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + cbor-extract@2.2.3: + dependencies: + node-gyp-build-optional-packages: 5.1.1 + optionalDependencies: + '@cbor-extract/cbor-extract-darwin-arm64': 2.2.3 + '@cbor-extract/cbor-extract-darwin-x64': 2.2.3 + '@cbor-extract/cbor-extract-linux-arm': 2.2.3 + '@cbor-extract/cbor-extract-linux-arm64': 2.2.3 + '@cbor-extract/cbor-extract-linux-x64': 2.2.3 + '@cbor-extract/cbor-extract-win32-x64': 2.2.3 + optional: true + + cbor-x@1.6.9: + optionalDependencies: + cbor-extract: 2.2.3 + + chai@6.3.0: {} + + chalk@5.6.2: {} + + chownr@3.0.0: {} + + cjs-module-lexer@2.3.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + compare-versions@6.1.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + detect-libc@2.1.2: {} + + dotenv@17.4.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + e2b@2.62.1: + dependencies: + '@bufbuild/protobuf': 2.16.0 + '@connectrpc/connect': 2.3.0(@bufbuild/protobuf@2.16.0) + '@connectrpc/connect-web': 2.3.0(@bufbuild/protobuf@2.16.0)(@connectrpc/connect@2.3.0(@bufbuild/protobuf@2.16.0)) + '@e2b/dockerfile-utils': 0.1.1 + chalk: 5.6.2 + compare-versions: 6.1.1 + glob: 14.0.3 + openapi-fetch: 0.17.0 + platform: 1.3.6 + tar: 7.5.22 + undici: 8.12.3 + + emoji-regex@8.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + engine.io-client@6.6.7: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.4.3 + engine.io-parser: 5.2.3 + ws: 8.21.3 + xmlhttprequest-ssl: 2.1.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@5.2.3: {} + + error-stack-parser-es@1.0.5: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.3.2: {} + + es-module-lexer@3.0.4: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.10 + + events-universal@1.0.1: + dependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + + expect-type@1.4.0: {} + + fast-fifo@1.3.2: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.3: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + follow-redirects@1.16.2: {} + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + forwarded-parse@2.1.2: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@14.0.3: + dependencies: + minimatch: 11.0.4 + minipass: 7.1.3 + path-scurry: 3.0.0 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + hpagent@1.2.0: {} + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ieee754@1.2.1: {} + + import-in-the-middle@3.5.3: + dependencies: + cjs-module-lexer: 2.3.0 + es-module-lexer: 3.0.4 + module-details-from-path: 1.0.4 + + inherits@2.0.4: {} + + ip-address@10.7.5: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isomorphic-ws@5.0.0(ws@8.22.1): + dependencies: + ws: 8.22.1 + + jose@6.2.15: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + jsep@1.6.0: {} + + jsonpath-plus@10.4.0: + dependencies: + '@jsep-plugin/assignment': 1.4.0(jsep@1.6.0) + '@jsep-plugin/regex': 1.1.0(jsep@1.6.0) + jsep: 1.6.0 + + kleur@4.1.5: {} + + lodash.camelcase@4.3.0: {} + + long@5.3.2: {} + + lru-cache@11.5.3: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + miniflare@5.20261102.0-beta(@types/node@24.19.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.35.5(@types/node@24.19.2) + undici: 7.30.1 + workerd: 1.20261102.1 + ws: 8.21.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - '@types/node' + - bufferutil + - utf-8-validate + + minimatch@11.0.4: + dependencies: + '@isaacs/nonrecursive-glob-match': 1.0.3 + brace-expansion: 5.0.12 + + minipass@7.1.3: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + + modal@0.7.6: + dependencies: + cbor-x: 1.6.9 + long: 5.3.2 + nice-grpc: 2.1.18 + protobufjs: 7.6.6 + smol-toml: 1.9.1 + uuid: 11.1.1 + + module-details-from-path@1.0.4: {} + + ms@2.1.3: {} + + nanoid@3.3.20: {} + + nice-grpc-common@2.0.4: + dependencies: + ts-error: 1.0.6 + + nice-grpc@2.1.18: + dependencies: + '@grpc/grpc-js': 1.14.7 + abort-controller-x: 0.5.0 + nice-grpc-common: 2.0.4 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-gyp-build-optional-packages@5.1.1: + dependencies: + detect-libc: 2.1.2 + optional: true + + novita-sandbox@1.0.3-b2: + dependencies: + '@bufbuild/protobuf': 2.16.0 + '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.16.0) + '@connectrpc/connect-web': 2.0.0-rc.3(@bufbuild/protobuf@2.16.0)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.16.0)) + compare-versions: 6.1.1 + openapi-fetch: 0.9.8 + platform: 1.3.6 + + oauth4webapi@3.8.8: {} + + obug@2.2.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + openapi-fetch@0.17.0: + dependencies: + openapi-typescript-helpers: 0.1.0 + + openapi-fetch@0.9.8: + dependencies: + openapi-typescript-helpers: 0.0.8 + + openapi-typescript-helpers@0.0.8: {} + + openapi-typescript-helpers@0.1.0: {} + + openid-client@6.8.9: + dependencies: + jose: 6.2.15 + oauth4webapi: 3.8.8 + + parse-passwd@1.0.0: {} + + path-scurry@3.0.0: + dependencies: + lru-cache: 11.5.3 + minipass: 7.1.3 + + path-to-regexp@6.3.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.7: {} + + platform@1.3.6: {} + + postcss@8.5.32: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + protobufjs@7.6.6: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 24.19.2 + long: 5.3.2 + + proxy-from-env@2.1.0: {} + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + queue-microtask@1.2.3: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + require-directory@2.1.1: {} + + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + + reusify@1.1.0: {} + + rfc4648@1.5.4: {} + + rollup@4.67.4: + dependencies: + '@types/estree': 1.0.10 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.67.4 + '@rollup/rollup-android-arm64': 4.67.4 + '@rollup/rollup-android-x64': 4.67.4 + '@rollup/rollup-darwin-arm64': 4.67.4 + '@rollup/rollup-darwin-x64': 4.67.4 + '@rollup/rollup-freebsd-arm64': 4.67.4 + '@rollup/rollup-freebsd-x64': 4.67.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.67.4 + '@rollup/rollup-linux-arm-musleabihf': 4.67.4 + '@rollup/rollup-linux-arm64-gnu': 4.67.4 + '@rollup/rollup-linux-arm64-musl': 4.67.4 + '@rollup/rollup-linux-loong64-gnu': 4.67.4 + '@rollup/rollup-linux-loong64-musl': 4.67.4 + '@rollup/rollup-linux-ppc64-gnu': 4.67.4 + '@rollup/rollup-linux-ppc64-musl': 4.67.4 + '@rollup/rollup-linux-riscv64-gnu': 4.67.4 + '@rollup/rollup-linux-riscv64-musl': 4.67.4 + '@rollup/rollup-linux-s390x-gnu': 4.67.4 + '@rollup/rollup-linux-x64-gnu': 4.67.4 + '@rollup/rollup-linux-x64-musl': 4.67.4 + '@rollup/rollup-openbsd-x64': 4.67.4 + '@rollup/rollup-openharmony-arm64': 4.67.4 + '@rollup/rollup-win32-arm64-msvc': 4.67.4 + '@rollup/rollup-win32-ia32-msvc': 4.67.4 + '@rollup/rollup-win32-x64-gnu': 4.67.4 + '@rollup/rollup-win32-x64-msvc': 4.67.4 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + semver@7.8.6: {} + + sharp@0.35.5(@types/node@24.19.2): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.6 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.5 + '@img/sharp-darwin-x64': 0.35.5 + '@img/sharp-freebsd-wasm32': 0.35.5 + '@img/sharp-libvips-darwin-arm64': 1.3.4 + '@img/sharp-libvips-darwin-x64': 1.3.4 + '@img/sharp-libvips-linux-arm': 1.3.4 + '@img/sharp-libvips-linux-arm64': 1.3.4 + '@img/sharp-libvips-linux-ppc64': 1.3.4 + '@img/sharp-libvips-linux-riscv64': 1.3.4 + '@img/sharp-libvips-linux-s390x': 1.3.4 + '@img/sharp-libvips-linux-x64': 1.3.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + '@img/sharp-linux-arm': 0.35.5 + '@img/sharp-linux-arm64': 0.35.5 + '@img/sharp-linux-ppc64': 0.35.5 + '@img/sharp-linux-riscv64': 0.35.5 + '@img/sharp-linux-s390x': 0.35.5 + '@img/sharp-linux-x64': 0.35.5 + '@img/sharp-linuxmusl-arm64': 0.35.5 + '@img/sharp-linuxmusl-x64': 0.35.5 + '@img/sharp-webcontainers-wasm32': 0.35.5 + '@img/sharp-win32-arm64': 0.35.5 + '@img/sharp-win32-ia32': 0.35.5 + '@img/sharp-win32-x64': 0.35.5 + '@types/node': 24.19.2 + + shell-quote@1.12.0: {} + + siginfo@2.0.0: {} + + smart-buffer@4.2.0: {} + + smol-toml@1.9.1: {} + + socket.io-client@4.8.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.4.3 + engine.io-client: 6.6.7 + socket.io-parser: 4.2.7 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.7: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.10 + transitivePeerDependencies: + - supports-color + + socks@2.8.10: + dependencies: + ip-address: 10.7.5 + smart-buffer: 4.2.0 + + source-map-js@1.2.2: {} + + stackback@0.0.2: {} + + std-env@4.3.0: {} + + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + + stream-buffers@3.0.3: {} + + streamsearch@1.1.0: {} + + streamx@2.28.1: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + supports-color@10.2.2: {} + + tar-fs@3.1.3: + dependencies: + pump: 3.0.4 + tar-stream: 3.2.2 + optionalDependencies: + bare-fs: 4.8.4 + bare-path: 3.1.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@3.2.2: + dependencies: + b4a: 1.9.0 + bare-fs: 4.8.4 + fast-fifo: 1.3.2 + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar@7.5.22: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + + teex@1.0.1: + dependencies: + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.7: + dependencies: + b4a: 1.9.0 + transitivePeerDependencies: + - react-native-b4a + + tinybench@2.9.0: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinyrainbow@3.2.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + ts-error@1.0.6: {} + + tslib@2.8.1: {} + + tslib@2.9.1: + optional: true + + typescript@5.9.3: {} + + undici-types@7.24.6: {} + + undici@7.30.1: {} + + undici@8.12.3: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + + util-deprecate@1.0.2: {} + + uuid@11.1.1: {} + + vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1): + dependencies: + esbuild: 0.28.2 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + postcss: 8.5.32 + rollup: 4.67.4 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.19.2 + fsevents: 2.3.3 + yaml: 2.9.1 + + vitest@4.1.12(@opentelemetry/api@1.10.0)(@types/node@24.19.2)(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)): + dependencies: + '@vitest/expect': 4.1.12 + '@vitest/mocker': 4.1.12(vite@7.3.7(@types/node@24.19.2)(yaml@2.9.1)) + '@vitest/pretty-format': 4.1.12 + '@vitest/runner': 4.1.12 + '@vitest/snapshot': 4.1.12 + '@vitest/spy': 4.1.12 + '@vitest/utils': 4.1.12 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.2.1 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 2.9.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + tinyrainbow: 3.2.0 + vite: 7.3.7(@types/node@24.19.2)(yaml@2.9.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.10.0 + '@types/node': 24.19.2 + transitivePeerDependencies: + - msw + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + workerd@1.20261102.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20261102.1 + '@cloudflare/workerd-darwin-arm64': 1.20261102.1 + '@cloudflare/workerd-linux-64': 1.20261102.1 + '@cloudflare/workerd-linux-arm64': 1.20261102.1 + '@cloudflare/workerd-windows-64': 1.20261102.1 + + wrangler@4.156.2(@cloudflare/workers-types@4.20260702.1)(@types/node@24.19.2): + dependencies: + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261102.1) + '@cspotcode/source-map-support': 0.8.1 + blake3-wasm: 2.1.5 + esbuild: 0.28.2 + miniflare: 5.20261102.0-beta(@types/node@24.19.2) + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20261102.1 + optionalDependencies: + '@cloudflare/workers-types': 4.20260702.1 + fsevents: 2.3.3 + transitivePeerDependencies: + - '@types/node' + - bufferutil + - utf-8-validate + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@8.21.0: {} + + ws@8.21.3: {} + + ws@8.22.1: {} + + xmlhttprequest-ssl@2.1.2: {} + + y18n@5.0.8: {} + + yallist@5.0.0: {} + + yaml@2.9.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.24 + cookie: 1.1.1 + youch-core: 0.3.3 + + zod@4.6.7: {} diff --git a/scripts/typecheck-sandbox-providers/pnpm-workspace.yaml b/scripts/typecheck-sandbox-providers/pnpm-workspace.yaml new file mode 100644 index 00000000000..8a7199ba45e --- /dev/null +++ b/scripts/typecheck-sandbox-providers/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +# Sandbox providers stay out of the root workspace, and most of them must not +# commit a lockfile: scripts/build-standalone-public-packages.mjs uses a frozen +# install when one exists. This workspace pins their dependencies in one lockfile +# for scripts/typecheck-test-programs.mjs, without changing how they are released. +packages: + - ../../packages/plugins/sandbox-providers/* + - ../../packages/plugins/sandbox-providers/cloudflare/bridge-template diff --git a/scripts/typecheck-test-programs.mjs b/scripts/typecheck-test-programs.mjs new file mode 100644 index 00000000000..562efef0939 --- /dev/null +++ b/scripts/typecheck-test-programs.mjs @@ -0,0 +1,220 @@ +#!/usr/bin/env node +// Typechecks the test files that no build or package typecheck reads. +// +// Most package tsconfigs exclude tests so that `tsc` does not emit them, and +// Vitest strips types, so a type error in those tests fails nothing. Each program +// in PROGRAMS reads them without emitting. Sandbox providers are outside the +// workspace, so the root install has none of their dependencies. They install from +// typecheck-sandbox-providers/pnpm-lock.yaml here, because their SDK types are part +// of what a test checks. +// +// The script then fails if a tracked test file is in no program. tsc decides that, +// not a glob: `include` silently drops foo.test.tsx when foo.test.ts exists. +// +// Usage: node scripts/typecheck-test-programs.mjs [--low-memory] [tsconfig ...] +// Naming a tsconfig runs only that program and skips the coverage check. + +import { spawnSync } from "node:child_process"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { linkExcludedPlugins } from "./link-plugin-dev-sdk.mjs"; +import { LOW_MEMORY_VARIABLE, runTsc } from "./run-tsc.mjs"; +import { PROJECTS } from "./typecheck-test-projects.mjs"; + +export const PROVIDER_WORKSPACE = "scripts/typecheck-sandbox-providers"; + +export const SANDBOX_PROVIDERS = [ + "packages/plugins/sandbox-providers/boxd", + "packages/plugins/sandbox-providers/cloudflare", + "packages/plugins/sandbox-providers/cloudflare/bridge-template", + "packages/plugins/sandbox-providers/createos", + "packages/plugins/sandbox-providers/daytona", + "packages/plugins/sandbox-providers/e2b", + "packages/plugins/sandbox-providers/exe-dev", + "packages/plugins/sandbox-providers/kubernetes", + "packages/plugins/sandbox-providers/modal", + "packages/plugins/sandbox-providers/novita", +]; + +export const PROGRAMS = [ + "packages/db/tsconfig.test.json", + "packages/kv-demo-mcp-server/tsconfig.test.json", + "packages/paperclip-runner/tsconfig.browser.test.json", + "packages/paperclip-runner/tsconfig.surfaces.test.json", + "packages/paperclip-runner/tsconfig.test.json", + "packages/plugins/examples/plugin-orchestration-smoke-example/tsconfig.test.json", + "packages/plugins/plugin-workspace-diff/tsconfig.test.json", + "packages/plugins/sdk/tsconfig.test.json", + ...SANDBOX_PROVIDERS.map((directory) => `${directory}/tsconfig.test.json`), + "tests/runner-acceptance/tsconfig.json", + "tests/tsconfig.playwright.json", +]; + +// server/scripts/typecheck-layers.mjs reads this tree. Everything else must be in a program. +export const CHECKED_ELSEWHERE = ["server/src/"]; + +const PREPARE_STEPS = [ + ["--filter", "@paperclipai/plugin-sdk", "ensure-build-deps"], + ["--filter", "@paperclipai/paperclip-runner", "run", "ensure:eval-build-deps"], +]; +// Replacing a provider's standalone node_modules would otherwise ask for confirmation. +const INSTALL_ARGS = ["install", "--frozen-lockfile", "--ignore-scripts", "--prefer-offline", "--config.confirmModulesPurge=false"]; + +export function findUncheckedTests({ trackedFiles, checkedFiles, checkedElsewhere = CHECKED_ELSEWHERE }) { + const checked = new Set(checkedFiles); + return trackedFiles.filter( + (file) => + /\.(test|spec)\.tsx?$/.test(file) && + !checked.has(file) && + !checkedElsewhere.some((prefix) => file.startsWith(prefix)), + ); +} + +function runPnpm(args, { cwd, env }) { + const result = spawnSync("pnpm", args, { + cwd, + env, + stdio: "inherit", + // A bare `pnpm` on Windows is pnpm.cmd, which needs the shell to resolve. + shell: process.platform === "win32", + }); + if (result.error) throw result.error; + return result.status ?? 1; +} + +function listProgramFiles(config, { cwd, env }) { + const tsc = path.join(cwd, "node_modules", "typescript", "bin", "tsc"); + const result = spawnSync(process.execPath, [tsc, "-p", config, "--listFilesOnly"], { + cwd, + env, + encoding: "utf8", + maxBuffer: 1 << 28, + }); + if (result.error) throw result.error; + return { + status: result.status ?? 1, + output: result.stdout, + files: result.stdout + .split("\n") + .filter(Boolean) + .map((file) => path.relative(cwd, file).split(path.sep).join("/")), + }; +} + +function listWorkspaceConfigs(root) { + const result = spawnSync("pnpm", ["ls", "-r", "--depth", "-1", "--json"], { + cwd: root, + encoding: "utf8", + maxBuffer: 1 << 26, + shell: process.platform === "win32", + }); + if (result.error) throw result.error; + if (result.status !== 0) throw new Error("Unable to list pnpm workspace packages."); + return JSON.parse(result.stdout) + .map((workspace) => path.relative(root, path.join(workspace.path, "tsconfig.json")).split(path.sep).join("/")) + .filter((config) => config !== "server/tsconfig.json" && existsSync(path.join(root, config))); +} + +function listTrackedFiles(root) { + const result = spawnSync("git", ["ls-files", "-z"], { cwd: root, encoding: "utf8", maxBuffer: 1 << 28 }); + if (result.error) throw result.error; + if (result.status !== 0) throw new Error("Unable to list tracked files."); + return result.stdout.split("\0").filter(Boolean); +} + +/** Returns the test files in no program, or null if a program could not be listed. */ +export function checkCoverage({ + root, + env = process.env, + programs = PROGRAMS, + workspaceConfigs = listWorkspaceConfigs, + trackedFiles = listTrackedFiles, + listFiles = listProgramFiles, + logError = console.error, +}) { + const checkedFiles = []; + // `pnpm build` and `typecheck:build-gaps` check each workspace's tsconfig.json. + // scripts/typecheck-test-projects.mjs checks these same files through tsconfig.layers.json. + const projectConfigs = PROJECTS.map((project) => `${project}/tsconfig.json`); + for (const config of [...workspaceConfigs(root), ...projectConfigs, ...programs]) { + const listing = listFiles(config, { cwd: root, env }); + if (listing.status !== 0) { + logError(`typecheck:test-programs: could not list the files of ${config}\n${listing.output ?? ""}`); + return null; + } + checkedFiles.push(...listing.files); + } + return findUncheckedTests({ trackedFiles: trackedFiles(root), checkedFiles }); +} + +export function typecheckTestPrograms({ + root, + env = process.env, + programs = PROGRAMS, + coverage = true, + pnpm = runPnpm, + linkPlugins = linkExcludedPlugins, + run = runTsc, + checkFiles = checkCoverage, + log = console.log, + logError = console.error, +}) { + for (const args of PREPARE_STEPS) { + const status = pnpm(args, { cwd: root, env }); + if (status !== 0) return status; + } + const installed = pnpm(INSTALL_ARGS, { cwd: path.join(root, PROVIDER_WORKSPACE), env }); + if (installed !== 0) { + logError( + `typecheck:test-programs: if a provider's package.json changed, run ` + + `\`pnpm install --ignore-scripts --lockfile-only\` in ${PROVIDER_WORKSPACE} and commit its pnpm-lock.yaml`, + ); + return installed; + } + // The install removes the SDK link from each provider's node_modules. + linkPlugins(); + + let exitCode = 0; + for (const config of programs) { + log(`typecheck:test-programs: ${config}`); + const { status, signal } = run(["-p", config], { cwd: root, env }); + if (signal) return status || 1; + if (status !== 0) exitCode = status; + } + if (!coverage) return exitCode; + + const unchecked = checkFiles({ root, env, logError }); + if (unchecked === null) return exitCode || 1; + if (unchecked.length > 0) { + logError( + `typecheck:test-programs: ${unchecked.length} test files are in no typechecked program. ` + + `Add them to a program in scripts/typecheck-test-programs.mjs. A foo.test.tsx beside a foo.test.ts needs a new name.\n` + + unchecked.map((file) => ` ${file}`).join("\n"), + ); + return exitCode || 1; + } + return exitCode; +} + +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const args = process.argv.slice(2); + const flags = args.filter((arg) => arg.startsWith("--")); + const programs = args.filter((arg) => !arg.startsWith("--")); + const unknownFlags = flags.filter((flag) => flag !== "--low-memory"); + const unknown = programs.filter((program) => !PROGRAMS.includes(program)); + if (unknownFlags.length > 0) { + console.error(`Unknown option: ${unknownFlags.join(", ")}`); + process.exitCode = 2; + } else if (unknown.length > 0) { + console.error(`Unknown program: ${unknown.join(", ")}. Known programs:\n ${PROGRAMS.join("\n ")}`); + process.exitCode = 2; + } else { + process.exitCode = typecheckTestPrograms({ + root: path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."), + env: flags.includes("--low-memory") ? { ...process.env, [LOW_MEMORY_VARIABLE]: "1" } : process.env, + programs: programs.length > 0 ? programs : PROGRAMS, + coverage: programs.length === 0, + }); + } +} diff --git a/scripts/typecheck-test-programs.test.mjs b/scripts/typecheck-test-programs.test.mjs new file mode 100644 index 00000000000..52816e4fbdb --- /dev/null +++ b/scripts/typecheck-test-programs.test.mjs @@ -0,0 +1,170 @@ +import assert from "node:assert/strict"; +import { execFileSync, spawnSync } from "node:child_process"; +import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { test } from "node:test"; +import { fileURLToPath } from "node:url"; +import { + checkCoverage, + findUncheckedTests, + PROGRAMS, + PROVIDER_WORKSPACE, + SANDBOX_PROVIDERS, + typecheckTestPrograms, +} from "./typecheck-test-programs.mjs"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const tsc = path.join(root, "node_modules", "typescript", "bin", "tsc"); + +const harness = (overrides = {}) => { + const calls = { pnpm: [], tsc: [], linked: 0, errors: [] }; + return { + calls, + options: { + root: "/repo", + programs: ["a/tsconfig.test.json", "b/tsconfig.test.json"], + pnpm: (args, { cwd }) => { + calls.pnpm.push({ args, cwd }); + return 0; + }, + linkPlugins: () => { + calls.linked += 1; + }, + run: (args) => { + calls.tsc.push(args); + return { status: 0 }; + }, + checkFiles: () => [], + log: () => {}, + logError: (message) => calls.errors.push(message), + ...overrides, + }, + }; +}; + +test("findUncheckedTests reports only test files outside programs and other checks", () => { + assert.deepEqual( + findUncheckedTests({ + trackedFiles: ["a/x.test.ts", "a/y.spec.tsx", "a/z.test.tsx", "a/helper.ts", "a/n.test.mjs", "server/src/s.test.ts"], + checkedFiles: ["a/x.test.ts", "a/y.spec.tsx"], + checkedElsewhere: ["server/src/"], + }), + ["a/z.test.tsx"], + ); +}); + +test("every program exists, and the provider list matches the tracked provider packages", () => { + for (const program of PROGRAMS) assert.ok(existsSync(path.join(root, program)), program); + assert.ok(existsSync(path.join(root, PROVIDER_WORKSPACE, "pnpm-lock.yaml"))); + const tracked = execFileSync("git", ["ls-files", "packages/plugins/sandbox-providers"], { cwd: root, encoding: "utf8" }); + const manifests = tracked.split("\n").filter((file) => file.endsWith("/package.json")); + assert.deepEqual(manifests.map((manifest) => path.dirname(manifest)).sort(), [...SANDBOX_PROVIDERS].sort()); +}); + +test("no provider commits a lockfile, except the one Daytona already has", () => { + const tracked = execFileSync("git", ["ls-files", "packages/plugins/sandbox-providers"], { cwd: root, encoding: "utf8" }); + assert.deepEqual( + tracked.split("\n").filter((file) => file.endsWith("/pnpm-lock.yaml")), + ["packages/plugins/sandbox-providers/daytona/pnpm-lock.yaml"], + ); +}); + +test("a type error in one program still checks the rest and fails the run", () => { + const { options, calls } = harness({ + run: (args) => { + calls.tsc.push(args); + return { status: args[1].startsWith("a/") ? 2 : 0 }; + }, + }); + assert.equal(typecheckTestPrograms(options), 2); + assert.deepEqual(calls.tsc, [ + ["-p", "a/tsconfig.test.json"], + ["-p", "b/tsconfig.test.json"], + ]); +}); + +test("a killed compiler stops the run", () => { + const { options, calls } = harness({ + run: (args) => { + calls.tsc.push(args); + return { status: null, signal: "SIGKILL" }; + }, + }); + assert.equal(typecheckTestPrograms(options), 1); + assert.equal(calls.tsc.length, 1); +}); + +test("providers install frozen without scripts, then relink the SDK", () => { + const { options, calls } = harness(); + assert.equal(typecheckTestPrograms(options), 0); + const install = calls.pnpm.find(({ cwd }) => cwd === path.join("/repo", PROVIDER_WORKSPACE)); + assert.ok(install.args.includes("--frozen-lockfile")); + assert.ok(install.args.includes("--ignore-scripts")); + assert.equal(calls.linked, 1); +}); + +test("a stale provider lockfile fails before any program runs and names the fix", () => { + const { options, calls } = harness({ + pnpm: (args, { cwd }) => (cwd === path.join("/repo", PROVIDER_WORKSPACE) ? 1 : 0), + }); + assert.equal(typecheckTestPrograms(options), 1); + assert.equal(calls.tsc.length, 0); + assert.match(calls.errors[0], /pnpm-lock\.yaml/); +}); + +test("an uncovered test file fails the run and is named", () => { + const { options, calls } = harness({ checkFiles: () => ["a/z.test.tsx"] }); + assert.equal(typecheckTestPrograms(options), 1); + assert.match(calls.errors[0], /a\/z\.test\.tsx/); +}); + +test("the coverage check fails closed when a program cannot be listed", () => { + const { options } = harness({ checkFiles: () => null }); + assert.equal(typecheckTestPrograms(options), 1); +}); + +test("naming programs skips the coverage check", () => { + let checked = false; + const { options } = harness({ + coverage: false, + checkFiles: () => { + checked = true; + return []; + }, + }); + assert.equal(typecheckTestPrograms(options), 0); + assert.equal(checked, false); +}); + +test("checkCoverage uses the compiler's file list, so a dropped sibling is caught", () => { + const directory = mkdtempSync(path.join(root, "node_modules", ".typecheck-test-programs-")); + try { + const relative = path.relative(root, directory).split(path.sep).join("/"); + writeFileSync(path.join(directory, "foo.test.ts"), "export {};\n"); + writeFileSync(path.join(directory, "foo.test.tsx"), "export {};\n"); + writeFileSync(path.join(directory, "tsconfig.json"), JSON.stringify({ compilerOptions: { noEmit: true, jsx: "preserve" }, include: ["*"] })); + const unchecked = checkCoverage({ + root, + programs: [`${relative}/tsconfig.json`], + workspaceConfigs: () => [], + trackedFiles: () => [`${relative}/foo.test.ts`, `${relative}/foo.test.tsx`], + }); + assert.deepEqual(unchecked, [`${relative}/foo.test.tsx`]); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +}); + +test("a real type error in a test-style program makes tsc exit non-zero", () => { + const directory = mkdtempSync(path.join(tmpdir(), "typecheck-test-programs-")); + try { + writeFileSync(path.join(directory, "bad.test.ts"), "export const value: number = 'text';\n"); + writeFileSync(path.join(directory, "tsconfig.json"), JSON.stringify({ compilerOptions: { noEmit: true, types: [] }, include: ["*.ts"] })); + const { status, stdout } = spawnSync(process.execPath, [tsc, "-p", directory, "--pretty", "false"], { encoding: "utf8" }); + assert.notEqual(status, 0); + assert.match(stdout, /bad\.test\.ts.*TS2322/); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +}); diff --git a/scripts/typecheck-test-projects.mjs b/scripts/typecheck-test-projects.mjs index 8fd8cef014f..0c6d537b216 100644 --- a/scripts/typecheck-test-projects.mjs +++ b/scripts/typecheck-test-projects.mjs @@ -22,6 +22,7 @@ import { isTestFile, typecheckLayers } from "../server/scripts/typecheck-layers. export const PROJECTS = [ "server/src/__tests__/helpers/maintenance", + "tests/e2e", "tests/lifecycle-baseline", "tests/runner-e2e", "tests/speko-native", diff --git a/tests/ai-connections-app/playwright.config.ts b/tests/ai-connections-app/playwright.config.ts index 4d2c4748141..3901f6a5cb6 100644 --- a/tests/ai-connections-app/playwright.config.ts +++ b/tests/ai-connections-app/playwright.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ use: { baseURL: process.env.AI_CONNECTIONS_TEST_URL ?? "http://127.0.0.1:3100", browserName: "chromium", - reducedMotion: "reduce", + contextOptions: { reducedMotion: "reduce" }, screenshot: "only-on-failure", }, }); diff --git a/tests/ai-connections-review/playwright.config.ts b/tests/ai-connections-review/playwright.config.ts index 75b89da5f85..48896eaddad 100644 --- a/tests/ai-connections-review/playwright.config.ts +++ b/tests/ai-connections-review/playwright.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ use: { baseURL: "http://localhost:6116", browserName: "chromium", - reducedMotion: "reduce", + contextOptions: { reducedMotion: "reduce" }, }, webServer: { command: "node ../../scripts/serve-storybook-static.mjs --port 6116", diff --git a/tests/e2e/acp-stop-continuation.spec.ts b/tests/e2e/acp-stop-continuation.spec.ts index de927420a86..9b9037d027c 100644 --- a/tests/e2e/acp-stop-continuation.spec.ts +++ b/tests/e2e/acp-stop-continuation.spec.ts @@ -47,7 +47,7 @@ for (const { unfinishedWrite, stopResponse, sendSavedQueue = false } of [ .toContain("List my recent Drive files."); // Both actions stop the response; composer Stop must not create a task hold. - let stopped; + let stopped!: { status: string; resultJson?: { executionCancellation?: { state?: string } } | null }; if (stopResponse) { await page.getByRole("button", { name: "Stop", exact: true }).click(); } else { @@ -58,7 +58,7 @@ for (const { unfinishedWrite, stopResponse, sendSavedQueue = false } of [ return stopped.resultJson?.executionCancellation?.state; }, { timeout: 30_000 }).toBe("acknowledged"); expect(stopped.status).toBe("cancelled"); - expect(stopped.resultJson.executionCancellation.state).toBe("acknowledged"); + expect(stopped.resultJson?.executionCancellation?.state).toBe("acknowledged"); const writesAtStop = unfinishedWrite ? await readFile(path.join(root, "writes"), "utf8") : null; await page.reload(); if (stopResponse) { diff --git a/tests/e2e/agent-configuration.spec.ts b/tests/e2e/agent-configuration.spec.ts index 227e67ba551..653e0f6c5eb 100644 --- a/tests/e2e/agent-configuration.spec.ts +++ b/tests/e2e/agent-configuration.spec.ts @@ -51,7 +51,7 @@ async function document(page: Page): Promise { await editor(page).press("ControlOrMeta+a"); const selectedEditor = await editor(page).evaluate(element => { const selection = window.getSelection(); - return document.activeElement === element && !!selection?.anchorNode && !!selection.focusNode + return window.document.activeElement === element && !!selection?.anchorNode && !!selection.focusNode && element.contains(selection.anchorNode) && element.contains(selection.focusNode); }); if (!selectedEditor) return false; @@ -135,7 +135,7 @@ test("compact YAML stays small as the company grows and preserves hidden configu await json(await request.put(`/api/agents/${agent.id}/instructions-bundle/file`, { data: { path: "AGENTS.md", content: instructions, baseHash: entry.contentHash, baseRevisionId: entry.revision?.id ?? null, } })); - const profiles = []; + const profiles: Array<{ id: string }> = []; for (let index = 0; index < 8; index++) profiles.push(await json(await request.post(`/api/companies/${company.id}/tools/profiles`, { data: { profileKey: `profile-${index}`, name: `Profile ${index}`, defaultAction: "deny", entries: Array.from({ length: 30 }, (_, tool) => ({ selectorType: "tool_name", effect: "include", toolName: `fixture_tool_${tool}` })), @@ -238,7 +238,7 @@ test("Advanced YAML persists settings and commands through their real resource A expect((await json(await request.get(`/api/agents/${agent.id}/instructions-bundle`))).files.map((file: { path: string }) => file.path)).not.toContain("workflow.md"); expect(cleared.spec.skills).toBeUndefined(); expect(cleared.spec.tools).toBeUndefined(); - expect(cleared.spec.channels![0].settings.allowUnlinkedPeople).toBe(true); + expect(cleared.spec.channels![0].settings!.allowUnlinkedPeople).toBe(true); expect(cleared.spec.channels![0].settings).not.toHaveProperty("communicationInstructions"); expect((await json(await request.get(`/api/agents/${agent.id}/keys`))).find((row: { id: string }) => row.id === key.id).revokedAt).not.toBeNull(); await page.screenshot({ path: info.outputPath("saved-agent-yaml.png") }); @@ -305,7 +305,7 @@ test("a lost creation response requires reload and cannot blindly replay command // Reproduce the observed CI failure once: a copy contains surrounding page // text as well as the YAML header. The read helper must reacquire a clean // editor copy instead of accepting the header and parsing the whole page. - await page.evaluate(() => document.addEventListener("copy", event => { + await page.evaluate(() => window.document.addEventListener("copy", event => { event.clipboardData?.setData("text/plain", "Skip to Main Content\napiVersion: paperclip/v1\nCopied surrounding page text"); event.preventDefault(); }, { capture: true, once: true })); diff --git a/tests/e2e/board-attachment-receipts.spec.ts b/tests/e2e/board-attachment-receipts.spec.ts index 4b89c54f1a3..044e1d03772 100644 --- a/tests/e2e/board-attachment-receipts.spec.ts +++ b/tests/e2e/board-attachment-receipts.spec.ts @@ -3,6 +3,7 @@ import { expect, test, type APIRequestContext, + type APIResponse, type Locator, type Page, } from "@playwright/test"; @@ -18,7 +19,7 @@ type Attachment = { type Comment = { id: string; body: string; clientRequestId?: string | null }; async function body( - response: Awaited>, + response: Pick, ): Promise { expect(response.ok(), `${response.status()}: ${await response.text()}`).toBe( true, @@ -612,7 +613,7 @@ test("announcement dismissal handler tolerates detachment after its element snap const dismiss = page.getByRole("complementary", { name: "Paperclip announcements" }).getByRole("button", { name: "Dismiss announcement" }); const snapshot = await dismiss.elementHandles(); expect(snapshot).toHaveLength(1); - await snapshot[0]!.evaluate(element => element.remove()); + await snapshot[0]!.evaluate(element => (element as ChildNode).remove()); dismiss.elementHandles = async () => snapshot; await dismissAnnouncement(page, dismiss); await expect(dismiss).toHaveCount(0); diff --git a/tests/e2e/chat-adapters-ui-providers.spec.ts b/tests/e2e/chat-adapters-ui-providers.spec.ts index 6ebe799976c..bd3f627f2bc 100644 --- a/tests/e2e/chat-adapters-ui-providers.spec.ts +++ b/tests/e2e/chat-adapters-ui-providers.spec.ts @@ -614,13 +614,6 @@ test.describe.serial("native chat adapter UI", () => { await expectMinimumProviderSetup(page, provider); await fillProviderSetup(page, provider); - if (provider.provider === "github") { - // A completed click does not mean the async configure request reached the mock. - await expect.poll(() => mock.setupAttempts).toBe(2); - expect(mock.githubPrivateKeyMatchedFile).toBe(true); - expect(mock.githubPrivateKeyMatchedPaste).toBe(true); - } - if (provider.provider === "telegram") { const submittedToken = "123456:e2e-redacted"; // The next step intentionally has its own identity-readiness alert. @@ -762,25 +755,19 @@ test.describe.serial("native chat adapter UI", () => { { id: `resource-${provider.provider}`, enabled: true }, ]); - if (provider.provider === "github") { + const directMessages = page.getByRole("switch", { + name: "Allow direct messages", + }); + await expect(directMessages).toBeVisible(); + if (provider.provider === "discord") { await expect( - page.getByRole("heading", { name: "Private conversations" }), - ).toHaveCount(0); - } else { - const directMessages = page.getByRole("switch", { - name: "Allow direct messages", - }); - await expect(directMessages).toBeVisible(); - if (provider.provider === "discord") { - await expect( - page.getByText( - "People must also enable Direct Messages in their shared Discord server’s Privacy Settings.", - ), - ).toBeVisible(); - } - await directMessages.click(); - await expect.poll(() => mock.allowDirectMessages).toBe(true); + page.getByText( + "People must also enable Direct Messages in their shared Discord server’s Privacy Settings.", + ), + ).toBeVisible(); } + await directMessages.click(); + await expect.poll(() => mock.allowDirectMessages).toBe(true); if (provider.provider === "microsoft-teams") { const groupChats = page.getByRole("switch", { name: "Allow group chats", @@ -978,93 +965,13 @@ test.describe.serial("native chat adapter UI", () => { expect(new URL(page.url()).searchParams.get("reconnect")).toBe("1"); await expect( page.getByRole("heading", { - name: - provider.provider === "github" - ? "Reconnect GitHub App" - : provider.provider === "slack" ? "Add Slack credentials" : provider.setupHeading, + name: provider.provider === "slack" ? "Add Slack credentials" : provider.setupHeading, }), ).toBeVisible(); await expect( page.getByRole("button", { name: "Choose an active agent" }), ).toHaveCount(0); - if (provider.provider === "github") { - const connectButton = page.getByRole("button", { - name: provider.setupButton, - }); - await expect( - page.getByText( - /Under the target user or organization, create a new GitHub App/, - ), - ).toHaveCount(0); - await expect( - page.getByText( - /Leave App ID and private key blank to reuse saved credentials/, - ), - ).toBeVisible(); - await page.getByLabel("GitHub App ID").fill("123456"); - await page - .getByLabel("Private key (PEM)") - .fill("reconnect-private-key"); - await expect(connectButton).toBeEnabled(); - await page.evaluate((buttonName) => { - const state = window as typeof window & { - __githubConnectEnabledAfterRotation?: boolean; - }; - state.__githubConnectEnabledAfterRotation = false; - new MutationObserver(() => { - const button = [...document.querySelectorAll("button")].find( - (candidate) => candidate.textContent?.trim() === buttonName, - ); - if (button instanceof HTMLButtonElement && !button.disabled) { - state.__githubConnectEnabledAfterRotation = true; - } - }).observe(document.body, { - attributes: true, - childList: true, - subtree: true, - }); - }, provider.setupButton); - await page - .getByRole("button", { name: "Regenerate webhook secret" }) - .click(); - await expect(connectButton).toBeDisabled(); - await page.waitForTimeout(100); - expect( - await page.evaluate( - () => - ( - window as typeof window & { - __githubConnectEnabledAfterRotation?: boolean; - } - ).__githubConnectEnabledAfterRotation, - ), - ).toBe(false); - - await page.goBack(); - await page.getByText("Connection health and controls", { exact: true }).click(); - await expect( - page.getByRole("heading", { name: "Connection activity" }), - ).toBeVisible(); - await page - .getByRole("button", { name: "Reconnect", exact: true }) - .click(); - await expect( - page.getByRole("heading", { name: "Reconnect GitHub App" }), - ).toBeVisible(); - await page.getByLabel("GitHub App ID").fill("123456"); - await page - .getByLabel("Private key (PEM)") - .fill("reconnect-private-key"); - await expect( - page.getByRole("button", { name: provider.setupButton }), - ).toBeDisabled(); - mock.setWebhookVerified(); - await expect( - page.getByRole("button", { name: provider.setupButton }), - ).toBeEnabled(); - } - mock.setStatus("active"); await page.goto( `/${seed.prefix}/apps/chat/endpoint-${provider.provider}/activity`, diff --git a/tests/e2e/chat-adapters-ui.shared.ts b/tests/e2e/chat-adapters-ui.shared.ts index afa8a9686b9..5022b3afd17 100644 --- a/tests/e2e/chat-adapters-ui.shared.ts +++ b/tests/e2e/chat-adapters-ui.shared.ts @@ -284,6 +284,9 @@ export function endpointFixture(provider: ProviderCase, seed: Seed) { slackSetupMethod: undefined as "managed" | "automatic" | "manual" | "existing" | undefined, slackRegistration: undefined as SlackRegistrationState | undefined, slackApp: undefined as SlackAppConfiguration | undefined, + slackAccount: undefined as Record | undefined, + slackAvatar: undefined as Record | undefined, + github: undefined as Record | undefined, slackOAuthCallbackUri: "https://paperclip.example.test/api/chat-slack/oauth/callback", step: "provider_setup", authorizationUrl: @@ -301,7 +304,7 @@ export function endpointFixture(provider: ProviderCase, seed: Seed) { messagingEndpoint: `https://paperclip.example.test/api/chat-webhooks/public-${provider.provider}/microsoft-teams`, command: provider.provider === "slack" ? "/maya-public" : undefined, testStartedAt: null as string | null, - webhookVerifiedAt: null, + webhookVerifiedAt: null as string | null, webhookSecretConfigured: false, }, healthMessage: null, diff --git a/tests/e2e/company-context-refresh.spec.ts b/tests/e2e/company-context-refresh.spec.ts index 0a90d8a6fec..04f0698fb07 100644 --- a/tests/e2e/company-context-refresh.spec.ts +++ b/tests/e2e/company-context-refresh.spec.ts @@ -18,8 +18,8 @@ test.beforeAll(async () => { server: { host: "127.0.0.1", port: 0 }, plugins: [{ name: "company-context-regression", - resolveId(id) { if (id === "/src/__company_context_probe.tsx") return probePath; }, - load(id) { + resolveId(id: string) { if (id === "/src/__company_context_probe.tsx") return probePath; }, + load(id: string) { if (id !== probePath) return; return ` import React from "react"; @@ -39,7 +39,7 @@ test.beforeAll(async () => { }; `; }, - configureServer(vite) { + configureServer(vite: ViteDevServer) { vite.middlewares.use((req, res, next) => { if (req.url !== "/") return next(); res.setHeader("Content-Type", "text/html"); diff --git a/tests/e2e/connection-intents.spec.ts b/tests/e2e/connection-intents.spec.ts index 34ceaf43909..a2b37a9c4d8 100644 --- a/tests/e2e/connection-intents.spec.ts +++ b/tests/e2e/connection-intents.spec.ts @@ -250,7 +250,7 @@ async function waitForAgentRun( companyId: string, agentId: string, ) { - let terminalRun: { id: string; status: string } | null = null; + let terminalRun = null as { id: string; status: string } | null; await expect .poll( async () => { diff --git a/tests/e2e/execution-recovery/recovery.spec.ts b/tests/e2e/execution-recovery/recovery.spec.ts index 0ef482333e7..18ca0b85faf 100644 --- a/tests/e2e/execution-recovery/recovery.spec.ts +++ b/tests/e2e/execution-recovery/recovery.spec.ts @@ -109,7 +109,7 @@ for (const journey of [ }); try { const port = await listenOnFetchAllowedPort(fixture); - const env = { + const env: NodeJS.ProcessEnv = { ...process.env, PAPERCLIP_RECOVERY_CEO_LINEAGE: journey === "ceo_lineage" ? "1" : "0", IN_FEED_FIXTURE_KEY: "not-a-real-model-key", diff --git a/tests/e2e/in-feed-native/completion-stream.spec.ts b/tests/e2e/in-feed-native/completion-stream.spec.ts index 3095ca5b01a..e63c95bc6b4 100644 --- a/tests/e2e/in-feed-native/completion-stream.spec.ts +++ b/tests/e2e/in-feed-native/completion-stream.spec.ts @@ -34,7 +34,7 @@ test('a completion tool does not cut off a delayed final answer', async ({ page let child: ChildProcess | undefined; let logs = ''; try { - const env = { ...process.env, STREAM_FIXTURE_KEY: 'not-a-real-model-key', NODE_ENV: 'test', + const env: NodeJS.ProcessEnv = { ...process.env, STREAM_FIXTURE_KEY: 'not-a-real-model-key', NODE_ENV: 'test', PATH: `${root}/tests/e2e/fixtures/completion-stream-bin:${process.env.PATH}` }; delete env.DATABASE_URL; delete env.DATABASE_MIGRATION_URL; diff --git a/tests/e2e/in-feed-native/connections.spec.ts b/tests/e2e/in-feed-native/connections.spec.ts index c242a14ea6a..5b3f124cfc8 100644 --- a/tests/e2e/in-feed-native/connections.spec.ts +++ b/tests/e2e/in-feed-native/connections.spec.ts @@ -25,7 +25,7 @@ for (const journey of ['connect', 'decline', 'restart'] as const) test(`fresh na }); try { const port = await listenOnFetchAllowedPort(fixture); - const env = { ...process.env, IN_FEED_FIXTURE_KEY: 'not-a-real-model-key', NODE_ENV: 'test', PAPERCLIP_TEST_CONNECTION_DELIVERY_HOLD: journey === 'restart' ? '1' : '0', PATH: `${root}/tests/e2e/fixtures/in-feed-bin:${process.env.PATH}` }; + const env: NodeJS.ProcessEnv = { ...process.env, IN_FEED_FIXTURE_KEY: 'not-a-real-model-key', NODE_ENV: 'test', PAPERCLIP_TEST_CONNECTION_DELIVERY_HOLD: journey === 'restart' ? '1' : '0', PATH: `${root}/tests/e2e/fixtures/in-feed-bin:${process.env.PATH}` }; delete env.DATABASE_URL; delete env.DATABASE_MIGRATION_URL; processHandle = spawn(process.execPath, ['cli/node_modules/tsx/dist/cli.mjs', 'cli/src/index.ts', 'test-drive', '--harness', 'codex', '--api-key-env', 'IN_FEED_FIXTURE_KEY', '--company-name', 'In-feed native fixture', '--no-browser'], { cwd: root, env, detached: true, stdio: ['ignore', 'pipe', 'pipe'] }); processHandle.stdout!.on('data', (chunk) => { logs += chunk.toString(); }); diff --git a/tests/e2e/playwright-composer-stop.config.ts b/tests/e2e/playwright-composer-stop.config.ts index a58f8507034..f98508ae384 100644 --- a/tests/e2e/playwright-composer-stop.config.ts +++ b/tests/e2e/playwright-composer-stop.config.ts @@ -27,7 +27,7 @@ if (fixture) { { mode: 0o755 }, ); } -const server = base.webServer as Exclude; +const server = base.webServer as Exclude; export default defineConfig({ ...base, testMatch: "composer-stop.spec.ts", diff --git a/tests/e2e/playwright-transcript-delivery.config.ts b/tests/e2e/playwright-transcript-delivery.config.ts index f2e35aa2d67..c1d6eea22d3 100644 --- a/tests/e2e/playwright-transcript-delivery.config.ts +++ b/tests/e2e/playwright-transcript-delivery.config.ts @@ -5,7 +5,7 @@ import { defineConfig } from "@playwright/test"; import base from "./playwright.config"; const providerHome = fs.mkdtempSync(path.join(os.tmpdir(), "transcript-provider-")); -const server = base.webServer as Exclude; +const server = base.webServer as Exclude; export default defineConfig({ ...base, testMatch: "transcript-delivery.journey.ts", diff --git a/tests/e2e/tsconfig.json b/tests/e2e/tsconfig.json new file mode 100644 index 00000000000..ad82b65f0a9 --- /dev/null +++ b/tests/e2e/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "allowImportingTsExtensions": true, + "allowJs": true, + "jsx": "react-jsx", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "paths": { + "@/*": ["../../ui/src/*"], + "@paperclipai/shared": ["../../packages/shared/src/index.ts"] + }, + "rootDir": "../..", + "types": ["node"], + "typeRoots": ["../../server/node_modules/@types"] + }, + "include": ["./**/*.ts", "./**/*.tsx", "../../ui/src/vite-env.d.ts", "../../server/src/types/express.d.ts"] +} diff --git a/tests/e2e/tsconfig.layers.json b/tests/e2e/tsconfig.layers.json new file mode 100644 index 00000000000..f1c68632282 --- /dev/null +++ b/tests/e2e/tsconfig.layers.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": false, + "declarationMap": false + }, + "references": [{ "path": "../../server/node_modules/.cache/paperclip-typecheck/tsconfig.declarations.json" }] +} diff --git a/tests/perf/issue-detail/issue-detail.perf.spec.ts b/tests/perf/issue-detail/issue-detail.perf.spec.ts index 2dc5cca727d..e469e442533 100644 --- a/tests/perf/issue-detail/issue-detail.perf.spec.ts +++ b/tests/perf/issue-detail/issue-detail.perf.spec.ts @@ -10,13 +10,15 @@ const PAGE_READY_TIMEOUT_MS = 90_000; const HEADER_MEASURE = "issue-detail:navigate→header-paint"; const CONTENT_MEASURE = "issue-detail:navigate→content-paint"; -type Profile = { - name: "unthrottled" | "fast-4g-4x-cpu"; - latencyMs?: number; - downloadBytesPerSecond?: number; - uploadBytesPerSecond?: number; - cpuSlowdownRate?: number; -}; +type Profile = + | { name: "unthrottled"; latencyMs?: undefined } + | { + name: "fast-4g-4x-cpu"; + latencyMs: number; + downloadBytesPerSecond: number; + uploadBytesPerSecond: number; + cpuSlowdownRate: number; + }; type NetworkRecord = { requestId: string; diff --git a/tests/perf/issue-detail/rich-loading-fixture.ts b/tests/perf/issue-detail/rich-loading-fixture.ts index 7b32a59f6cb..a9fc3a0e539 100644 --- a/tests/perf/issue-detail/rich-loading-fixture.ts +++ b/tests/perf/issue-detail/rich-loading-fixture.ts @@ -83,7 +83,7 @@ export async function richLoadingFixture(request: APIRequestContext): Promise ({ - id: randomUUID(), companyId: company.id, issueId: issue.id, authorType: "agent", authorAgentId: agent.id, + id: randomUUID(), companyId: company.id, issueId: issue.id, authorType: "agent" as const, authorAgentId: agent.id, body: `## ${index === 199 ? "Latest investigation findings" : `Investigation update ${index}`}\n\n` + "Verified the saved workspace, inspected the reported behavior, and checked that the evidence still matches the current revision. ".repeat(index === 199 ? 12 : 5) + (index === 199 ? "\n\n[Read the saved analysis](#document-analysis) and [verification results](#document-verification).\n\n| Check | Result |\n|---|---|\n| Saved evidence | Complete |\n| Review | Waiting for your decision |" : ""), diff --git a/tests/perf/issue-detail/window-globals.d.ts b/tests/perf/issue-detail/window-globals.d.ts new file mode 100644 index 00000000000..4abf722fa69 --- /dev/null +++ b/tests/perf/issue-detail/window-globals.d.ts @@ -0,0 +1,9 @@ +export {}; + +// Mirrors the declarations in ui/src/lib/issue-detail-performance.ts, which these specs set from init scripts. +declare global { + interface Window { + __PAPERCLIP_ISSUE_DETAIL_NAVIGATE_START__?: number; + __PAPERCLIP_ISSUE_DETAIL_PROFILE__?: boolean; + } +} diff --git a/tests/storybook-visual/agent-personas.config.ts b/tests/storybook-visual/agent-personas.config.ts index bfda1740b67..bbdd8fbf8c6 100644 --- a/tests/storybook-visual/agent-personas.config.ts +++ b/tests/storybook-visual/agent-personas.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ snapshotPathTemplate: "{testDir}/.snapshots/agent-personas/{arg}{ext}", use: { browserName: "chromium", baseURL: process.env.PAPERCLIP_PERSONA_STORYBOOK_URL ?? "http://localhost:6106", - viewport: { width: 1200, height: 900 }, deviceScaleFactor: 1, reducedMotion: "reduce", + viewport: { width: 1200, height: 900 }, deviceScaleFactor: 1, contextOptions: { reducedMotion: "reduce" }, launchOptions: { args: ["--use-angle=swiftshader", "--enable-unsafe-swiftshader"] }, }, }); diff --git a/tests/storybook-visual/agent-personas.spec.ts b/tests/storybook-visual/agent-personas.spec.ts index 555a8e653ec..d735eac2331 100644 --- a/tests/storybook-visual/agent-personas.spec.ts +++ b/tests/storybook-visual/agent-personas.spec.ts @@ -24,7 +24,7 @@ test("500 avatars load images without WebGL, live modules or avatar frame loops" await page.addInitScript(() => { (window as any).__personaProbe = { webgl: 0, frames: 0 }; const getContext = HTMLCanvasElement.prototype.getContext; - HTMLCanvasElement.prototype.getContext = function (kind: string, ...args: any[]) { + HTMLCanvasElement.prototype.getContext = function (this: HTMLCanvasElement, kind: string, ...args: any[]) { if (kind.includes("webgl")) (window as any).__personaProbe.webgl++; return (getContext as any).call(this, kind, ...args); } as any; diff --git a/tests/storybook-visual/app-shell-design.spec.ts b/tests/storybook-visual/app-shell-design.spec.ts index 1f55ff87cfd..17992c32723 100644 --- a/tests/storybook-visual/app-shell-design.spec.ts +++ b/tests/storybook-visual/app-shell-design.spec.ts @@ -140,6 +140,7 @@ test("right divider matches the left highlight, resizes in both directions, pers }); test("both pane dividers animate a double-click reset while manual sizing stays immediate", async ({ page }) => { + await page.emulateMedia({ reducedMotion: "no-preference" }); await openShell(page, "three-panes"); const left = page.getByRole("separator", { name: "Resize sidebar", exact: true }); const leftShell = page.locator(".design-page-navigation"); diff --git a/tests/storybook-visual/feature-gap-stories.config.ts b/tests/storybook-visual/feature-gap-stories.config.ts index d774c97fda9..c5a41bb9fa7 100644 --- a/tests/storybook-visual/feature-gap-stories.config.ts +++ b/tests/storybook-visual/feature-gap-stories.config.ts @@ -12,6 +12,6 @@ export default defineConfig({ use: { baseURL: process.env.PAPERCLIP_GAP_STORYBOOK_URL ?? "http://localhost:6136", viewport: { width: 1280, height: 900 }, - reducedMotion: "reduce", + contextOptions: { reducedMotion: "reduce" }, }, }); diff --git a/tests/storybook-visual/fireflies-pr.config.ts b/tests/storybook-visual/fireflies-pr.config.ts index 13bcabdcd24..3ea471f78f0 100644 --- a/tests/storybook-visual/fireflies-pr.config.ts +++ b/tests/storybook-visual/fireflies-pr.config.ts @@ -8,6 +8,6 @@ export default defineConfig({ retries: 0, outputDir: "./test-results/fireflies-pr", reporter: [["list"]], - use: { baseURL: "http://127.0.0.1:6149", viewport: { width: 1440, height: 1000 }, reducedMotion: "reduce", trace: "retain-on-failure" }, + use: { baseURL: "http://127.0.0.1:6149", viewport: { width: 1440, height: 1000 }, contextOptions: { reducedMotion: "reduce" }, trace: "retain-on-failure" }, webServer: { command: "node ../../scripts/serve-storybook-static.mjs --port 6149", url: "http://127.0.0.1:6149/index.json", reuseExistingServer: false }, }); diff --git a/tests/storybook-visual/imessage-photon.config.ts b/tests/storybook-visual/imessage-photon.config.ts index aaac3e55cef..6dc8bc0d5f9 100644 --- a/tests/storybook-visual/imessage-photon.config.ts +++ b/tests/storybook-visual/imessage-photon.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ testDir: ".", testMatch: "imessage-photon.spec.ts", workers: 2, timeout: 30_000, retries: 0, outputDir: "./test-results/imessage-photon", reporter: [["list"]], - use: { browserName: "chromium", baseURL: "http://127.0.0.1:6128", reducedMotion: "reduce" }, + use: { browserName: "chromium", baseURL: "http://127.0.0.1:6128", contextOptions: { reducedMotion: "reduce" } }, webServer: { command: "node ../../scripts/serve-storybook-static.mjs --port 6128", url: "http://127.0.0.1:6128/index.json", reuseExistingServer: false, diff --git a/tests/storybook-visual/in-feed-connections.config.ts b/tests/storybook-visual/in-feed-connections.config.ts index 32aa673a073..2325532f13d 100644 --- a/tests/storybook-visual/in-feed-connections.config.ts +++ b/tests/storybook-visual/in-feed-connections.config.ts @@ -1,5 +1,5 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ testDir: '.', testMatch: 'in-feed-connections.spec.ts', workers: 4, timeout: 30_000, retries: 0, outputDir: './test-results/in-feed', reporter: [['list']], - use: { browserName: 'chromium', baseURL: 'http://127.0.0.1:6126', reducedMotion: 'reduce' }, + use: { browserName: 'chromium', baseURL: 'http://127.0.0.1:6126', contextOptions: { reducedMotion: 'reduce' } }, webServer: { command: 'node ../../scripts/serve-storybook-static.mjs --port 6126', url: 'http://127.0.0.1:6126/index.json', reuseExistingServer: false } }); diff --git a/tests/storybook-visual/playwright.config.ts b/tests/storybook-visual/playwright.config.ts index 0e7608f1859..2e099a3d53e 100644 --- a/tests/storybook-visual/playwright.config.ts +++ b/tests/storybook-visual/playwright.config.ts @@ -49,7 +49,7 @@ export default defineConfig({ deviceScaleFactor: 1, // JS-driven tickers/timers key off prefers-reduced-motion for // deterministic captures (CSS animations are already disabled). - reducedMotion: "reduce", + contextOptions: { reducedMotion: "reduce" }, baseURL: process.env.STORYBOOK_TEST_BASE_URL ?? `http://localhost:${storybookPort}`, }, webServer: process.env.STORYBOOK_TEST_BASE_URL ? undefined : { diff --git a/tests/storybook-visual/remote-mcp-connections.config.ts b/tests/storybook-visual/remote-mcp-connections.config.ts index 041b689c720..da791479e95 100644 --- a/tests/storybook-visual/remote-mcp-connections.config.ts +++ b/tests/storybook-visual/remote-mcp-connections.config.ts @@ -3,6 +3,6 @@ import { defineConfig } from "@playwright/test"; export default defineConfig({ testDir: ".", testMatch: "remote-mcp-connections.spec.ts", workers: 1, fullyParallel: false, timeout: 180_000, retries: 0, outputDir: "./test-results/remote-mcp", reporter: [["list"]], - use: { browserName: "chromium", baseURL: "http://127.0.0.1:6138", reducedMotion: "reduce", actionTimeout: 10_000, trace: "retain-on-failure" }, + use: { browserName: "chromium", baseURL: "http://127.0.0.1:6138", contextOptions: { reducedMotion: "reduce" }, actionTimeout: 10_000, trace: "retain-on-failure" }, webServer: { command: "node ../../scripts/serve-storybook-static.mjs --port 6138", url: "http://127.0.0.1:6138/index.json", reuseExistingServer: false }, }); diff --git a/tests/storybook-visual/repository-skill-packages.config.ts b/tests/storybook-visual/repository-skill-packages.config.ts index b969734a280..764afd07f02 100644 --- a/tests/storybook-visual/repository-skill-packages.config.ts +++ b/tests/storybook-visual/repository-skill-packages.config.ts @@ -11,6 +11,6 @@ export default defineConfig({ use: { baseURL: process.env.PAPERCLIP_REPOSITORY_STORYBOOK_URL ?? 'http://localhost:6106', viewport: { width: 1280, height: 900 }, - reducedMotion: 'reduce', + contextOptions: { reducedMotion: 'reduce' }, }, }); diff --git a/tests/storybook-visual/task-list-reproduction-trial.config.ts b/tests/storybook-visual/task-list-reproduction-trial.config.ts index e130e5f2ae4..17c0e04c082 100644 --- a/tests/storybook-visual/task-list-reproduction-trial.config.ts +++ b/tests/storybook-visual/task-list-reproduction-trial.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1, colorScheme: "dark", - reducedMotion: "reduce", + contextOptions: { reducedMotion: "reduce" }, locale: "en-US", timezoneId: "America/Los_Angeles", launchOptions: { executablePath: process.env.TRIAL_CHROMIUM_EXECUTABLE }, diff --git a/tests/storybook-visual/task-sidebar-loading.spec.ts b/tests/storybook-visual/task-sidebar-loading.spec.ts index 77bced22d48..c31526ee666 100644 --- a/tests/storybook-visual/task-sidebar-loading.spec.ts +++ b/tests/storybook-visual/task-sidebar-loading.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test"; -test.use({ viewport: { width: 1440, height: 960 }, reducedMotion: "no-preference" }); +test.use({ viewport: { width: 1440, height: 960 }, contextOptions: { reducedMotion: "no-preference" } }); async function sampleLayout(page: import("@playwright/test").Page, duration: number) { return page.evaluate(async (duration) => { diff --git a/tests/storybook-visual/trial-2/task-list-reproduction-trial.spec.ts b/tests/storybook-visual/trial-2/task-list-reproduction-trial.spec.ts index 395d3ba7c5e..46844829e97 100644 --- a/tests/storybook-visual/trial-2/task-list-reproduction-trial.spec.ts +++ b/tests/storybook-visual/trial-2/task-list-reproduction-trial.spec.ts @@ -10,7 +10,7 @@ const clock = "2026-10-15T00:00:00.000Z"; test.use({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1, - colorScheme: "dark", reducedMotion: "reduce", locale: "en-US", + colorScheme: "dark", contextOptions: { reducedMotion: "reduce" }, locale: "en-US", timezoneId: "America/Los_Angeles", launchOptions: { executablePath: process.env.TRIAL_CHROMIUM_EXECUTABLE }, }); diff --git a/tests/storybook-visual/typography-foundations.spec.ts b/tests/storybook-visual/typography-foundations.spec.ts index 6ebbadad34a..3e39dff26bf 100644 --- a/tests/storybook-visual/typography-foundations.spec.ts +++ b/tests/storybook-visual/typography-foundations.spec.ts @@ -390,7 +390,7 @@ test("mixed-font tool text shares one uninterrupted shimmer loop", async ({ page const animations = element.getAnimations({ subtree: true }); const animation = animations.find(candidate => (candidate as CSSAnimation).animationName === "shimmer-text-slide")!; const timing = animation.effect!.getTiming(); - const keyframes = animation.effect!.getKeyframes(); + const keyframes = (animation.effect as KeyframeEffect).getKeyframes(); // Sample the old dead zone: the background must keep moving after 60%. animation.pause(); animation.currentTime = Number(timing.duration) * 0.65; diff --git a/tests/tsconfig.playwright.json b/tests/tsconfig.playwright.json new file mode 100644 index 00000000000..615325aa972 --- /dev/null +++ b/tests/tsconfig.playwright.json @@ -0,0 +1,28 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "paths": { + "@/*": ["../ui/src/*"], + "@paperclipai/shared": ["../packages/shared/src/index.ts"] + }, + "rootDir": "..", + "types": ["node"], + "typeRoots": ["../server/node_modules/@types"] + }, + "include": [ + "ai-connections-app/**/*.ts", + "ai-connections-review/**/*.ts", + "canary-onboarding/**/*.ts", + "hiring-ai-connections/**/*.ts", + "html-preview/**/*.ts", + "perf/**/*.ts", + "release-smoke/**/*.ts", + "storybook-visual/**/*.ts", + "../ui/src/vite-env.d.ts" + ] +} diff --git a/ui/src/components/AttentionInteractionResolver.test.ts b/ui/src/components/AttentionInteractionResolver.replace.test.ts similarity index 100% rename from ui/src/components/AttentionInteractionResolver.test.ts rename to ui/src/components/AttentionInteractionResolver.replace.test.ts